SuperAPI AI Directory

smart-charts

Data & analytics

View repository

Updated 10-15 22:13

About this skill

Smart Charts

Convert data files (CSV/Excel/JSON) into interactive ECharts HTML. Supports 32 chart types, 3 theme presets (default/classic/dark), multi-file merging, and LLM data transformation code (sandboxed execution). For full CLI parameters, flag semantics, multi-chart/multi-file details, capability boundaries, error code table, complete delivery interpretation specification, and FAQ, see REFERENCE.md。


Activation Triggers

TRIGGER

  • The user provides / points to a tabular data file and requests a chart, visualization, or chart generation
  • The user mentions "draw a chart", "make a chart", "generate a chart", "data visualization", "chart", or "visualization"

DO-NOT-TRIGGER(If any one matches, recommend another skill instead; do not force this one)

  • What is wanted is a report/weekly report/retrospective/briefing document with a conclusion narrative → use a report-type skill (this skill produces charts + traceable facts + candidate insight signals; it does not write narrative documents)
  • There is no data file (pure text description, verbal idea)
  • A backend dashboard is needed, or non-tabular content such as images/audio/video
  • Map rendering: built-in China provincial-level (china, including Hong Kong, Macao, and Taiwan) and world country-level (world, exact match on English country names); finer regions such as city-level/district-level require the user to provide their own GeoJSON file (--geo-path); this skill does not download it on the user's behalf

Workflow (profile first, then select the type)

chart_type is a required CLI parameter, while the basis for type selection (cardinality, distribution, granularity, entity vs. detail) originally only exists in the plot_stats after the type is selected—deciding first and gathering evidence later would turn type selection into guesswork. So there are two paths depending on whether the user has clearly stated their intent:

A. The user's intent is clear(e.g. "compare sales by region", "look at monthly trends", "show the composition of proportions") → Directly pick the chart type + axes + aggregation basis according to the type selection table, and produce the chart.Do notrun profile just to follow the process. If the data cannot support that intent (a trend is wanted but there is no time column, or proportions are wanted but all rows are detail rows), state this honestly and give feasible alternatives (for example, replace "trend" with comparison by category); do not force a chart.

B. The user only provided data and did not say what they want to see(the most frequent case) → two commands:

  1. python {skill_base}/scripts/cli.py <file> --profile(one command; read-only, no rendering, no disk writes), obtaining: each column's dtype/cardinality/missing rate/sample/statistics, entity granularity (grain), candidate insight signals (signals), suspected non-data rows (row_quality)。
  2. You decidewhich 1–3 charts to produce: read columns 's dtype/sample/cardinality to determine which column is an ID, which is a dimension, and which is a measure, combine this with signals to pick out findings worth discussing, and based on that define the chart type + axes + aggregation basis, then produce the chart directly (--transform-code aggregation/pivot + --title conclusion + --annotation interpretation). When delivering, explain "why these charts".

signals The ones most worth prioritizing for charting in divergent_category(a certain category changing against the trend) and trend: they are the easiest to be masked by the overall average, and are exactly the findings that "the user did not think of themselves but understands at a glance".

MUST NOT pushes the responsibility for type selection back onto the user ("Do you want to see trends, comparisons, or proportions?")—which column is an ID, which is a dimension, and what chart to produce is work the agent should judge on its own from the objective facts of the profile, not a menu for the user to choose from.


Contract (8 items, MUST)

  1. Column names are normalized after parsing: converted to lowercase, special characters →_(e.g. 总学时→总_学时), Chinese characters are preserved;--x-axis/--y-axis/transform must reference normalized column names
  2. transform sandbox: available variables are only df/pd/np(np.select/np.where available), supports multiple statements (; or newline separated), must produce a DataFrame named result ; import/open/try/class definitions are forbidden (blacklist + AST whitelist enforced validation, violations return an error with suggestion )
  3. pie/bar/treemap/funnel etc. read data as "1 categorical column + 1 numeric column" (the name/value in the docs refers to therole, not a requirement that the column be named this—keeping the original column names is more readable, and you don't need to write an extra rename); for categorical frequency charts, first aggregate into these two columns with transform, then use --x-axis <分类列> --y-axis <数值列> to specify
  4. stdout on success:success/html_path/chart_type/title/source_rows/plotted_rows/unique_entities/data_rows/data_preview(first 10 rows of plotting data, for calibration checks)/annotation_source/plot_stats(full statistical summary of plotting data, for writing interpretation; covers all 32 categories);assumptions(automatic choices made by the CLI) and advisories(caliber reminders) appear only when non-empty—absence of this field means "no automatic choices/no reminders this time", not missing
  5. For calibration, read data_preview + data_rowsdirectly from stdout, do not open the HTML to search for data——data_preview is the first 10 rows of the x/y columns in the rendered frame(same source and same frame as the render, but excluding auxiliary columns not involved in plotting);data_rows is the actual number of plotted rows. When you need to check auxiliary columns produced by transform, use print(df.columns)/print(df.head())inside transform (goes to stderr, does not pollute stdout) or separately --dry-run
  6. For aggregation caliber reconciliation, read source_rows / plotted_rows / unique_entities:source_rows=number of raw rows before transform,plotted_rows=actual number of plotted rows,unique_entities=number of deduplicated entities (if there is --label-col use that column, otherwise use the x column).Note:plotted_rows == source_rows only indicates "no aggregation", while row-based charts like scatter/box plots inherently should not aggregate—the criterion must be used together with chart-type semantics, don't treat it as a generic error signal. The true mechanical criterion is the "suspected non-aggregation" in advisories : the number of distinct x-axis values < the number of rows and the chart type requires "one x corresponds to one y"; in this case the max/min/mean from plot_stats are row-level rather than for that dimension as a whole
  7. When intent is unclear, you MUST first run --profile(python {skill_base}/scripts/cli.py <file> --profile(chart type not needed). It returns each column's dtype/cardinality/missing rate/sample/statistics,grain、signals、row_quality—these areobjective facts, and are the basis for selection; which column is an ID, which is a dimension, and what chart to produce are for you to judge from these facts, profile does not ghostwrite (it does not produce role classification or chart type suggestions)
  8. Before delivery you MUST read assumptions + advisories:assumptions are the automatic choices the CLI made for you (automatic x/y value selection, etc.),advisories are reminders that "generation succeeded but you may have wanted another caliber" (suspected non-aggregation, too many pie chart categories, partial downgrade of numeric columns…). Both must be echoed in the delivery message—assumptions the user disagrees with can be regenerated with one sentence

Golden examples (templates, copy and change column names)

1 Categorical frequency → pie/bar(most frequent scenario):

python {skill_base}/scripts/cli.py data.xlsx bar --title "标题" --output-dir ./out \
  --x-axis name --y-axis value \
  --transform-code "result = df['类别列'].fillna('未标注').value_counts().rename_axis('name').reset_index(name='value')"

(gauge/liquid must add --target <目标值>when you want a "completion rate"; without it plot_stats.achievement is null. For environment self-check use python {skill_base}/scripts/cli.py --doctor。)

2 Multiple charts batch + escaping pitfalls(≥2 images MUST be batched; when transform contains Chinese/quotes, do not pass it directly in the shell --charts, write it into a JSON file and use --charts-file):

python {skill_base}/scripts/cli.py data.xlsx --sheet "Sheet1" \
  --charts-file charts.json --output-dir ./out

charts.json Each item:type(required) + title/x_axis/y_axis(string or array) /transform_code(single-image level) /label_col/color_by/annotation. The configuration is written by the agent itself—based on the objective facts of --profile (dtype/cardinality/samples/signals), decide what chart to produce and what axes to configure.

3-group aggregation → bar:

python {skill_base}/scripts/cli.py data.xlsx bar --title "标题" --output-dir ./out \
  --x-axis 分组列 --y-axis 数值列 \
  --transform-code "result = df.groupby('分组列')['数值列'].sum().reset_index()"

(reset_index() the column names afterward are still "group column/value column", and the axes can reference them directly; changing to rename_axis('name')+reset_index(name='value') is also fine, but the axes must be changed accordingly to name/value—the two must be consistent.)

Scope pitfall: Before aggregating, think clearly about "by data row vs by deduplicated entity"—when counting entity attributes (such as the hours structure of each course), first drop_duplicates; after generation, check against data_preview (if the sum of each row's value equals the original row count rather than the entity count, you forgot to deduplicate).


Hard Constraints (MUST follow)

  1. MUST use the CLI workflow(data_parser.py → cli.py), do not write your own script as a substitute.
  2. Dirty headers MUST use CLI flags(--skiprows N / --header-row N / --drop-rows 0,1 / --sheet, see REFERENCE.md for semantics), N is determined by the actual data (first run once without flags to see the raw layout), and must not be fixed arbitrarily.Note:--skiprows N and --header-row N are the same behavior (both are "use row N as the header, discard the row above it"), choose either one; do not think of N as two different decisions; it is recommended to standardize on --header-row。
    • two-row header + score row/subheader row(--header-row N can only choose "which row becomes the column names", and cannot both preserve the main header and merge the subheaders) use thethree-step approach: ① run --profile, read row_quality.suspicious_non_data_rows(or advisories) to locate the remaining non-data rows; ② use --drop-rows 0,1(position is given by profile, do not guess) or --transform-code "result = df.drop(index=[0,1])" to drop rows; ③ rerun --profile to confirm it is clean(rows becomes fewer,row_quality zero hits) before generating the chart.
  3. Column renaming/reshaping/aggregation MUST use --transform-code. The parsing layer only solves "which row is the header"; all other cleaning belongs to transform.
  4. To change the display names of axes/series, MUST use --x-name/--y-name/--series-name(s), do not rely on changing column names. Changing column names will change both axis names and series names (both are bound to column names), and for charts that originally hardcode default text (heatmap "Heatmap"/gauge "Gauge"/sankey "Sankey chart", etc.), changing column names also cannot change the series names. Display names are the only clean entry point:--x-name changes the x-axis,--y-name changes the y-axis,--series-name(single) or --series-names(multiple, in visible series order) changes series names.
  5. MUST report unsupported scenarios: For things the CLI truly does not support (such as nested JSON more than 1 level deep), first explain to the user and give suggestions; do not silently bypass.
  6. MUST NOT Hardcode absolute paths in the generated code; resolve paths at runtime.
  7. Do not proactively pass --lang; the CLI automatically follows the data language. Pass it only when the user explicitly requests a certain language.
  8. MUST deliver with interpretation: When delivering charts, you must attach a textual interpretation written by the LLM, and inject it into the HTML via --annotation (see "Interpretation Delivery Specification" below); do not deliver only a bare chart. In the successful output, the annotation_source The interpretation source will be indicated:'user'= what you passed in,'default'= filled in by the code using a template (in this case the output will also carry an advisory)——Before delivery, confirm it is user'。
  9. MUST explicitly pass --output-dir, pointing to a user-visible directory (such as ./out, the Desktop directory, or an explicit subdirectory under the current working directory). The default value ./smart_charts_output will be written to disk under the current working directory, making the artifact hard for the user to find.
  10. {skill_base} = skill root directory(containing SKILL.md). If the current host does not replace this placeholder, locate it once first and then change it to an absolute path for use:SKILL_DIR=$(dirname "$(find "$HOME" -maxdepth 6 -type f -path "*smart-charts/scripts/cli.py" | head -1)")/.., then use $SKILL_DIR/scripts/cli.py. Do not assemble the path from memory.

Default policy (no confirmation from the user)

Generating a chart is a cheap and reversible action (regeneration takes 1-10s, with zero external side effects). The chart type, multi-file merge strategy, and value semantics are all decided internally by the agent, without interrupting the user——basisis: the user's explicit intent (branch A) or --profile the objective facts provided (dtype/cardinality/sample/signals, branch B).

Post-hoc review instead of pre-hoc confirmation: In the delivery message, explicitly list the key assumptions made this time (e.g., "chose line, because month is a time series column", "multiple files were concatenated vertically because their column names are exactly the same, and a source_file column has been injected", "sales are measured by amount"). If the user disagrees with any assumption, they can ask in one sentence to change the semantics/change the type/change the merge method and regenerate.

The only point where user intervention is required: see the "still failing" branch of Exit Criteria.


Exit Criteria (mechanically determinable)

  • ✅ Success: stdout is {"chart": {"success": true, ...}}(in multi-chart mode, {"charts": [...], "summary": ...}), and html_path points to a file that exists and is non-empty → use the same stdout's data_preview/data_rows/plotted_rows/unique_entities to check the aggregation semantics (by row vs by deduplicated entity), and after confirming it is correct, deliver it with a textual interpretation.
  • ℹ️ --dry-run does not count as delivery:html_path is null and is not written to disk; it is only used for trial-and-error column names or trial aggregation scripts.Writing the interpretation does not require running it first——the formal output already carries plot_stats。
  • ❌ Failure: success: false or exit code 1 → read error.details.suggestion, fix it and retry;at most 2 retries for the same step。
  • 🛑 Still failing (the only point where user intervention is required): report code_name、suggestionand the fixes already attempted truthfully to the user, give suggestions, and wait for the user's decision.Must notsilently fall back to a self-written script (violates constraints 1/4).

Chart Types selection table

Before selecting, check the Required Format; if it does not match, use transform code to adapt it.

Scale hint: for multi-column charts such as heatmap / boxplot / radar, if the scales of the columns differ greatly (e.g., a mix of a maximum score of 10 and a maximum score of 100), you need to use transform code to normalize them first; otherwise the small-scale columns will be dominated by the large-scale columns.

ID Best For Trigger Keywords y_axis Required DataFrame Format
line Time-series trends trend, change, over time, 趋势, 变化, 走势 1~N 1 category/time + 1~N numeric
bar Category comparison compare, rank, difference, 对比, 比较, 排名, 差异 1~N 1 category + 1~N numeric
area Cumulative change cumulative, change, 累计, 变化 1~N 1 category/time + 1~N numeric
pie Composition/share share, composition, proportion, 占比, 构成, 比例 1 1 name + 1 value
scatter Correlation correlation, relationship, scatter, 相关, 关系, 散点 1 2 numeric, or 1 category + 1 numeric
radar Multi-dimension comparison multi-dimension, comprehensive, radar, 多维, 综合, 雷达 N 1 indicator + N numeric
heatmap Density/cross-tab density, cross, matrix, heatmap, density, cross, matrix, heatmap 1~N 1 category + 1~N numeric (each column is a "column" matrix; for cross tables, first use pivot_table to spread into columns)
treemap Hierarchical proportion hierarchy, proportion, nested, hierarchy, proportion, nested 1 1 name + 1 value
graph Entity relationships relationship, network, topology, relationship, network, topology special source + target (+ value)
boxplot Distribution/outliers distribution, outlier, quartile, distribution, outlier, quartile N N numeric
waterfall Incremental change increment, change, waterfall, increment, change, waterfall 1 1 category + 1 numeric (increments)
gauge KPI progress progress, kpi, achievement, progress, KPI, achievement 1 1 numeric (mean used)
sankey Flow transfer flow, transfer, sankey, flow, flow, transfer special source + target + value
funnel Conversion rate conversion, funnel, churn, conversion, funnel, churn 1 1 name + 1 value
sunburst Single-level proportion proportion, sunburst, proportion, ratio 1 1 name + 1 value
wordcloud Frequency/keywords word frequency, keywords, text, word frequency, keywords, word cloud 1 1 name + 1 value
histogram Distribution shape distribution, histogram, distribution, histogram 1 1 numeric column(--x-axis or --y-axis both work; numeric --x-axis preferred)
stacked_bar Composition over categories composition, stacked, stacked, composition 1~N 1 category + 1~N numeric
bubble 3-variable correlation bubble, 3-variable, bubble, three-variable 2 2 numeric + 1 size
pareto 80/20 analysis pareto, 80/20, Pareto, 80/20 1 1 category + 1 numeric
combo Dual-axis comparison dual-axis, combo, dual-axis, combo 1~N 1 category + 1 bar + 1~N line
venn Set overlap (2~3 sets) overlap, intersection, venn, intersection, overlap, Venn 1 1 name + 1 value; name intersection rows as A∩B(separators: ∩ & + × and)
mindmap Hierarchical ideas mind map, outline, mind map, mind map, outline 1 1 parent + 1 child (categorical columns)
orgchart Organization structure org chart, hierarchy, org chart, reporting relationship, hierarchy 1 1 parent + 1 child (categorical columns)
liquid Percentage/progress liquid, progress, percent, liquid, progress, percentage, completion rate 1 1 numeric (mean used)
spreadsheet Raw table / pivot display table, detail, spreadsheet, table, detail, list N any columns (x/y optionally filter displayed columns; use transform for aggregation)
map Geo choropleth map, region, province, geo, map, region, province, region 1 1 region name + 1 numeric (default China provinces;--geo/--geo-path to switch to any region)
lines Route/migration on map route, migration, flow map, route, migration, flow map special source + target (+ value) (default province-level names;--geo/--geo-path to switch to any region)
effect_scatter Animated scatter highlights ripple, highlight, scatter animation, ripple scatter, animated scatter 1 2 numeric, or 1 category + 1 numeric
calendar Daily heatmap (GitHub-style) calendar heatmap, daily activity, calendar heatmap, daily activity 1 1 date + 1 numeric
pictorial_bar Symbolic/iconic bars pictorial, icon bar, symbol bar, pictorial bar, icon bar 1~N 1 category + 1~N numeric
theme_river Theme evolution over time theme river, event evolution, theme river, event evolution 1~N 1 date + 1~N numeric

gauge/liquid To output "achievement rate" you must pass --target <目标值>:achievement = mean ÷ target. If not passed, this field is null(an automatically inferred range is not a business target, so the ratio has no business meaning), and the output will carry an advisory.

y_axis cardinality key: 1 = only first column used; 1~N = each column becomes a series; N = multiple columns expected; 2 = exactly 2 numeric columns required; special = relationship charts (graph/sankey/lines) are determined by --x-axis(source)+ --y-axis(target, optional value)explicitly specify; no column-name glossary auto-detection is performed. For scatter/bubble/boxplot, the identity column is explicitly specified by --label-col ; if not passed, identity is not displayed (tooltip only shows coordinate values).


Transform Code Contract

Contract (enforced by the sandbox; violations receive an error with suggestion ; just fix it as prompted):

  • The only available variables are df, pd, np; you must produce a result named pd.DataFrame
  • Do not modify in place df(use df.copy() or chained operations)
  • When the raw data already matches the target format, do not pass --transform-code

Common transform patterns:

  • Long→multi-series: result = df.pivot_table(index='<time>', columns='<category>', values='<value>', aggfunc='sum').reset_index()
  • Long→pie (filter): result = df[df['metric']=='revenue'][['category','value']].rename(columns={'category':'name'})
  • Wide→long: result = df.melt(id_vars=['date'], var_name='name', value_name='value')
  • Aggregate→bar: result = df.groupby('<category>')['<value>'].sum().reset_index()
  • Rename columns: result = df.rename(columns={'来源':'source','去向':'target','金额':'value'})
  • Compute delta→waterfall: tmp = df.copy(); tmp['delta'] = tmp['profit'].diff().fillna(tmp['profit'].iloc[0]); result = tmp[['month','delta']]
  • Rename messy/uninformative column names (after --header-row leaves columns like score_a, unnamed_3): result = df.rename(columns={'unnamed_0':'student_id','unnamed_1':'name','score_a':'homework_score','score_b':'exam_score'})
  • Forward-fill merged cells (when only the first row of a group is populated): result = df.ffill()
  • Drop non-data rows (the positions are given by --profile in row_quality.suspicious_non_data_rows , do not guess; this is equivalent to using --drop-rows to write to disk before plotting, but in transform it is more intuitive): result = df.drop(index=[0,1])
  • Combine sub-headers into a single column name (when --header-row N flattens one row but loses context): result = df.rename(columns={c: f'{c}_score' for c in df.columns if c not in ['student_id','name']})

A routine step for verifying cleaning results: the print(df.head()) / print(df.columns) in transform is redirected to stderr, without polluting the JSON contract of stdout—so you can directly print in transform to check the results after dropping rows/aggregation/renaming, and treat it as a routine means of verification after cleaning (you do not have to use it only when an error occurs).


Delivery Annotation

When delivering each chart, you must attach a passage oftext interpretation written by the LLM(not template text), then use --annotation to inject it into the HTML (the "Chart Description" block below the chart).

The interpretation should answer "what is the most noteworthy finding in this dataset", not restate what is on the chart. The skill is responsible for calculating facts; judging which finding is worth mentioning and stating it clearly is the agent's job—so the structure of the interpretation is "conclusion → evidence → boundary", not "what the chart is → what the maximum value is".

Citable fact sources (three places):

  • profile.signals — candidate insights before plotting (trend direction and percentage change, head concentration, counter-trend categories, outliers, strongly correlated pairs)
  • plot_stats / data_preview — this chart'sstatistics of the actually plotted data(the final standard is based on this)
  • assumptions / advisories — automatic choices and standard reminders made by the CLI for you

Standard process:

  1. When the intent is unclear, first --profile, pick out the findings to present from signals , and based on that decide the chart type and aggregation standard (see the B branch of "Workflow").
  2. Formally generate once with --annotation "解读文字" .The formal output itself already includes plot_stats and data_preview, so there is no need to first run --dry-run to get the numbers.
  3. Read that run's plot_stats to verify the numbers you want to present;after finishing the interpretation, look back: if you find that the standard/chart needs to be changed, then regenerate a second time—regeneration is a cheap and reversible action (1~10s).

--dry-run Use it only when "you want to see the numbers first and clearly do not want to write to disk" (such as trying out column names or testing aggregation scripts). Treating it as a required step before writing the interpretation will double the number of calls with no benefit at all.

Minimum structure of the interpretation (2~4 sentences, conclusion first):

  1. Finding: give the conclusion in one sentence. Write "South China sales halved starting in April, while the other three regions still grew in the same period", not "this chart shows monthly sales by region".
  2. Evidence: 1~2 specific values + corresponding labels + source (from plot_stats or profile.signals)。
  3. Boundary: explain the standard in one sentence (aggregation method, coverage, sample size), and echo the assumptions in assumptions/advisories —if the user disagrees with any assumption, it can be regenerated in one sentence.

Write the conclusion in the title, add the standard in the subtitle:--title Use a conclusion-style phrase (subject + verb + object + value, such as "revenue increased 23% year over year"); the numbers are taken directly from plot_stats, do not use noun phrases ("monthly revenue"); put context such as time range/filter conditions/data source in --subtitle, do not stuff it into the title.

Hard boundaries:

  • Every number in the interpretation must have its source traceable in plot_stats/data_preview/profile.signals — do not fabricate from impression;x_cardinality is the deduplicated count, expressed according to the semantics of column x (if x is "name", say "59 students" rather than "59 categories").
  • Only state facts that the evidence can support; "why/how" can be elaborated based on context, but must be labeled as speculation and must not be mixed in with facts.
  • advisories When "suspected unaggregated" appears, aggregate according to the prompt first before writing the interpretation— otherwise the extreme values/averages you cite are row-level values, not the overall values for that dimension, and the conclusion will be wrong (tested: when plotting a line chart of month × region details,plot_stats reported "June highest 156" but it was actually only one region, East China).

See REFERENCE.md for the complete specification and examples.