skills/ppt-station-skill/reference/layout-chart-table.md

305 lines
12 KiB
Markdown
Raw Normal View History

# 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 | — | secondary30%透明)| — |
| 第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 资讯,内部测算",
}
```