# Layout: `chart_table` — 上图表+下表格 > 幻灯片尺寸: 13.333 × 7.5 英寸 | 边距: 0.6 英寸 | 内容宽: 12.133 英寸 --- ## 用途 图表和明细数据一页展示:走势图 + 数据明细、收益图 + 各期收益表、指数图 + 成分股数据。 --- ## ASCII 线框图 ``` ┌─────────────────────────────────────────────────────────────┐ │ [标题] y=0.25 │ │ ▬▬ (accent 装饰线) y=0.90 │ │ │ │ ╔══════════════════════════════════════════════════════╗ │ │ ║ ║ │ │ ║ [ 图 表 区 ] ║ │ │ ║ ║ │ │ ╚══════════════════════════════════════════════════════╝ │ │ │ │ ┌────────┬─────────┬─────────┬─────────┬─────────────┐ │ │ │ 指标名 │ 数值A │ 数值B │ 数值C │ 数值D │ │ ← 表头行(accent背景) │ ├────────┼─────────┼─────────┼─────────┼─────────────┤ │ │ │ 行1 │ xx.x% │ xx.x% │ xx.x% │ xx.x% │ │ │ │ 行2 │ xx.x% │ xx.x% │ xx.x% │ xx.x% │ │ │ │ 行3 │ xx.x% │ xx.x% │ xx.x% │ xx.x% │ │ │ └────────┴─────────┴─────────┴─────────┴─────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` --- ## 分区比例与布局参数 ### 高度分配 ``` chart_table_ratio: 55/45(默认)、60/40、70/30 chart_start_y = 1.20 chart_end_y = 1.20 + 7.5 * ratio_chart = 5.325(默认55%) table_start_y = chart_end_y + 0.15(间距) table_end_y = 7.10 实际各值: ratio 55/45: chart_h=3.525", table_area_h=1.60" ratio 60/40: chart_h=3.900", table_area_h=1.225" ratio 70/30: chart_h=4.550", table_area_h=0.575"(仅一行表头+1-2数据行) ``` > **建议比例**: 55/45(图表适中,表格可放3-5行数据) --- ## 精确坐标标注 ### 固定元素 | 元素 | x | y | w | h | 字号 | 颜色 | 对齐 | |------|---|---|---|---|------|------|------| | 页面标题 | 0.60 | 0.25 | 12.133 | 0.60 | title_size-2 | text_dark | 左 | | 装饰线 | 0.60 | 0.90 | 2.00 | 0.04 | — | accent | — | ### 图表区 | 参数 | 值(55/45 比例) | |------|----------------| | chart_x | 0.60 | | chart_y | 1.20 | | chart_w | 12.133 | | chart_h | 3.525 | ### 表格区(55/45 比例,表格起始 y=4.875) **表头行**: | 元素 | x | y | w | h | 字号 | 颜色 | 对齐 | |------|---|---|---|---|------|------|------| | 表头背景 | 0.60 | 4.875 | 12.133 | 0.38 | — | primary | — | | 表头文字(每列) | col_x | 4.875 | col_w | 0.38 | body_size-1 | text_light | 中 | **数据行(斑马条纹)**: ``` row_h = 0.38 最多行数 = floor((7.10 - 4.875 - 0.38) / 0.38) = floor(1.845 / 0.38) ≈ 4行 偶数行背景: secondary(极淡),奇数行: card_bg(白) ``` | 元素 | x | y(相对 row_y) | w | h | 字号 | 颜色 | 对齐 | |------|---|----------------|---|---|------|------|------| | 行背景(偶数行) | 0.60 | +0 | 12.133 | 0.38 | — | secondary(30%透明)| — | | 第1列(标签) | 0.60 | +0.03 | col_widths[0] | 0.32 | body_size-1 | text_dark | 左 | | 数据列(对齐右) | col_x[j] | +0.03 | col_widths[j] | 0.32 | body_size-1 | text_dark | 右 | | 行间细线 | 0.60 | +0.36 | 12.133 | 0.02 | — | border | — | ### 列宽分配(自动计算) ```python def calc_col_widths(headers, table_w=12.133): """ 第1列(标签列)分配 25%,其余等分剩余空间 或根据 headers 自定义 """ n_cols = len(headers) label_w = table_w * 0.25 # 3.033" data_w = (table_w - label_w) / (n_cols - 1) return [label_w] + [data_w] * (n_cols - 1) # 示例(5列): # label_w = 3.033",每数据列 = (12.133-3.033)/4 = 2.275" ``` **正负值着色规则**(表格数据): - 含 "+" 或正数: `positive` 绿色 - 含 "-" 或负数: `negative` 红色 - 中性/非数字: `text_dark` --- ## 数据接口 ```python data = { "title": str, # 页面标题 "subtitle": str, # 可选 # 图表参数(与 layout_chart_full 相同) "df": pd.DataFrame, # 图表数据 "categories_col": str, # 分类列名(X轴) "series_config": list, # 系列配置 "style_config": StyleConfig | None, # 图表样式 "layout_config": ChartLayoutConfig | None, # 表格参数 "headers": list[str], # 列头(含第一列标签名) "rows": list[list[str]], # 数据行,每行一个列表 "col_widths": list[float] | None, # 可选,自定义各列宽(英寸),默认自动分配 "chart_ratio": float, # 可选,图表占高比(0.5-0.75),默认 0.55 "header_color": str, # 可选,表头背景色变量名,默认 "primary" "zebra": bool, # 是否斑马条纹,默认 True "highlight_last_row": bool, # 是否高亮最后一行(汇总行),默认 False "source": str, # 可选,数据来源 } ``` --- ## 实现伪代码 ```python def layout_chart_table(slide, data, theme): from ppt_station.chart_builder.api import create_combo_chart m = theme["margin"] sw, sh = 13.333, 7.5 cw = sw - 2 * m set_slide_bg(slide, theme["bg_light"]) add_text(slide, data["title"], x=m, y=0.25, w=cw, h=0.60, font_size=theme["title_size"]-2, color=theme["text_dark"], bold=True, font_name=theme["header_font"]) add_rect(slide, m, 0.90, 2.0, 0.04, fill=theme["accent"]) ratio = data.get("chart_ratio", 0.55) chart_y = 1.20 chart_h = (sh - chart_y - 0.40) * ratio # 可用高度的 ratio 给图表 table_y = chart_y + chart_h + 0.15 # 图表 from pptx.util import Inches create_combo_chart( slide=slide, df=data["df"], categories_col=data["categories_col"], series_config=data["series_config"], position=(Inches(m), Inches(chart_y)), size=(Inches(cw), Inches(chart_h)), style_config=data.get("style_config"), layout_config=data.get("layout_config"), ) # 表格 headers = data["headers"] rows = data["rows"] n_cols = len(headers) col_widths = data.get("col_widths") if col_widths is None: label_w = cw * 0.25 data_w = (cw - label_w) / (n_cols - 1) col_widths = [label_w] + [data_w] * (n_cols - 1) col_xs = [m] for w in col_widths[:-1]: col_xs.append(col_xs[-1] + w) row_h = 0.38 header_color = theme.get(data.get("header_color", "primary"), theme["primary"]) # 表头 add_rect(slide, m, table_y, cw, row_h, fill=header_color) for j, (header, cx, cw_j) in enumerate(zip(headers, col_xs, col_widths)): align = "left" if j == 0 else "right" px = cx + (0.10 if j == 0 else 0) add_text(slide, header, x=px, y=table_y+0.04, w=cw_j-0.10, h=row_h-0.08, font_size=theme["body_size"]-1, color=theme["text_light"], bold=True, align=align, font_name=theme["body_font"]) # 数据行 for k, row in enumerate(rows): ry = table_y + row_h + k * row_h if ry + row_h > sh - 0.30: break # 超出页面则停止 # 斑马背景 if data.get("zebra", True) and k % 2 == 1: add_rect(slide, m, ry, cw, row_h, fill=theme["secondary"], alpha=20) # 汇总行高亮 is_last = (k == len(rows) - 1) if is_last and data.get("highlight_last_row", False): add_rect(slide, m, ry, cw, row_h, fill=theme["secondary"], alpha=40) for j, (cell, cx, cw_j) in enumerate(zip(row, col_xs, col_widths)): align = "left" if j == 0 else "right" # 正负着色 cell_str = str(cell) if j > 0: try: val = float(cell_str.replace("%", "").replace("+", "").replace(",", "")) c = theme["positive"] if val > 0 else (theme["negative"] if val < 0 else theme["text_dark"]) except ValueError: c = theme["text_dark"] else: c = theme["text_dark"] bold = is_last and data.get("highlight_last_row", False) px = cx + (0.10 if j == 0 else 0) add_text(slide, cell_str, x=px, y=ry+0.04, w=cw_j-0.10, h=row_h-0.08, font_size=theme["body_size"]-1, color=c, bold=bold, align=align, font_name=theme["body_font"]) # 行间线 add_rect(slide, m, ry+row_h-0.01, cw, 0.01, fill=theme["border"]) if data.get("source"): add_text(slide, f"数据来源:{data['source']}", x=m, y=7.10, w=cw, h=0.30, font_size=theme["caption_size"], color=theme["text_muted"], font_name=theme["body_font"]) ``` --- ## 设计要点 1. **55/45 比例** 是最常用配置:图表够大,表格放得下 3-4 行有效数据 2. **表头用 `primary` 背景 + `text_light` 白字**,与数据行形成强烈对比 3. **斑马条纹** 轻微区分奇偶行(透明度 20% 的 secondary),不喧宾夺主 4. **正负值自动着色** 让数字表格直观传达方向 5. **70/30 比例** 适合图表是核心时,表格仅作辅助参考(仅1-2行数据) 6. **表格列宽** 第1列(标签/日期)固定 25%,其余等分 --- ## 测试数据示例 ```python import pandas as pd from ppt_station.chart_builder.models import SeriesConfig df = pd.DataFrame({ "日期": ["2024-01", "2024-02", "2024-03", "2024-04", "2024-05", "2024-06", "2024-07", "2024-08"], "组合收益": [1.2, 2.5, -0.8, 3.1, 2.0, 1.5, 0.7, 2.3], "基准收益": [0.8, 1.9, -1.2, 2.4, 1.6, 1.1, 0.5, 1.8], }) data = { "title": "2024年月度收益对比", "df": df, "categories_col": "日期", "series_config": [ SeriesConfig(key="组合收益", name="本组合", type="bar", axis="primary"), SeriesConfig(key="基准收益", name="基准", type="line", axis="primary"), ], "chart_ratio": 0.55, "headers": ["时间段", "1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月"], "rows": [ ["本组合(%)", "+1.2", "+2.5", "-0.8", "+3.1", "+2.0", "+1.5", "+0.7", "+2.3"], ["基准(%)", "+0.8", "+1.9", "-1.2", "+2.4", "+1.6", "+1.1", "+0.5", "+1.8"], ["超额(%)", "+0.4", "+0.6", "+0.4", "+0.7", "+0.4", "+0.4", "+0.2", "+0.5"], ["累计超额(%)", "+0.4", "+1.0", "+1.4", "+2.1", "+2.5", "+2.9", "+3.1", "+3.6"], ], "highlight_last_row": True, "source": "Wind 资讯,内部测算", } ```