91 lines
6.8 KiB
Markdown
91 lines
6.8 KiB
Markdown
# 图表开发约定
|
||
|
||
## 新建图表时
|
||
|
||
必须从 `theme.js` 取共享配置,不要手写等价的值:
|
||
|
||
```js
|
||
import {
|
||
PALETTE, FONT, axisCommon, periodAxis, titleStyle,
|
||
lineSeries, barSeries, standardLegend, footnote, watermarks, LAYOUT,
|
||
} from '../theme.js';
|
||
```
|
||
|
||
- 折线用 `lineSeries()`,柱状用 `barSeries()` —— 空心圆点、标签强显等细节已内置
|
||
- 图例用 `standardLegend(names)`,间距用 `LAYOUT.legendTop` / `LAYOUT.gridTop`
|
||
- 期间轴用 `periodAxis`(自带 `2026H1 → 26H1`、`2026H2 → 26全年` 简写)
|
||
|
||
## 测试
|
||
|
||
**每次只测本次改动的图表,不要跑全量。** 这是硬要求,不是建议:
|
||
|
||
```bash
|
||
node test/regress.mjs ppmt_ip_share # 改了一张,就只测这一张
|
||
node test/regress.mjs ppmt_ip_share ppmt_core_margin # 改了多张,列出来
|
||
npm test # 全量,只在改了 theme.js / csv.js 等公共模块时才跑
|
||
```
|
||
|
||
全量跑纯属浪费,而且十几张无关图表的输出会盖住本次改动的真实问题。不传参数时才是全量,所以 `npm test` 保持向后兼容。
|
||
|
||
它会断言 `legend.top`、`grid.top`、折线 `symbolSize`/`borderWidth`,并检查 SVG 字体引号。加新图记得同步往 `regress.mjs` 顶部的 `all` 数组加一行,否则不会被测到。
|
||
|
||
**右侧纵向图例的图会报 `legend.top=middle (期望85)`,这是预期的,不要去"修"。** 那条断言的 85px 基准是给顶部横向图例定的,对 `orient: 'vertical'` + `top: 'middle'` 的图不适用(`ppmt_ip_share`、`ppmt_ip_share_history` 都会报)。
|
||
|
||
## 踩过的坑
|
||
|
||
**不要用 `...legendStyle`。** 它遗留 `top: 62` 且曾带 `icon: 'roundRect'`——后者会盖掉 ECharts 按系列类型自动生成的图例(折线本该是「短线+空心圆」)。用 `standardLegend()`。
|
||
|
||
**`label.position` 不接受函数。** 写了会被静默忽略、退化成默认位置,实测确认过。要让同一系列内不同点朝向不同,只能逐点覆盖 `data`:`{ value, label: { position: 'top' } }`。
|
||
|
||
**字体名用单引号。** `FONT` 里若用双引号,SVG 渲染时会提前闭合 `style="..."` 属性生成非法 SVG。canvas 看不出来,导出 SVG 才炸。
|
||
|
||
**多折线标签避让:按「朝间隙大的一侧」放,不要按排名奇偶交替。** 奇偶交替会让排名相邻的两条线一个朝下一个朝上、相向靠拢,值接近时标签间距被压到小于字高,然后被 `hideOverlap` 丢弃(表现是「某个数看不到」)。正确做法见 `ppmt_margins.js` 的 `labeledData()`:比较上下间隙取大者,最高值必朝上、最低值必朝下。
|
||
|
||
**矮柱子的标签会被自动隐藏。** 数据跨度大时(如 1.9 ~ 169.1),矮柱标签比柱子还高就不渲染了。`barSeries()` 已设 `label.overflow: 'none'` 兜住;手写柱状系列时记得加。
|
||
|
||
## 数据口径
|
||
|
||
**数值默认保留一位小数。** CSV 数据列与图表展示(柱/点标签、tooltip)统一保留一位小数(如 `71.1`、`311.4%`),四舍五入;确实需要其他精度时必须在 `footnote()` 或代码注释里说明原因。
|
||
|
||
**CSV 存四位年份**(`2026H1`),简写只在显示层做。核对数据时无歧义。
|
||
|
||
**利润表的 `H2` 列是全年累计,不是下半年。** 已核对:2025H2 营收 371.2 = 2025H1 的 138.76 + 单半年 232.44。所以 `26全年` 这类标签指累计值。
|
||
|
||
**但「IP收入和占比」表的 `H2` 是单半年,不是累计。** 同一个 workbook 里两种口径,别想当然。已核对:该表 2025H2 合计 232.44 = 利润表 371.2 − 138.76,五年逐年都吻合。**所以这张表的图不能用 `periodAxis`**——它会把 `2025H2` 渲染成「25全年」,是错的。改用 `categoryAxis` + 只去世纪前缀的 formatter(`v.replace(/^20/, '')` → `25H2`),见 `ppmt_ip_share_history.js`。新建图表前先确认数据源用的是哪种口径。
|
||
|
||
**衍生指标标注来源。** 非 GAAP 净利率是 `非GAAP净利 ÷ 营业收入` 算出来的,表里没这行;2020H1/H2 非 GAAP 缺失,用归母净利替代。这类替代必须写进 `footnote()`。
|
||
|
||
**桥图/瀑布图要验算闭合。** 各项占营收比是税前口径,加总到净利率前须乘税盾 `(1 - 有效税率)`;税率变化单独成项。恒等式 `净利率 = 税前利润率 × (1-税率)`,分解为 `Δ净 = Δ税前×(1-t₁) − 税前₂×Δt`。改完核对首尾误差应 < 0.05pct。
|
||
|
||
## 配色
|
||
|
||
先分清两件事,别混用同一个门槛。**选色的功夫要和系列数成正比**——两个系列的图不该选半天。
|
||
|
||
**① 同一张图内的系列要能互相区分。** 这条永远要满足,是可读性问题:
|
||
|
||
- ≤ 4 个系列:手挑,凭常识就够,**不用跑 ΔE**
|
||
- 5~9 个系列:手挑后验算一次,两两 > 25
|
||
- ≥ 10 个系列:ΔE 到 ~24 就收手,硬凑 > 25 会把颜色逼得很丑(见 `ppmt_ip_share.js` 的 11 色谱)
|
||
|
||
**② 跨图撞色只在「同类编码 + 位置紧邻」时才查。** 目的是防止读者把两张图的系列对应起来,所以:
|
||
|
||
- 折线图 vs 紧邻的折线图、或两图共用同一套分类色谱 → 要查
|
||
- 柱+线双轴图 vs 饼图 → **不用查**,编码类型本身就区分开了
|
||
- 隔着好几张、主题无关的图 → 不用查
|
||
|
||
**不要拿整页累积的所有颜色当避让集合。** 这是个只会越来越紧的棘轮:每加一张图就多几个色要避让。实测过,对整页 26 色都要求 ΔE > 25,可行域只剩 18%(只对紧邻图要求则有 39%);而且**现有配色自己就有 13 对不达标**——`星星人`/`orange` ΔE 6.5、`DIMOO`/`green` 7.5、`MONSTERS`/`red` 10.4、`blue`/`navy` 14.4。拿一条自己都没遵守的规则去卡新图,结果就是在两个系列的图上白耗三轮验算。
|
||
|
||
**不要用贪心/模拟退火/随机搜索去"解"配色。** 太耗时,且算出来的色往往不好看(机器只顾拉开距离,不管协调)。手写一条色相谱再验算:
|
||
|
||
1. 按色相环顺序挑(如 红→橙→金→橄榄→翡翠→青→蓝→紫),残差项("其他"/"外采")用中性灰
|
||
2. 同色系连续多项靠**明暗交替**拉开(深蓝 → 浅蓝紫 → 紫 → 浅兰紫),不然挤在一起分不清
|
||
3. 只对不达标的几项微调,一两轮收手
|
||
|
||
`ppmt_margins.js` 顶部记着当初选深青/芥黄/茄紫/玫红的过程。注意那张图是「4 条折线 + 同页有别的折线图」,正属于 ② 要查的情形;别把它的严格度套到所有图上。它当年淘汰 `slate`/`cyan`/`indigo` 的理由是「离 `blue`/`navy` 仅 ΔE 17~19」,可 `blue` 和 `navy` 自己才差 14.4——这个先例本身就偏严,别照抄。
|
||
|
||
## 脚注
|
||
|
||
`footnote()` 单行约 60 字符封顶(1200px 画布),超了用 `\n` 手动断行。结尾统一 `| @思考的Joey`。
|
||
|
||
水印文案在 `theme.js` 的 `WATERMARK_TEXT`,改一处全局生效。
|