skills/ppt-station-skill/reference/layout-chart-table.md
wangyitong a65adcc2e5 Initial commit: merged, deduplicated, and vetted skill collection
Sources: extracted from two upstream archives (skill-repo, skills-main),
merged with the following policy:

- 15 broken symlinks (pointing to /Users/jameslee/.cc-switch/skills or
  ../../.agents/skills on a foreign machine) discarded
- 3 real name collisions with identical content (ai-pair, ifind-http-api,
  zhipu-websearch) kept as one copy
- Functional overlaps deduped keeping the strongest variant:
  - docx family: kept docx (official, full toolchain) + docx-cn
    (GB/T 9704 Chinese official-document constants),
    dropped docx_writer (no scripts, name collided with docx)
  - humanizer family: kept humanizer-zh (6 zh reference docs),
    dropped humanizer (en, redundant for CN workflow)
- Skills that only ran in a foreign environment removed:
  ablemind-ops, app-publish, hlb-design-system, openclaw-adj-skill,
  claude-driver
- alphapai excluded from this public repo because its SKILL.md hard-coded
  live credentials

Result: 25 skills, 572 files, ~7.5 MB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 14:47:12 +08:00

12 KiB
Raw Permalink Blame 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

列宽分配(自动计算)

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

数据接口

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,                         # 可选,数据来源
}

实现伪代码

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%,其余等分

测试数据示例

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 资讯,内部测算",
}