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

293 lines
10 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.

# Layout: `waterfall` — 瀑布图/归因分析
> 幻灯片尺寸: 13.333 × 7.5 英寸 | 边距: 0.6 英寸 | 内容宽: 12.133 英寸
---
## 用途
收益归因、成本分解、利润桥图Profit Bridge、组合贡献分析。
展示"从起点 → 各因素贡献 → 终点"的累积变化过程。
---
## ASCII 线框图
```
┌─────────────────────────────────────────────────────────────┐
│ [标题] y=0.25 │
│ ▬▬ (accent 装饰线 2.0") y=0.90 │
│ │
│ 起始值 增加项 增加项 减少项 最终值 │
│ ┌──────┐ ┌──────┐ │
│ │ │ │ +██ │ +██ │ │ │
│ │ 基础 │ │ │ │ -██ │ ┌──────┐ │
│ │ │ │ │ │ │ │ 合计 │ │
│ └──────┘ └──────┘ └──────┘ └──────┘ │
│ 8.50% +2.10% +1.30% -1.80% 10.10% │
│ [期初收益] [权益贡献] [债券贡献] [汇率拖累] [期末收益] │
│ │
└─────────────────────────────────────────────────────────────┘
```
---
## 数据结构说明
瀑布图数据包含:
- **start** — 起始值(期初,作为第一个完整柱)
- **items** — 各贡献因素(浮动柱,正向或负向)
- **end** — 终止值(期末,作为最后一个完整柱,可自动计算)
---
## 精确坐标标注
### 固定元素
| 元素 | 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 | — |
| 零基准线 | 0.60 | 图表区中心 | 12.133 | 0.02 | — | text_muted | — |
### 图表区域
| 参数 | 值 | 说明 |
|------|---|------|
| chart_x | 0.60 | 图表左边界 |
| chart_y | 1.30 | 图表顶部(内容区起始) |
| chart_w | 12.133 | 图表宽度 |
| chart_h | 5.50 | 图表高度(到 y=6.80 |
| bar_area_h | 4.00 | 柱体绘制区高度y=1.50 ~ y=5.50 |
| label_y | 5.60 | 标签行 y柱下方 |
| value_y | 1.20 | 数值标注 y柱上方浮动 |
| footer_y | 6.20 | 数据来源行 |
### 柱体尺寸计算
```
N = len(items) + 2 # 总柱数start + items + end
bar_gap = 0.20 # 柱间距(英寸)
bar_w = (chart_w - bar_gap * (N - 1)) / N # 每柱宽
# N=5: bar_w = (12.133 - 0.80) / 5 = 2.267"
# N=6: bar_w = (12.133 - 1.00) / 6 = 1.856"
# N=7: bar_w = (12.133 - 1.20) / 7 = 1.562"
# N=8: bar_w = (12.133 - 1.40) / 8 = 1.342"
bar_x[i] = chart_x + i * (bar_w + bar_gap)
```
### 柱体高度计算
```
# 图表坐标系
y_axis_max = max(所有累积值的最大值) * 1.15 # 留 15% 上边距
y_axis_min = min(所有累积值的最小值) * 1.15 # 留 15% 下边距(负值时)
y_range = y_axis_max - y_axis_min
# 将数据值映射到像素(英寸)
def value_to_y(v):
return chart_y + bar_area_h * (1 - (v - y_axis_min) / y_range)
# 起始柱完整柱从0到start_value
start_bar_top = value_to_y(start_value)
start_bar_bottom = value_to_y(0)
start_bar_h = start_bar_bottom - start_bar_top
# 贡献柱(浮动柱,从累积值到累积值+delta
for item in items:
bar_top = value_to_y(max(cumsum, cumsum + item.value))
bar_bottom = value_to_y(min(cumsum, cumsum + item.value))
bar_h = bar_bottom - bar_top
cumsum += item.value
# 连接虚线(前一柱的浮动底部 → 当前柱底部)
连接线: w=bar_gap, h=0.01, 颜色=text_muted, 虚线样式
```
### 颜色规则
| 柱类型 | 条件 | 颜色变量 |
|--------|------|---------|
| 起始柱 | — | primary |
| 正向贡献柱 | item.value > 0 | positive |
| 负向贡献柱 | item.value < 0 | negative |
| 终止柱 | | primary |
| 连接虚线 | | text_muted |
### 数值标注(柱顶/底 + 数值)
| 元素 | 位置 | 字号 | 颜色 | 格式 |
|------|------|------|------|------|
| 起始/终止数值 | 柱顶上方 0.10" | body_size | text_dark | "X.XX%" |
| 正向贡献值 | 柱顶上方 0.10" | body_size | positive | "+X.XX%" |
| 负向贡献值 | 柱底下方 0.10" | body_size | negative | "-X.XX%" |
| 柱体内标注高柱时 | 柱体垂直居中 | body_size-1 | text_light | 同上 |
### 底部标签行
| 元素 | x | y | w | h | 字号 | 颜色 | 对齐 |
|------|---|---|---|---|------|------|------|
| 柱体标签 | bar_x[i] | 5.60 | bar_w | 0.45 | caption_size | text_muted | |
---
## 数据接口
```python
data = {
"title": str, # 页面标题
"subtitle": str, # 可选,如 "2024年全年收益归因分析"
"start": {
"label": str, # 如 "期初收益"
"value": float, # 如 8.50
"value_fmt": str, # 如 "8.50%" 或 "850亿"
},
"items": [
{
"label": str, # 贡献因素名称8字以内
"value": float, # 数值(正=向上,负=向下)
"value_fmt": str, # 显示文字(如 "+2.10%"
"direction": "up" | "down", # 可选,若不传则根据 value 符号判断
},
...
],
"end": {
"label": str, # 如 "期末收益"
"value": float | None, # None则自动计算(start + sum(items))
"value_fmt": str | None,
},
"unit": str, # 可选,如 "%" 或 "亿元"用于Y轴标注
"show_connector": bool, # 是否显示连接虚线,默认 True
}
```
---
## 实现伪代码
```python
def layout_waterfall(slide, data, theme):
m = theme["margin"]
sw = 13.333
cw = sw - 2 * m # 12.133
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"])
# 计算柱体布局
all_bars = [data["start"]] + data["items"] + [data["end"]]
N = len(all_bars)
bar_gap = 0.20
bar_w = (cw - bar_gap * (N - 1)) / N
chart_y = 1.50
bar_area_h = 4.00
# 计算值域
cumsum = data["start"]["value"]
all_values = [0, cumsum]
for item in data["items"]:
cumsum += item["value"]
all_values.append(cumsum)
end_val = data["end"].get("value") or cumsum
all_values.append(end_val)
y_max = max(all_values) * 1.15
y_min = min(all_values) * 1.15 if min(all_values) < 0 else 0
y_range = y_max - y_min
def val_to_y(v):
return chart_y + bar_area_h * (1 - (v - y_min) / y_range)
# 绘制零线
zero_y = val_to_y(0)
add_rect(slide, m, zero_y, cw, 0.015, fill=theme["text_muted"])
# 绘制柱体
cumsum = 0
prev_top_y = None
for i, bar in enumerate(all_bars):
bx = m + i * (bar_w + bar_gap)
is_total = (i == 0 or i == N - 1)
if is_total:
v = bar.get("value", cumsum)
top_y = val_to_y(max(v, 0))
bot_y = val_to_y(min(v, 0))
color = theme["primary"]
else:
v = bar["value"]
base = cumsum
top_y = val_to_y(max(base, base + v))
bot_y = val_to_y(min(base, base + v))
color = theme["positive"] if v >= 0 else theme["negative"]
cumsum += v
bar_h = bot_y - top_y
add_rect(slide, bx, top_y, bar_w, bar_h, fill=color)
# 连接线
if prev_top_y is not None and data.get("show_connector", True) and not is_total:
add_rect(slide, bx - bar_gap, prev_top_y, bar_gap, 0.01,
fill=theme["text_muted"])
prev_top_y = bot_y if v >= 0 else top_y
# 数值标注
fmt = bar.get("value_fmt", f"{v:+.2f}%")
label_y = top_y - 0.30 if v >= 0 else bot_y + 0.05
add_text(slide, fmt,
x=bx, y=label_y, w=bar_w, h=0.28,
font_size=theme["body_size"], color=color,
bold=True, align="center", font_name=theme["body_font"])
# 底部标签
add_text(slide, bar["label"],
x=bx, y=5.60, w=bar_w, h=0.45,
font_size=theme["caption_size"], color=theme["text_muted"],
align="center", font_name=theme["body_font"])
```
---
## 设计要点
1. **柱间连接虚线** 从前一柱的"累积底部"水平延伸到当前柱帮助读者追踪累积过程
2. **数值标注位置**: 正向柱标在顶部上方负向柱标在底部下方避免遮挡柱体
3. **起始/终止柱**汇总柱 `primary` 与贡献柱颜色形成视觉区分
4. **最多 8 个柱**含起始和终止超过时建议合并小贡献项为"其他"
5. **含负值时**零线显示并加粗0.02"负值柱延伸到零线以下
---
## 测试数据示例
```python
data = {
"title": "2024年组合收益归因分析",
"subtitle": "单位: 百分点(%",
"start": {"label": "期初基准", "value": 0, "value_fmt": "0%"},
"items": [
{"label": "权益资产", "value": 3.20, "value_fmt": "+3.20%"},
{"label": "债券资产", "value": 1.80, "value_fmt": "+1.80%"},
{"label": "另类资产", "value": 0.90, "value_fmt": "+0.90%"},
{"label": "汇率影响", "value": -0.60, "value_fmt": "-0.60%"},
{"label": "费用扣除", "value": -0.30, "value_fmt": "-0.30%"},
],
"end": {"label": "期末收益", "value": None, "value_fmt": None}, # 自动计算=5.00%
}
```
---
## 与 投行级研报 GTM 的对应
投行级研报 P4GDP分解)、P44EPS贡献等页面均使用堆叠柱变体
真正的"利润桥"瀑布图浮动柱在收益归因报告中极为常见需单独布局支持