智能图表
本 skill 用于将数据文件(CSV/Excel/JSON)自动转化为交互式 ECharts 图表。
上传数据 → 自动分析 → 推荐图表类型 → 生成交互式 HTML。支持 16 种图表类型,多文件合并,以及 LLM 生成的数据转换代码(在沙箱中安全执行)。
以下各节面向 agent 编写。请阅读这些内容,以决定何时以及如何调用此 skill。
---
启用条件
满足以下任一条件时,加载此 skill:
- 用户提到:“分析数据”、“生成图表”、“数据可视化”、“图表”、“可视化”
- 用户提供数据文件并要求进行分析或可视化
- 用户要求根据表格数据生成图表或报告
/ 用户提到:「分析数据」「生成图表」「数据可视化」
---
硬性约束(必须遵循)
- 必须遵循 CLI 工作流:
data_parser.py→ 确认并推荐 →chart_generator.py。即使数据看起来杂乱,也不得编写临时 Python 脚本来替代 CLI 调用。 - 处理杂乱表头时必须使用 CLI 参数,不得绕过工作流。
data_parser.py和chart_generator.py均支持使用--skiprows N/--header-row N/--sheet <name|index>处理多行表头、开头的无关行和多工作表 Excel 文件。N 的值必须通过检查实际数据来确定,禁止硬编码。 - 当原始列与目标图表格式不匹配时,必须使用
--transform-code进行列重命名/重塑/聚合。数据解析层仅处理“哪一行是表头”;其他所有清洗都应通过转换代码完成。 - 必须报告不支持的场景:如果 CLI 确实不支持某种数据结构(例如嵌套对象超过 1 层),则必须先向用户报告问题并提供建议,然后才能回退到手动编写脚本。禁止静默绕过。
- 不得在生成的代码中硬编码绝对路径;应在运行时解析路径。
- 不得跳过确认步骤,除非用户明确表示“自动生成” / “无需确认”。
- 必须使图表语言与数据语言保持一致:所有图表文本(标题、系列名称、提示框、按钮、页脚、HTML 的
lang)均采用数据的语言(自动检测:CJK 比例 > 5% 时为中文,否则为英文)。仅当用户明确要求特定语言时才传入--lang zh|en。禁止在同一图表中混用语言。
---
能力边界
支持: CSV(.csv/.tsv/.txt)、Excel(.xlsx/.xls)、JSON(.json);16 种图表类型(见下文);最多可自动合并约 10 个文件;单个文件 ≤ 100 MB(建议 ≤ 50 MB);自动检测 UTF-8/GBK/GB2312。
不支持: 数据库(先导出为 CSV)、实时/流式数据、地理地图、超过 100 MB 的文件、嵌套层级超过 1 层的 JSON、非表格数据(图像/音频/视频)。自动合并要求列重叠率 ≥ 50%。
网络要求: 生成的 HTML 通过 CDN(jsdelivr/unpkg)加载 ECharts。渲染图表需要互联网连接。Agent 运行时假定网络已连通。
---
安全性
由 LLM 生成的转换代码在三层安全防护下执行(无需用户确认):(1)关键字黑名单(阻止 exec、eval、open、import、os.system 等),(2)AST 白名单(仅允许赋值、调用、循环和推导式),(3)沙箱内置函数(仅允许 len、range、sorted 等安全函数;已移除 open/exec/eval/__import__)。被阻止的代码会引发 CodeValidationError,其中 suggestion 字段会说明解决方法。
---
执行工作流
- 获取数据 — 用户上传文件或提供路径。
- 解析数据 — 对所有文件调用
data_parser.py;若有多个文件,则评估合并可行性。 - 确认并推荐 — 显示摘要表;根据数据语义推荐合并、分别处理或连接策略以及图表类型;等待用户确认。
- 转换(如有需要) — 如果原始数据与目标图表的输入格式不匹配,则由 LLM 生成 pandas 转换代码 → 安全检查(黑名单 + AST)→ 在沙箱中执行 → 标准化的 DataFrame。失败时:最多重试 2 次,随后回退到原始数据 + 自动检测。
- 生成图表 — 调用
chart_generator.py→ ECharts HTML。合并数据 → 跨组比较;分开处理的数据 → 每个文件生成独立图表。 - 呈现结果 — 通过
html_path提供交互式图表。
关键原则: 多文件优先;执行前确认;LLM 根据数据语义选择图表类型(禁止硬编码映射);禁止硬编码绝对路径(在运行时解析);立即呈现结果;必要时通过转换代码适配数据;默认保障安全。
---
配置
output_dir: ./smart_charts_output # optional; never hard-code absolute paths
---
错误码
所有错误均通过 SmartChartsError.to_dict() 以结构化字典形式返回: {"error": <message>, "code": <int>, "code_name": <str>, "details": {...}}。 details 字段始终包含 suggestion,用于告知 agent 如何恢复。
| 代码 | 名称 | 含义 | |------|------|---------| | 1001 | FILE_NOT_FOUND | 文件路径不存在 | | 1002 | FILE_PERMISSION_DENIED | 路径不是常规文件 | | 1003 | FILE_FORMAT_INVALID | 不支持的文件扩展名 | | 1004 | FILE_SIZE_EXCEEDED | 文件大小超过 100 MB 上限 | | 2001 | DATA_PARSE_ERROR | 解析失败(编码、结构等) | | 2003 | DATA_EMPTY | 文件或清洗后的数据为空 | | 2004 | DATA_TYPE_MISMATCH | 数据类型不匹配 | | 3001 | TRANSFORM_EXEC_ERROR | 转换代码执行失败(黑名单/AST/超时) | | 3002 | TRANSFORM_NO_RESULT | 转换代码未生成 result 变量 | | 3003 | TRANSFORM_INVALID_RESULT | result 不是 DataFrame | | 3004 | TRANSFORM_EMPTY_RESULT | result DataFrame 为空 | | 4001 | CHART_GENERATION_ERROR | 图表生成失败 | | 4002 | CHART_TYPE_UNSUPPORTED | 不支持的图表类型 | | 4003 | CHART_CONFIG_ERROR | DataFrame 中不存在轴字段 | | 9999 | UNKNOWN_ERROR | 未分类错误 |
---
数据解析
{skill_base}= 此 skill 的根目录(包含SKILL.md)。
单个文件:
python {skill_base}/core/data_parser.py <file_path> [--summary] [--skiprows N] [--header-row N] [--sheet <name|index>]
多个文件(可选自动合并):
python {skill_base}/core/data_parser.py <file1> <file2> ... [--merge] [--summary]
表头 / 跳过行标志(仅限单文件):
--skiprows N— 跳过前 N 行,然后将下一行作为表头读取。当文件在任意表头之前存在前置无效行(注释、空行)时使用。--header-row N— 将以 0 为起始索引的第 N 行作为表头;丢弃 N 上方的行。当文件具有多行表头(合并单元格、子表头),且要将其中某一行用作列名时使用。--sheet <name|index>— 按名称或以 0 为起始索引的位置选择 Excel 工作表(默认值:0)。- N 的值通过检查实际数据确定(例如,首次运行
data_parser.py时不使用标志)。禁止假定所有文件都使用固定的 N 值。
合并行为:
- 列完全相同 → 纵向拼接。⚠️ 会注入一个
source_file列,用于指示每行的来源文件。下游转换代码必须考虑这个额外列。 - 列重合度 ≥ 50% → 基于共享键横向连接。
- 无共同结构 → 报错(建议分别分析)。
Programmatic API: DataParser.parse_files(paths, merge=...) -> {'merged': bool, 'data': ..., 'merge_type': Optional[str]}.
parse_file(path, skiprows=None, header_row=None, sheet_name=0)— 单个文件,可选择清理表头。parse_files(paths, merge=...)— 多个文件;merge=False返回List[{'file', 'data'}],merge=True返回合并后的DataFrame。
---
图表生成
python {skill_base}/core/chart_generator.py \
<file_path> <chart_type> \
--title "Chart Title" \
--x-axis "date" \
--y-axis "revenue profit" \
--transform-code "<pandas code>" \
--skiprows N --header-row N --sheet <name|index> \
--lang zh|en \
--output-dir "./output"
参数: file_path(必填),chart_type(必填,见下表),--title(默认值遵循 --lang / 数据语言),--x-axis(省略时自动检测),--y-axis(以空格分隔;默认使用前 5 个数值列),--transform-code(由 LLM 生成的 pandas 代码,经验证并在渲染前执行),--skiprows / --header-row / --sheet(与 data_parser.py 语义相同;透传至解析步骤),--lang zh|en(可选;覆盖自动检测结果),--output-dir(默认值:./smart_charts_output)。
语言一致性(必须遵循):所有图表文本——标题、系列名称、工具提示标签、操作按钮(“保存图片” / “全屏”)、滚动提示、页脚以及 HTML 的 lang 属性——都必须遵循数据的语言。CLI 根据列名和字符串单元格内容自动检测中文或英文(CJK 字符占比 > 5% → zh,否则为 en)。仅当用户明确要求特定语言时,才传入 --lang zh 或 --lang en;否则,让自动检测结果与数据保持一致。禁止在同一图表中混用多种语言。
溢出处理:当图表的数据点数量超过缩放阈值(默认值为 15)时,生成的 HTML 会自动启用 ECharts dataZoom(滑块 + 内部拖动),并在图表容器上启用水平滚动条,因此用户可以拖动滑块、水平滚动或单击全屏按钮来查看所有数据点。agent 无需执行任何操作。
图表类型
LLM 必须检查原始数据是否符合所需格式;如果不符合,则生成转换代码。
| ID | 最适用场景 | 触发关键词 | y_axis 基数 | 所需的 DataFrame 格式 | 示例列 | |----|----------|------------------|:-------------------:|--------------------------|-----------------| | line | 时间序列趋势 | trend, change, over time, 趋势, 变化, 走势 | 1~N | 1 个类别/时间列 + 1 至 N 个数值列 | month, productA, productB | | bar | 类别比较 | compare, rank, difference, 对比, 比较, 排名, 差异 | 1~N | 1 个类别列 + 1 至 N 个数值列 | city, revenue, profit | | area | 累计变化 | cumulative, change, 累计, 变化 | 1~N | 1 个类别/时间列 + 1 至 N 个数值列 | date, uv, pv | | pie | Composition/share | share, composition, proportion, 占比, 构成, 比例 | 1 | 1 个名称列 + 1 个值列 | category, share | | scatter | 相关性 | correlation, relationship, scatter, 相关, 关系, 散点 | 1 | 2 个数值列,或 1 个类别列 + 1 个数值列 | height, weight | | radar | 多维比较 | multi-dimension, comprehensive, radar, 多维, 综合, 雷达 | N | 1 个指标列 + N 个数值列 | metric, productA, productB | | heatmap | Density/cross-tab | density, cross, matrix, heatmap, 密度, 交叉, 矩阵, 热力 | N | 2 个类别列 + 1 个数值列 | row, col, value | | treemap | 层级占比 | hierarchy, proportion, nested, 层级, 占比, 嵌套 | 1 | 1 个名称列 + 1 个值列 | category, sales | | graph | 实体关系 | relationship, network, topology, 关系, 网络, 拓扑 | 特殊 | 源列 + 目标列(+ 值列) | from, to, weight | | boxplot | Distribution/outliers | distribution, outlier, quartile, 分布, 离群, 四分位 | N | N 个数值列 | math, chinese, english | | waterfall | 增量变化 | increment, change, waterfall, 增量, 变化, 瀑布 | 1 | 1 个类别列 + 1 个数值列(增量) | month, profit_delta | | gauge | KPI 进度 | progress, kpi, achievement, 进度, KPI, 达成 | 1 | 1 个数值列(使用均值) | completion_rate | | sankey | 流量转移 | flow, transfer, sankey, 流向, 流量, 转移 | 特殊 | 源 + 目标 + 值 | origin, destination, amount | | funnel | 转化率 | conversion, funnel, churn, 转化, 漏斗, 流失 | 1 | 1 个名称列 + 1 个值列 | stage, count | | sunburst | 多层级构成 | hierarchy, proportion, nested, 层级, 占比, 嵌套 | 1 | 1 个名称列 + 1 个值列 | category, value | | wordcloud | Frequency/keywords | word frequency, keywords, text, 词频, 关键词, 词云 | 1 | 1 个名称列 + 1 个值列 | word, frequency |
y_axis 基数说明:1 = 仅使用第一列(其余列将被静默忽略);1~N = 每列都会成为一个系列;N = 预期有多列;special = 自动检测源列、目标列和值列。
编程 API
from core.chart_generator import ChartGenerator
# Single chart — returns {'chart': {'success', 'html_path'/'error', ...}}
# lang=None auto-detects from data; pass 'zh'/'en' to override (only when user asks).
result = ChartGenerator(output_dir="./output").generate_chart(
df=df, chart_type="bar", title="Regional Revenue",
x_axis="region", y_axis=["revenue"], lang=None,
)
# result = {"chart": {"success": True, "html_path": "...", "chart_type": "bar", "title": "..."}}
# Batch — returns {'charts': [...]} where each item has the same shape as 'chart' above
result = ChartGenerator(output_dir="./output").generate_multi_charts(
df=df,
chart_configs=[
{"type": "bar", "title": "Regional Revenue", "x_axis": "region", "y_axis": ["revenue"]},
{"type": "line", "title": "Monthly Trend", "x_axis": "month", "y_axis": ["revenue", "profit"]},
],
lang=None,
)
失败时,success 为 False,error 包含结构化字典(其结构与 SmartChartsError.to_dict() 相同)。不会抛出异常——agent 会检查 success 以决定后续步骤。
---
转换代码生成
当原始数据与目标图表的输入格式不匹配时,使用以下提示词模板:
Known information:
- Raw data columns: {columns_with_dtypes}
- Data sample (first 5 rows): {sample}
- Target chart type: {chart_type}
- Required format for this chart: {chart_input_spec}
Generate a pandas code snippet that transforms df into a result DataFrame matching the chart's input format.
Rules:
1. Only use variables: df, pd, np
2. Must produce a variable named result (pd.DataFrame)
3. Do not modify df in-place; use df.copy() or chain operations
4. Keep code concise; prefer pandas built-in methods (pivot_table, melt, groupby, rename, etc.)
5. If raw data already matches the required format, output an empty string
6. Do NOT use: import, open, exec, eval, os, sys, subprocess, file I/O, network calls
Output format:
{转换操作的单行说明}
{transform_code}
常见转换模式:
- 长格式→多系列:
result = df.pivot_table(index='<time>', columns='<category>', values='<value>', aggfunc='sum').reset_index() - 长格式→饼图(筛选):
result = df[df['metric']=='revenue'][['category','value']].rename(columns={'category':'name'}) - 宽格式→长格式:
result = df.melt(id_vars=['date'], var_name='name', value_name='value') - 聚合→柱状图:
result = df.groupby('<category>')['<value>'].sum().reset_index() - 重命名列:
result = df.rename(columns={'来源':'source','去向':'target','金额':'value'}) - 计算差值→瀑布图:
tmp = df.copy(); tmp['delta'] = tmp['profit'].diff().fillna(tmp['profit'].iloc[0]); result = tmp[['month','delta']] - 重命名混乱或表意不明的列名(当使用
--header-row后留下类似10分、unnamed_3的列名时):result = df.rename(columns={'unnamed_0':'student_id','unnamed_1':'name','10分':'homework_score','30分':'exam_score'}) - 向前填充合并单元格(当一个分组中只有第一行有值时):
result = df.ffill() - 将子表头合并为单个列名(当
--header-row N将一行扁平化但丢失上下文时):result = df.rename(columns={c: f'{c}_score' for c in df.columns if c not in ['student_id','name']})
有关 CLI 参考文档和安装说明,请参阅 [REFERENCE.md](./REFERENCE.md)。