skills/ppt-station-skill/reference/layout-dashboard.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

393 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 布局标注:`dashboard`(仪表盘页)
> 设计师交付物 · v1.0 · 2026-02-20
> 参考来源:投行级研报 图表页设计语言 + 现有 kpi.py / chart.py 实现规范
---
## 设计意图
一页内同时展示**核心数字 + 趋势图表**。金融场景典型用途:
- 组合季报首页:年化收益 / 夏普比率 / 最大回撤 / 规模 + 净值曲线图
- 宏观月报GDP / CPI / PMI / 利率 + 经济指标走势图
- 基金评价:超额收益 / 信息比率 / 跟踪误差 / Calmar + 比较图
---
## 变体 A主变体顶部 KPI 条 + 下方大图
### ASCII 线框图
```
┌─────────────────────────────────────────────────────────────┐ y=0
│ ▌ 页面标题bold, title_size-2, text_dark │ y=0.25~0.85
│ ████████ accent 装饰线2.0w × 0.04h │ y=0.90
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ y=1.10
│ │ ▌▌▌▌▌▌▌▌▌▌▌ │ │ ▌▌▌▌▌▌▌▌▌▌▌ │ │ ▌▌▌▌▌▌▌▌▌▌▌ │ │ (顶部色条 h=0.05)
│ │ 指标名 │ │ 指标名 │ │ 指标名 │ │ y=1.42
│ │ │ │ │ │ │ │
│ │ 12.3% │ │ 1.87 │ │ -4.2% │ │ y=1.72 大数字
│ │ │ │ │ │ │ │
│ │ +2.1% ↑ │ │ +0.15 ↑ │ │ 本期最低 │ │ y=2.50 变化/注释
│ └──────────────┘ └──────────────┘ └──────────────┘ │ y=2.80
│ │
│ 图表区副标题可选body_size, text_muted │ y=3.05
│ │
│ ┌────────────────────────────────────────────────────────┐ │ y=3.35
│ │ │ │
│ │ 大图表full width │ │ h=3.90
│ │ │ │
│ └────────────────────────────────────────────────────────┘ │ y=7.25
└─────────────────────────────────────────────────────────────┘ y=7.50
```
### 精确元素标注英寸m = theme["margin"] = 0.6
#### 全局常量
```
sw = 13.333 # 幻灯片宽度
sh = 7.5 # 幻灯片高度
m = theme["margin"] # 默认 0.6
content_w = sw - 2*m # = 12.133
```
#### 页面标题区
| 元素 | x | y | w | h | 样式 |
|------|---|---|---|---|------|
| 背景 | 0 | 0 | 13.333 | 7.5 | `bg_light` |
| 页面标题 | `m` | 0.25 | `content_w` | 0.6 | `text_dark`, bold, `title_size-2`, left, header_font |
| accent 装饰线 | `m` | 0.90 | 2.0 | 0.04 | `accent` fill |
#### KPI 卡片区3 张卡片)
```
n = len(cards) # 3 或 4
gap = 0.30 # 卡片间距
card_w = (content_w - gap*(n-1)) / n
card_h = 1.70
start_y = 1.10
```
**3 张卡片时**`card_w = (12.133 - 0.60) / 3 = 3.844`
**4 张卡片时**`card_w = (12.133 - 0.90) / 4 = 2.808`
每张卡片(以第 i 张为例,`cx = m + i*(card_w+gap)`
| 元素 | x相对卡片左边 | y相对幻灯片 | w | h | 样式 |
|------|----------------|---------------|---|---|------|
| 卡片背景 | `cx` | `start_y` = 1.10 | `card_w` | 1.70 | `card_bg` fill, `border` 边框 1pt, 圆角 0.08" |
| 顶部色条 | `cx + 0.12` | 1.10 + 0.10 = **1.20** | `card_w - 0.24` | 0.05 | `primary` fill |
| 指标名label | `cx + 0.20` | **1.40** | `card_w - 0.40` | 0.28 | `text_muted`, `body_size`, center, body_font |
| 大数字value | `cx + 0.20` | **1.72** | `card_w - 0.40` | 0.72 | `primary`, **`kpi_size`**, bold, center+middle, header_font |
| 变化值change | `cx + 0.20` | **2.50** | `card_w - 0.40` | 0.25 | `positive`/`negative`, `body_size+1`, bold, center, body_font |
> **change 颜色判断**:以 `+` 开头 → `positive`;以 `-` 开头 → `negative`
#### 图表区
| 元素 | x | y | w | h | 样式 |
|------|---|---|---|---|------|
| 图表区副标题(可选) | `m` | **3.05** | `content_w` | 0.25 | `text_muted`, `body_size-1`, left, body_font |
| 图表 | `m` | **3.35** | `content_w` | **3.90** | ChartBuilder |
> 有副标题时图表上移 0 px副标题和图表不重叠副标题占 3.05~3.30,图表从 3.35 开始)
> 底部安全区3.35 + 3.90 = **7.25**,留 0.25 底部安全边距 ✓
---
## 变体 B次变体左图 60% + 右侧 KPI 竖排
用于 KPI 较多4-5 个)且图表更重要的场景。
### ASCII 线框图
```
┌──────────────────────────────────────────────────────────┐
│ 页面标题 │ y=0.25
│ ▌▌ accent 线 │ y=0.90
│ │
│ ┌─────────────────────────────┐ ┌────────────────────┐ │ y=1.10
│ │ │ │ 指标名 │ │
│ │ │ │ 12.3% │ │ KPI card 1
│ │ │ │ +2.1% ↑ │ │
│ │ │ └────────────────────┘ │
│ │ 大图表 │ ┌────────────────────┐ │
│ │ (60% 宽) │ │ 指标名 │ │ KPI card 2
│ │ │ │ 1.87 │ │
│ │ │ └────────────────────┘ │
│ │ │ ┌────────────────────┐ │
│ │ │ │ 指标名 │ │ KPI card 3
│ │ │ │ -4.2% │ │
│ └─────────────────────────────┘ └────────────────────┘ │
└──────────────────────────────────────────────────────────┘
```
### 精确标注
```
chart_w = content_w * 0.60 # = 7.280
right_w = content_w * 0.38 # = 4.611 (留 0.02 给中间分隔)
gap_lr = content_w - chart_w - right_w # ≈ 0.242 居中分隔
right_x = m + chart_w + gap_lr # ≈ 8.122
chart_top = 1.20
chart_h = 7.5 - chart_top - 0.25 # = 6.05
```
| 元素 | x | y | w | h |
|------|---|---|---|---|
| 大图表 | `m`=0.6 | 1.20 | 7.280 | 6.05 |
| KPI 卡片竖排n张 | 8.122 | 见下方计算 | 4.611 | 见下方 |
**竖排 KPI 卡片高度计算**
```
total_h = 6.05 # 与图表等高
n = len(cards) # 通常 3-5 张
gap_v = 0.20 # 纵向间距
card_h = (total_h - gap_v*(n-1)) / n
start_y = 1.20
# 第 i 张 y = start_y + i*(card_h + gap_v)
```
竖排卡片内部布局card_h 可变):
- 左侧竖色条(而非顶部色条):`right_x`, `card_y+0.1`, `0.05`, `card_h-0.2`, `primary` fill
- 指标名:`right_x+0.25`, `card_y+0.15`, `right_w-0.45`, `0.28`, `text_muted`, body_size
- 大数字:`right_x+0.25`, `card_y+0.45`, `right_w-0.45`, `card_h*0.45`, `primary`, kpi_size*0.85, bold
- 变化值:底部 0.28 高度区域positive/negative
---
## 数据接口
### 变体 Atop_kpi 模式)
```python
data = {
# 必须
"title": "组合运行情况", # 页面标题
"cards": [ # 3-4 个 KPI 卡片
{
"label": "年化收益率", # 指标名
"value": "12.3%", # 已格式化数值(大数字)
"change": "+2.1%", # 可选:变化值(+开头→绿,-开头→红)
},
{
"label": "夏普比率",
"value": "1.87",
"change": "+0.15",
},
{
"label": "最大回撤",
"value": "-4.2%",
# change 省略时不显示变化行
},
],
# 图表(同 chart_full 的图表参数)
"df": df, # DataFrame
"categories_col": "日期",
"series_config": [...],
"style_config": None, # 可选
"layout_config": None, # 可选
# 可选
"subtitle": None, # 页面副标题accent 线后)
"chart_label": "净值走势", # 图表区左上方小标题
"variant": "top_kpi", # "top_kpi"(默认)| "side_kpi"
}
```
### 变体 Bside_kpi 模式)
同上,改 `"variant": "side_kpi"`,图表和 cards 含义相同。
---
## 实现伪代码
```python
def layout_dashboard(slide, data, theme):
m = theme["margin"]
sw = 13.333
content_w = sw - 2 * m
variant = data.get("variant", "top_kpi")
set_slide_bg(slide, theme["bg_light"])
# ── 标题区 ──────────────────────────────────────
add_text(slide, data["title"],
x=m, y=0.25, w=content_w, h=0.6,
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"])
if data.get("subtitle"):
add_text(slide, data["subtitle"],
x=m, y=1.00, w=content_w, h=0.30,
font_size=theme["body_size"] - 1, color=theme["text_muted"],
font_name=theme["body_font"])
# ── 分支:变体 A顶部 KPI + 下方图表)────────────
if variant == "top_kpi":
cards = data["cards"]
n = len(cards)
gap = 0.30
card_w = (content_w - gap * (n - 1)) / n
card_h = 1.70
start_y = 1.10
for i, card in enumerate(cards):
cx = m + i * (card_w + gap)
# 卡片背景(圆角)
add_rounded_rect(slide, cx, start_y, card_w, card_h,
fill=theme["card_bg"], line_color=theme["border"],
line_width=1)
# 顶部色条
add_rect(slide, cx + 0.12, start_y + 0.10, card_w - 0.24, 0.05,
fill=theme["primary"])
# 指标名
add_text(slide, card["label"],
x=cx + 0.20, y=start_y + 0.30, w=card_w - 0.40, h=0.28,
font_size=theme["body_size"], color=theme["text_muted"],
align="center", font_name=theme["body_font"])
# 大数字
add_text(slide, card["value"],
x=cx + 0.20, y=start_y + 0.62, w=card_w - 0.40, h=0.72,
font_size=theme["kpi_size"], color=theme["primary"],
bold=True, align="center", valign="middle",
font_name=theme["header_font"])
# 变化值
if card.get("change"):
change = card["change"]
color = theme["positive"] if not change.startswith("-") else theme["negative"]
add_text(slide, change,
x=cx + 0.20, y=start_y + 1.40, w=card_w - 0.40, h=0.25,
font_size=theme["body_size"] + 1, color=color,
bold=True, align="center", font_name=theme["body_font"])
# 图表区副标题
if data.get("chart_label"):
add_text(slide, data["chart_label"],
x=m, y=3.05, w=content_w, h=0.25,
font_size=theme["body_size"] - 1, color=theme["text_muted"],
font_name=theme["body_font"])
# 大图表
_add_chart_to_slide(
slide, data,
position=(Inches(m), Inches(3.35)),
size=(Inches(content_w), Inches(3.90)),
)
# ── 分支:变体 B左图 + 右侧竖排 KPI───────────
elif variant == "side_kpi":
chart_w = content_w * 0.60 # 7.280
right_w = content_w * 0.38 # 4.611
gap_lr = content_w - chart_w - right_w
right_x = m + chart_w + gap_lr
chart_top = 1.20
chart_h = 7.5 - chart_top - 0.25
_add_chart_to_slide(
slide, data,
position=(Inches(m), Inches(chart_top)),
size=(Inches(chart_w), Inches(chart_h)),
)
cards = data["cards"]
n = len(cards)
gap_v = 0.20
card_h = (chart_h - gap_v * (n - 1)) / n
for i, card in enumerate(cards):
cy = chart_top + i * (card_h + gap_v)
add_rounded_rect(slide, right_x, cy, right_w, card_h,
fill=theme["card_bg"], line_color=theme["border"], line_width=1)
# 左侧竖色条
add_rect(slide, right_x, cy + 0.10, 0.05, card_h - 0.20,
fill=theme["primary"])
# 指标名
add_text(slide, card["label"],
x=right_x + 0.25, y=cy + 0.15, w=right_w - 0.45, h=0.28,
font_size=theme["body_size"], color=theme["text_muted"],
font_name=theme["body_font"])
# 大数字(自适应 card_h
add_text(slide, card["value"],
x=right_x + 0.25, y=cy + 0.45, w=right_w - 0.45,
h=card_h * 0.45,
font_size=int(theme["kpi_size"] * 0.85), color=theme["primary"],
bold=True, valign="middle", font_name=theme["header_font"])
# 变化值
if card.get("change"):
change = card["change"]
color = theme["positive"] if not change.startswith("-") else theme["negative"]
add_text(slide, change,
x=right_x + 0.25, y=cy + card_h - 0.35, w=right_w - 0.45, h=0.28,
font_size=theme["body_size"], color=color,
bold=True, font_name=theme["body_font"])
```
---
## 设计决策说明
### 为什么 KPI 大数字用 `kpi_size` 而不是固定字号?
卡片数量影响卡片宽度3张→3.84英寸 vs 4张→2.81英寸),字号应跟随卡片宽度变化。`kpi_size` 是主题全局设置开发者可按主题调整建议3张卡时 kpi_size=36pt4张时=28pt
### 为什么顶部色条而非圆角变化?
参考 投行级研报 和现有 kpi.py 的一致性。色条 `primary` 颜色给卡片一个品牌锚点,同时避免全彩卡片在报告中过于抢眼。
### 变体 A 的图表起始 y=3.35 而非 3.10
- KPI 区结束1.10 + 1.70 = 2.80
- 分隔 gap0.25 → y=3.05
- 副标题区3.05~3.30(高度 0.25
- 图表内边距0.05
- 图表起始3.35
- 这样即使没有副标题,图表也在 y=3.35 开始(不因 `chart_label` 有无而跳动)
### 变体 B 的图表比例为 60%
投行级研报 中"1大+2小"布局中大图约占 60%。60% 给图表足够面积展示时间序列细节40% 给右侧 KPI 仍有约 5 英寸宽度,足够显示 2-3 位小数的数值。
---
## 与现有布局的区别
| 布局 | 与 dashboard 的区别 |
|------|-------------------|
| `kpi_cards` | 只有数字卡片,没有图表 |
| `chart_full` | 只有图表,没有 KPI 数字 |
| `two_charts` | 两个图表,没有 KPI 数字 |
| `dashboard` | **唯一** 在一页内同时显示 KPI + 趋势图的布局 |
---
## 测试用 data 示例
```python
import pandas as pd
df = pd.DataFrame({
"日期": pd.date_range("2024-01-01", periods=12, freq="ME"),
"组合净值": [1.00, 1.02, 1.05, 1.08, 1.07, 1.10, 1.12, 1.09, 1.13, 1.15, 1.18, 1.21],
"基准净值": [1.00, 1.01, 1.03, 1.05, 1.04, 1.06, 1.07, 1.06, 1.08, 1.10, 1.12, 1.14],
})
data = {
"title": "组合2024年运行情况",
"subtitle": "截至2024年12月31日",
"cards": [
{"label": "年化收益率", "value": "21.0%", "change": "+9.0% vs 基准"},
{"label": "夏普比率", "value": "1.87", "change": "+0.23"},
{"label": "最大回撤", "value": "-2.8%", "change": "-1.4% vs 基准"},
],
"df": df,
"categories_col": "日期",
"series_config": [
{"key": "组合净值", "name": "组合净值", "type": "line", "axis": "primary"},
{"key": "基准净值", "name": "基准净值", "type": "line", "axis": "primary"},
],
"chart_label": "净值走势2024年",
"variant": "top_kpi",
}
```