商业图表生成(咨询风)
〇 定位
通用图表库(smart-charts、Excel、matplotlib)会画柱状图折线图,但画不了咨询顾问天天在用的那批图——BCG 矩阵、波特五力、增长瀑布、MECE 问题树、龙卷风敏感性。这类图过去只能手工在 PPT 里拼。
本技能把 12 类咨询专用图做成可批量生成的矢量图:给一段 JSON 数据 → 出一张可直接放进汇报材料的 SVG。
- 零依赖:只用 Python 标准库,不装任何包,离线可跑。
- 矢量输出:SVG 可直接插入 Word / PPT / 网页,放大不失真,可在 PPT 里取消组合后二次编辑。
- 中文友好:内置中文字体栈与 CJK 折行算法,长标签自动换行不溢出。
一 触发场景
- 用户要做汇报材料/方案/董事会材料,需要配图。
- 用户提到下列任一图名:
BCG 矩阵、波特五力、瀑布图/桥接图、SWOT、逻辑树/问题树、价值链、安索夫矩阵、龙卷风图/敏感性分析、S 曲线/三条曲线、优先级矩阵、金字塔、象限图/竞争格局。 - 用户说"画个图""出个图表""配张图",且语境是商业分析而非纯数据可视化。
- 不触发:只要通用图表(柱状/折线/饼图)——那用 smart-charts 或 Excel 更合适。
- 不触发:只要位图(PNG/JPG)且场景是公众号/微信——SVG 在部分微信场景不渲染,需先转图。
二 选图指南(最重要的一步)
先判断用户要表达什么关系,再选图。选错图比画错图更致命。
| 用户想表达 | 用这个 | type |
|---|---|---|
| 业务/产品组合怎么排兵布阵 | BCG 增长-份额矩阵 | bcg |
| 这个行业赚不赚钱、利润被谁拿走 | 波特五力 | five_forces |
| 从一个数到另一个数,中间增减怎么来的 | 增长瀑布 / 桥接图 | waterfall |
| 内外部优劣势盘点 | SWOT | swot |
| 一个大问题怎么拆成不重不漏的小问题 | MECE 逻辑树 | mece |
| 玩家之间怎么站位(自定义两个维度) | 竞争格局象限 | quadrant |
| 利润沉淀在价值链的哪个环节 | 波特价值链 | value_chain |
| 增长从哪来(老产品老市场→新产品新市场) | 安索夫矩阵 | ansoff |
| 哪个变量对结果影响最大 | 龙卷风敏感性 | tornado |
| 新旧业务怎么接力、什么时候到顶 | 三层增长 S 曲线 | scurve |
| 一堆举措先做哪个 | 优先级矩阵(影响力×可行性) | priority |
| 从使命到动作的层层拆解 | 战略金字塔 | pyramid |
三 三分钟上手(渐进式)
从"看一眼效果"到"批量出图",四步递进。第一次用建议完整走一遍,之后直接跳到第 3 步。
PY="C:/Users/ghszb/.workbuddy/binaries/python/envs/default/Scripts/python.exe"
S="C:/Users/ghszb/.workbuddy/skills/consulting-charts/scripts"
# 第 1 步:看全部图表类型(30 秒)
"$PY" "$S/make_chart.py" --list
# 第 2 步:看效果(12 套内置示例一次出齐,第一次必跑)
"$PY" "$S/make_chart.py" --demo all --out-dir ./gallery
"$PY" "$S/build_gallery.py" # 生成 gallery.html,一屏预览 12 张
# 第 3 步:正式出图(--data 接受文件路径,也接受直接粘 JSON 字符串)
"$PY" "$S/make_chart.py" --data spec.json --out 战略组合.svg
# 第 4 步:批量出图(spec.json 写成数组即可)
"$PY" "$S/make_chart.py" --data specs.json --out-dir ./out
最小可用只需要两条命令:把示例 spec 改三个数字 → 出图。
# 第 5 步(可选):一键自检环境是否正常 —— 16 项断言,全过返回 0
"$PY" "$S/make_chart.py" --selftest
命令与参数总表
| 参数 | 作用 | 备注 |
|---|---|---|
| --list | 列出 12 种图表类型 | 退出码 0 |
| --demo <type>\|all | 用内置示例出图 | 与 --data 互斥 |
| --data <路径\|JSON串\|-> | 出图数据源 | - 表示从 stdin 读 |
| --type <type> | spec 没写 type 时用它 | 与 spec 里的 type 冲突会报错 |
| --out <文件> | 单图输出路径 | 批量模式禁用(用 --out-dir) |
| --out-dir <目录> | 批量输出目录 | 默认 . |
| --selftest | 运行 16 项内置断言 | 全过 0,有失败 3 |
四 完整实战案例(照抄即可跑)
场景:给董事会汇报"今年营收从 120 到 158 是怎么来的,明年增长从哪来"。
第一步 · 选图。营收从 A 到 B、中间增减构成 → waterfall(增长瀑布);明年增长来源 → ansoff(安索夫矩阵)。两张图够了,不要堆满。
第二步 · 写 spec.json(数字来自财务口径,不编):
{"type":"waterfall","title":"营收增量的 76% 来自量增,涨价贡献为负",
"subtitle":"2026 财年 · 单位:百万元","start":120,"unit":"百万元",
"steps":[{"label":"销量增长","value":41},
{"label":"新品上市","value":16},
{"label":"售价下滑","value":-12},
{"label":"汇率损失","value":-7}]}
第三步 · 出图:
"$PY" "$S/make_chart.py" --data spec.json --out 营收桥接.svg
第四步 · 过质量门(见第七节):标题写的是结论不是"营收分析";4 个条目 ≤8;数字与财务口径一致;标签无溢出。
产出:营收桥接.svg,1240×780,矢量。插入 PowerPoint 后右键「转换为形状」即可改配色与文字。注意:steps 的合计(120+41+16-12-7 = 158)由脚本自动算出终值柱,不必手动传。
五 数据格式
spec.json 通用字段:type(必填)、title、subtitle、foot、w(默认 1240)、h(默认 780)。
以下为各 type 的专属字段。
bcg — BCG 矩阵
items[]:name、x 相对份额(0–1,越大越靠左)、y 市场增长率(0–1)、size 营收
{"type":"bcg","title":"业务组合诊断","items":[
{"name":"智能传感","x":0.86,"y":0.78,"size":320},
{"name":"代工制造","x":0.80,"y":0.16,"size":460}]}
five_forces — 波特五力
forces[]:name、score(0–5)、note;center 中心文案
{"type":"five_forces","center":"行业\n吸引力","forces":[
{"name":"现有竞争者","score":4.5,"note":"同质化严重"},
{"name":"替代品威胁","score":2.5,"note":"短期可控"}]}
waterfall — 增长瀑布
start 期初值;steps[]:label、value(可正可负);可选 start_label/end_label/unit。终值柱由脚本自动求和。
{"type":"waterfall","start":120,"unit":"百万元","steps":[
{"label":"销量增长","value":28},{"label":"售价下滑","value":-19}]}
swot — SWOT
S/W/O/T 各为数组,元素可为字符串或 {text, note}
mece — MECE 逻辑树
root 根问题;branches[]:name、children[](元素为字符串或 {name, note})
quadrant — 竞争格局象限
xlabel/ylabel/quadrants(四个象限名,从左上顺时针);items[]:name、x、y、size
value_chain — 价值链
support[] 支持活动(自上而下);primary[] 主活动({name, margin});margin_label
ansoff — 安索夫矩阵
market_penetration / product_development / market_development / diversification
tornado — 龙卷风敏感性
base 基准值;factors[]:name、low(负向)、high(正向)。自动按影响幅度排序,无需手工排。
scurve — 增长 S 曲线
years 横轴年数;curves[]:name、ceiling 天花板、steepness 陡峭度、mid 拐点位置
priority — 优先级矩阵
同 quadrant,默认象限名为「立即做 / 重点规划 / 顺手做 / 暂缓」
pyramid — 战略金字塔
levels[]:name、items[](自上而下,顶层最窄)
六 输出与集成
- 默认输出
.svg。插入 Word / PPT:直接「插入 → 图片」;PPT 中右键「转换为形状」可二次编辑配色与文字。 - 需要位图(PNG)时,用浏览器打开 SVG 截图,或让 agent 调用图片转换工具——本技能刻意不引入 cairosvg 等依赖。
- 画布尺寸:
w/h可调。默认 1240×780(16:10),适合单页放一张图;要做 PPT 整页配图可用w:1600, h:900。
七 质量门(交付前自检)
- [ ] 图型选对了吗?(对照第二节选图指南)
- [ ] 标题写的是结论还是"XX分析"?优先用结论式标题(如"主业见顶,第二曲线尚未接棒")。
- [ ] 数字与源文件一致,无凭空编造的数据。
- [ ] 标签是否溢出?长文本用
note拆成副行,或调大w。 - [ ] 气泡/条形数量 ≤8,超过就先归类合并——图一乱结论就没了。
- [ ] 跑过
--demo确认渲染正常再交稿。
八 常见错误与应对(Anti-Patterns)
| 常见错误做法 | 为什么会错 | 正确做法 |
|---|---|---|
| 用柱状图代替瀑布图 | 看不出"从 A 到 B 中间增减是怎么来的",这是瀑布图唯一存在的理由 | 用 waterfall,steps 传正负值 |
| 标题写「营收分析」「业务组合」 | 标题是废话,读者看完不知道结论 | 写结论:"营收增量的 76% 来自量增" |
| BCG 气泡一铺十几个 | 图一乱结论就没了,等于没画 | 先归类合并,压到 ≤8 个 |
| BCG/象限的 x/y 填绝对值(如填营收 320) | 坐标是相对位置 0–1,填绝对值会让气泡全挤在角落 | 先归一化到 0–1;绝对值放 size |
| 给 tornado 手工排序 factors | 脚本自动按影响幅度排序,手工排反而会覆盖正确顺序 | 按业务顺序填即可,排序交给脚本 |
| 条目文字超过 12 字仍硬塞进 name | 会挤爆画布或互相重叠 | 主标不超过 10–12 字,补充说明拆到 note |
| 直接把 SVG 塞进公众号/微信 | 部分微信与旧版浏览器不渲染 SVG | 先转 PNG 再上传 |
| 用 matplotlib 硬画 BCG / 龙卷风 | 四象限底色、气泡标注、自动折行都要手写,产出还不能二次编辑 | 用本技能,输出可在 PPT 里转形状继续改 |
| 一次性出十几张图塞进一份材料 | 董事会材料一页一个结论,图多说明没想清楚 | 先定每页结论,一页一图 |
| 同时传 --demo bcg --data spec.json | 以为两个都生效,实际用户数据被静默丢弃 | 二选一;新版已显式报错(退出码 2),不再静默 |
| 指望 --type 覆盖 spec 里的 type | 旧版 --type 是死参数,完全不生效 | 新版:spec 无 type 时生效,冲突时明确报错 |
| 在 spec 的 out 里写 ../xxx.svg 想指定上级目录 | 会写穿 --out-dir,污染预期外的位置 | 新版已净化为 <out-dir>/xxx.svg;要换目录请用 --out-dir |
| 批量数组里混进字符串当占位 | 旧版抛裸 AttributeError 中断整批 | 数组每项必须是对象;新版记为单张失败,其余继续 |
| 用 Excel 另存的 GBK 编码 JSON 当 spec | 旧版抛裸 UnicodeDecodeError | 新版自动兜底 UTF-8/BOM/GBK/GB18030 |
九 异常与兜底
所有报错统一以 [错误] 前缀打到 stdout(不是 stderr),便于 agent 直接抓取。
| 现象 | 原因 | 处理 |
|---|---|---|
| [错误] 未知图表类型 '' | spec 漏了 type,或 type 拼错/大小写不对 | 在 spec 顶层加 "type":"bcg";跑 --list 复制准确名称 |
| [错误] 未知图表类型 'nope'(批量里 [失败]) | 同上,发生在批量第 N 张 | 批量不会中断,其余照常出,最后看汇总行定位 |
| [错误] 元素不是 JSON 对象(收到 str) | 批量数组里混进了字符串/数字/数组 | 数组每一项都必须是 {...} 对象 |
| [错误] 找不到 spec 文件:xxx | 路径写错或文件不在当前目录 | 核对路径;也可直接粘 JSON 字符串,或用 --data - 从 stdin 读 |
| [错误] JSON 解析失败 | 尾逗号、单引号、注释 | JSON 不支持注释与尾逗号,先用编辑器校验 |
| [错误] 字段 w 必须是整数,收到 'abc' | w/h 传了非数字 | 传数字,如 "w":1600 |
| [错误] --demo 与 --data 不能同时传 | 两个数据源都给了 | --demo 会覆盖你的数据,故显式拒绝;二选一 |
| [错误] --type bcg 与 spec 里的 type swot 冲突 | 命令行与 spec 各写了一个 type | 只保留一个;--type 仅在 spec 没写 type 时生效 |
| [错误] 批量模式请用 --out-dir | 批量出图却传了 --out | 批量用 --out-dir ./out,文件名由 spec 的 out 或 type 决定 |
| [错误] 未预期的内部错误:XxxError | 兜底分支,理论上不该出现 | 带上完整命令与 spec 反馈;退出码 3 |
| [依赖缺失] No module named 'svgkit' | 只复制了 make_chart.py | 本技能零第三方依赖,把整个 scripts/ 目录一起复制 |
| 文字重叠/挤出画布 | 标签过长或条目过多 | 条目压到 8 个以内;长句拆 note;调大 w/h |
| 中文字体显示成方框 | 目标机缺中文字体 | SVG 已内置 Microsoft YaHei/PingFang SC 字体栈,一般无需处理 |
| 输出 SVG 在浏览器里空白 | 文件未写完或路径含特殊字符 | 用 XML 解析器验一次;路径避免中文以外的特殊字符 |
退出码(封闭,只有三个)
| 码 | 含义 | 典型场景 |
|---|---|---|
| 0 | 成功 | 单图出图成功;批量全部成功 |
| 2 | 输入/使用错误 | 坏 JSON、未知 type、文件不存在、参数互斥、w 非数字;批量中任意一张失败也是 2 |
| 3 | 环境或意外错误 | 依赖缺失、未预期内部异常、Ctrl+C 中断 |
退出约定:所有错误以 [错误] 前缀输出到 stdout 并退出,不产出半截 SVG;成功时逐张打印 [生成] 绝对路径。
批量模式下单张失败不中断其余,最后打印 批量完成:成功 X / 失败 Y / 共 Z 并逐条列出失败原因;只要有一张失败就返回 2。
输出路径一定是干净的
spec 里的 out 会被净化后再拼到 --out-dir 下:目录分隔符、盘符一律剥掉,只留最后一段文件名;
空名回退到 <type>.svg;缺 .svg 自动补上;Windows 非法字符 <>:"|?* 替换成 _。
所以 "out": "../../evil.svg" 只会写成 <out-dir>/evil.svg,不会写穿输出目录。
十 已验证
以下全部为 2026-09-15 在 Python 3.13.12 上实跑确认,可用 --selftest 一键复现(16 项断言)。
--selftest:16 项内置断言全过,退出码 0。- 12 种图全部通过生成测试:XML 合法、零元素越界、每张非空(实测 2026-09-03,Python 3.13.12)。
- 12 类全部内置
--demo示例,--demo all --out-dir稳定产出 12 个 SVG。 - 零第三方依赖,Python 3.9+ 可直接运行。
- 批量模式:含 1 个未知 type 的 3 张数组 → 成功 2 / 失败 1,退出码 2,未中断。
- 批量模式:数组混入字符串元素 → 该张记
[失败] 元素不是 JSON 对象,其余继续,不再抛AttributeError。 - GBK / UTF-8 / UTF-8-BOM 三种编码的 spec 文件均可正常读取(旧版 GBK 会抛裸
UnicodeDecodeError)。 --data指向不存在的文件 →[错误] 找不到 spec 文件,退出码 2(旧版误报成 JSON 解析失败)。--data -从 stdin 读 spec 可用。--demo与--data同时传 → 显式报错退出码 2(旧版会静默丢弃--data)。--type在 spec 无 type 时生效(实测--type waterfall正确渲染出瀑布图),冲突时报错。out路径注入"../INJECTED.svg"→ 收敛到<out-dir>/INJECTED.svg,未越界。w传"abc"→[错误] 字段 w 必须是整数,退出码 2。- 顶层兜底:注入
RuntimeError后返回[错误] 未预期的内部错误+ 退出码 3,无裸 traceback。 - 依赖守卫:只复制
make_chart.py而缺svgkit时给出[依赖缺失]与修复指引,退出码 3。
微信扫一扫