| name | flint-chart |
| description | Use when the user wants to visualize data — from 'which chart should I use?' to 'render this'. Helps pick the right chart from the analytical question (comparison / trend / distribution / relationship / proportion / flow / KPI), then authors a ChartAssemblyInput and renders via the flint-chart-mcp server (Vega-Lite / ECharts / Chart.js). Transform data before Flint; style tweaks after Flint. |
| lastReviewed | 2026-08-21T00:00:00.000Z |
flint-chart: pick, author, and render a chart
Load the version-matched Flint language first
Read flint://agent-skill or invoke author_flint_chart before authoring. That
source-owned resource carries the exact semantic types, chart templates,
properties, and backend boundaries for the installed MCP version. This skill
adds Alex-owned framing, creative treatment selection, configured-runtime
setup, and render-verify; it does not replace Flint's language reference.
For the conceptual grammar, compiler model, data-access boundary, backend
contract, rendered-demo evidence, and tagged Microsoft links, read
references/flint-language-reference.md.
It describes the published 0.5.1 contract that Illustrator pins. Use the MCP
resource, list_chart_types, and list_themes as the final authority for the
runtime actually installed in the host.
What you produce (and what you do NOT)
Your output is the spec: the chart_spec and semantic_types of a
ChartAssemblyInput. You reference data columns by name. The host
passes the resulting input to assembleVegaLite, assembleECharts,
or assembleChartjs to get a backend spec.
You write the input spec, not the output spec. And critically:
- DO emit
chart_spec (chart type, channel→field mapping, properties)
and semantic_types (field → semantic type).
- Carry the claim into a standalone chart. Set
chart_spec.title to the
Chart Brief's Big Idea, tightened only for display, and use subtitle for
what is measured, of whom, when, and in which units. A chart may travel
without its surrounding prose; its claim must travel with it.
- Reference columns by name. How
data itself gets bound depends on
the situation — a URL, a host-side variable, or embedded rows (see "How
data gets bound"). Embedding is fine for small tables; just don't
re-serialize a large dataset by hand, since that risks truncation and
silent value corruption and wastes tokens.
- Transform data before Flint. If the requested chart needs aggregation,
filtering, joins, pivots, derived columns, or long/wide reshaping beyond
Flint's built-in static-series fold, use a coding, notebook, SQL, or data tool
first. Then author the Flint spec against the transformed table.
- Style after Flint, only when needed. Author structure in Flint. For a
presentation tweak Flint does not express (a reference line, annotation, or
shaded band), use the Vega-Lite escape hatch — see "Post-Flint style
customization". Never feed edited Vega-Lite JSON back to
render_chart.
- Look at what you rendered. A chart with a collapsed scale, a merged color
scale, or an empty data binding renders as a valid image that tells the wrong
story —
validate_chart cannot catch that. Load the render-verify skill
after rendering, and always after a post-Flint Vega-Lite edit.
Verify Flint is available before rendering
Before you promise to render a chart, confirm the tools exist. If they don't,
install them (or ask the user to). Failing loudly early is cheaper than
authoring a spec no one can render.
For MCP rendering (default in this skill)
-
Check for the flint MCP server. Look in your available tool inventory
for render_chart, compile_chart, validate_chart, list_chart_types,
or create_chart_view. If any of them are present, the server is registered
and reachable — skip to step 4.
-
If the tools are missing, flint-chart-mcp is not registered. Ask the
user to add it, or add it yourself if you can edit their workspace config.
Put the file in the right place — this is the single most common failure.
| Host | Path | Top-level key |
|---|
| VS Code (workspace) | .vscode/mcp.json | servers |
| Claude Code / Claude Desktop | .mcp.json at workspace root | servers |
| Cursor | .cursor/mcp.json | servers |
| GitHub Copilot CLI | ~/.copilot/mcp-config.json | mcpServers |
The schema is identical across the first three, which is exactly why the
wrong path looks like it should work. VS Code never reads a workspace-root
.mcp.json — and it reports no error, because it isn't parsing a broken
file, it's reading no file at all. If a user says "I added the config and
nothing happened," check the path before anything else.
Copilot CLI differs twice over: different path and a different
top-level key (mcpServers, not servers). A servers block pasted there
fails just as silently. Prefer telling the user to run /mcp add inside a
CLI session and let it write the file. The path is overridable via
$COPILOT_HOME.
Always merge into any existing config rather than overwriting it —
clobbering the file destroys whatever other servers the user had.
// .vscode/mcp.json (VS Code) — merge with any existing "servers" map
{
"servers": {
"flint": {
"type": "stdio",
"command": "node",
"args": [".github/skills/setup-illustrator-runtime/scripts/runtime-launcher.mjs", "flint"],
},
},
}
For project code integration
Only needed if the user asked you to write code that imports flint-chart
directly (not to render via MCP).
-
Check the project's package.json for flint-chart in dependencies
or devDependencies. If present, skip to step 3.
-
If missing, use the project's approved dependency workflow. Preserve its
lockfile and configured registry; never probe another registry or select a
newer package version automatically. The Flint library version must match
the project's approved compatibility decision.
npm install --save-exact flint-chart@0.5.1
Add renderer peer dependencies only through the same project dependency
policy; do not use this skill to discover or upgrade their versions.
3. **Import in code:**
```ts
import {
assembleChartjs,
assembleECharts,
assembleVegaLite,
} from "flint-chart";
const spec = assembleVegaLite(input); // or assembleECharts / assembleChartjs
For Python
Not yet published to PyPI (as of 2026-07-24). Use the npm package or MCP
server for released workflows until the Python release lands.
When the user wants more than a spec
First decide which workflow the user is asking for:
- Spec authoring only: return a
ChartAssemblyInput or its
semantic_types + chart_spec pieces. Do not install packages or write
renderer code unless asked.
- MCP chart output: if Flint MCP tools are available, default to
create_chart_view only for a Vega-Lite chart when the host supports MCP
App UIs — it opens an interactive, live-rendered SVG view with a
customization panel. For ECharts or Chart.js, use render_chart for a static
artifact or compile_chart for backend-native JSON. render_chart emits SVG
or PNG for Vega-Lite/ECharts and PNG only for Chart.js. Use validate_chart
to check a spec without rendering and list_chart_types when you need the
supported chart catalog.
- Project integration, only when the user asks for code: add Flint to an
app, notebook, script, or agentic product, install/import the library, and
call an assembler in code. Keep the same
ChartAssemblyInput contract, then
let the host render the backend result.
For MCP clients, the server uses an exact cache-first package:
node ".github/skills/setup-illustrator-runtime/scripts/runtime-launcher.mjs" flint
For JavaScript or TypeScript projects, use the approved project dependency
workflow and preserve the lockfile:
npm install --save-exact flint-chart@0.5.1
Do not add or upgrade renderer dependencies unless the user explicitly requests
project integration and approves the project's dependency changes.
Then compile with the requested backend:
import {
assembleChartjs,
assembleECharts,
assembleVegaLite,
} from "flint-chart";
const vegaLiteSpec = assembleVegaLite(input);
const echartsOption = assembleECharts(input);
const chartjsConfig = assembleChartjs(input);
Python support is planned for a later release. Until the PyPI package is
published, use the npm package or MCP server for released workflows.
interface ChartAssemblyInput {
// Bound by the HOST or by you, depending on the situation (see below).
data: { values: any[] } | { url: string };
semantic_types?: Record<string, string>; // field → semantic type ← you write this
chart_spec: {
// ← you write this
chartType: string; // e.g. "Scatter Plot"
title?: string; // claim-bearing headline for a standalone Vega-Lite chart
subtitle?: string; // measure, population, period, and units
encodings: Record<string, EncodingValue>; // channel → { field, ... } (or array)
baseSize?: { width: number; height: number }; // target layout size, default 400×320
canvasSize?: { width: number; height: number }; // optional hard ceiling on stretch
chartProperties?: Record<string, any>; // per-chart tuning (optional)
};
options?: Record<string, any>; // global layout options (rarely needed)
field_display_names?: Record<string, string>; // readable axis / legend labels
theme_spec?: string | { extends?: string; [key: string]: any };
}
Carry the Big Idea into the chart
For a standalone chart, use the Chart Brief as the source of its presentation
text:
chart_spec.title states the finding in a concise sentence. It is not a
topic label or an axis title.
chart_spec.subtitle supplies the reading context: measure, population,
period, and units.
- Omit both only when a surrounding caption already supplies the same reading,
such as a sparkline in a table or a dashboard tile beneath a titled panel.
Flint 0.5.1 realizes the title and subtitle in the Vega-Lite backend. When
ECharts or Chart.js is the delivery target, keep the authored text with the
ChartAssemblyInput and make it visible in the surrounding artifact after
inspecting the compiled result. Do not move the claim into theme_spec:
themes govern presentation, while title and subtitle state what the reader is
meant to understand.
Calendar Heatmap in Flint 0.5.1
Use "Calendar Heatmap" when the question is about daily intensity, recurrence,
or gaps over a long time span. Bind x to a date field and color to the daily
measure; Flint sums multiple rows from the same day into one GitHub-style
week-by-week cell.
Calendar Heatmap is available on the vegalite and echarts backends in the
reviewed 0.5.1 runtime. For an interactive MCP App use Vega-Lite with
create_chart_view; use render_chart or compile_chart for ECharts. It is
not listed for Chart.js, so choose a standard Heatmap or another supported
alternative there. Always call list_chart_types when the runtime version is
unknown or has changed.
Choose a visual theme
Call list_themes when the audience or tone calls for a coherent house style.
Use a preset ID beside chart_spec, for example theme_spec: "economist", or
load flint-theme for a custom or inherited ThemeSpec. ThemeSpec controls
presentation and compiler behavior; it does not choose fields, aggregation,
filtering, sorting, titles, or values.
Flint 0.5.1 themes apply to Vega-Lite. ECharts and Chart.js ignore ThemeSpec,
so do not claim cross-backend parity. For a custom system, validate the bare
ThemeSpec in Theme Lab and inspect a representative chart corpus before using it
in a deliverable.
How data gets bound
Use the binding mode that matches the runtime. Do not mix them.
- Direct MCP rendering: embed rows. When calling
render_chart,
compile_chart, or validate_chart, the tool arguments are JSON. If the
data is small or already transformed by another tool, pass it as
data: { values: [...] }. Do not pass runtime variable names in
MCP tool calls — the MCP server cannot see your local variables.
- Direct MCP rendering: reference a local file.
The
flint-chart-mcp server can load data: { url: "..." } from local
.json, .csv, or .tsv files. By default any local file the agent can
name is readable (relative paths resolve against the working directory); a
hardened deployment may reject local file references entirely via
--disable-file-reference (or FLINT_MCP_DISABLE_FILE_REFERENCE), in which
case pass rows inline with data.values. Remote URL
fetching is disabled. If the data must be transformed first, use a
coding/data tool to write a small prepared file, then reference that file.
- Generated application or notebook code: bind runtime variables. If the
user asks you to add Flint to code, write normal data-loading code first and
pass a real runtime value, e.g.
data: { values: rows }, to
assembleVegaLite, assembleECharts, or assembleChartjs. This variable
pattern is for generated code, not for MCP tool calls.
For spec-only answers, return the semantic_types and chart_spec pieces and
state how the host should bind data. In the worked examples below, data is
shown as { values: [] } to signal "host binds this" — focus on chart_spec
and semantic_types.
Data transformation before charting
Flint is a chart compiler, not a data-wrangling layer. If the chart needs grouped
totals, time buckets, filters, joins, pivots, derived ratios, or a long-form
table, transform the data first with a host tool, then bind the prepared table
(see "How data gets bound"). Pick semantic types and channels for the transformed
columns, not for columns that no longer exist.
Sanity-read the values first — don't chart blind. Inspect the actual data
with your data tool (distinct values per category column, min/max per measure),
not just the column names, and watch for:
- Embedded totals. A category column may mix an aggregate level with its
parts (e.g.
all alongside cage-free/caged, or a Total region). Charting
the total with its parts double-counts and flattens the parts — keep one or the
other on a stacked/grouped/colored channel, not both.
- Units. Check whether a rate is a fraction (0–1) or already a percent
(0–100) before tagging it
Percentage; don't scale twice.
- One real entity. If your breakdown column has a single distinct value, the
per-group chart collapses to one mark — the intended breakdown is likely a
different column.
Post-Flint style customization
Stay at the Flint level for structure (data, chart type, channels, transforms,
sizing, properties) — Flint specs stay portable and regenerate safely. Drop to
backend JSON only after a valid Flint chart exists, and only for a narrow
presentation change Flint does not expose (exact axis/legend/mark styling,
titles, annotations, reference lines, layout polish). Never use it to change the
data, chart type, field mappings, or transforms — fix those upstream.
For a Vega-Lite-specific style tweak:
- Author and validate the Flint
ChartAssemblyInput.
- Render or inspect the Flint chart first, when possible.
- Call
compile_chart with backend: "vegalite".
- Make the smallest necessary style/presentation edit to the returned
Vega-Lite spec.
- Render the edited spec in the host environment with a Vega-Lite renderer.
This edited Vega-Lite spec is no longer a portable Flint spec. Do not send it to
render_chart; use render_chart only for Flint ChartAssemblyInput.
Verification is mandatory here. Once you leave the Flint level, the MCP
server's validation no longer protects you — an edited spec can render a
plausible-looking chart that is silently wrong. Load the render-verify skill:
open the result, read its console errors, and check it against the failure
catalog before declaring it done.
Publication config preset (books, reports, exec-facing)
When the render target is a book chapter, a print report, or an exec-facing
document, three settings routinely need to be pinned across every chart in the
artifact so the visuals read as one voice. This is a Vega-Lite config block
you can drop into the compiled spec (via Step 3 semantic types →
compile_chart → backend edit as described above).
Print variant of the constellation brand palette. The categorical range
below (blue-800 / amber-700 / green-700 / gray-500 / red-700) is the
print-quality variant of the Alex ACT chart.categorical palette. The
screen variant (#10b981 / #0ea5e9 / #f59e0b / #8b5cf6 / #ef4444)
lives in .github/config/brand-palette.json
in Alex_ACT_Core. Same 5-role semantic categorical, deeper contrast for
paper.
Pin this once at the top of the artifact's chart set; regenerated charts
inherit it without per-chart overrides.
{
"config": {
"background": "transparent",
"font": "Inter, system-ui, sans-serif",
"axis": {
"labelColor": "#6b7280",
"titleColor": "#1f2937",
"gridColor": "#e5e7eb",
"labelFontSize": 12,
"titleFontSize": 13
},
"title": {
"color": "#1f2937",
"fontSize": 18,
"fontWeight": 700,
"anchor": "middle"
},
"range": {
"category": ["#1e40af", "#b45309", "#15803d", "#6b7280", "#b91c1c"]
}
}
}
Semantic discipline in the categorical range — the palette carries meaning
across the artifact, so use each color only for its assigned role:
#1e40af (blue-800) — correct / principled / primary emphasis
#b45309 (amber-700) — Composition family / warning / footer takeaway
#15803d (green-700) — approval / correction
#6b7280 (gray-500) — muted / de-emphasised
#b91c1c (red-700) — rejection / critique / target line
Report typography scale for the HTML surrounding the chart (not the chart
itself):
| Role | Style |
|---|
| Report title | 18pt / 700 / #1f2937 |
| Section header | 14pt / 700 / #1f2937 |
| Body copy | 15-16px / #1f2937 (text) or #6b7280 (asides) |
| REJECTED / APPROVED badges | 12pt / 700 white on red-700 or green-700, rx="3" 80x20 pill |
Print-legibility floor for figures embedded in the artifact: 12px at a 640
viewBox is 5.93pt — the instructional minimum. For the full print-legibility
grammar (formula, data-print-role markers, text-fits ladder), the Tailwind
semantic palette, and the composition idioms (BEFORE/AFTER paired panels,
numbered critique callouts, family-band abstracts, 5-Visual Rule dashboards),
see the print-svg-style-guide skill.
For the engineering discipline that emits SVGs conforming to that guide
(hand-authored .mjs generators, data-sha256 audit hash, dataset-first with
contract tests, dataset inversion), see the
figure-generator skill.
Adapted from The Defensible Decision (Fabio Correa) via the
dd-book-illustrator skill in Alex_DDA.
Attribution
Chart-selection framework (§0 below) distilled from standard visualization
literature — Cole Nussbaumer Knaflic (Storytelling with Data), Andy Kirk
(Data Visualisation), Stephen Few (Show Me the Numbers, Information
Dashboard Design), Wexler / Shaffer / Cotgreave (Big Book of Dashboards). For
per-chart design tips and the full 48-chart catalog, see The Defensible
Decision chart gallery: https://www.thedefensibledecision.com/gallery/chart-gallery.html.
For live examples of every Flint chartType across all backends, organized by
semantic category (Bar & Column / Line & Area / Scatter & Points / Distributions
/ Circular & Radial / Tables & Multi-Dimensional / Maps), see the canonical
Flint gallery at https://microsoft.github.io/flint-chart/#/gallery/vegalite
(swap /vegalite for /echarts or /chartjs to view the other backends).
flint-chart itself is a Microsoft Research + IDEAS Lab (Renmin University)
project — canonical docs:
https://microsoft.github.io/flint-chart/#/documentation/getting-started
(getting started, API reference, architecture, chart-template extension),
https://microsoft.github.io/flint-chart/#/mcp (MCP server deployment + full
CLI), and https://microsoft.github.io/flint-chart/ (project home + live
editor).
Step 0 — pick the chart (when the user hasn't said which one)
Skip this step if the user already named the chart type (e.g. "scatter of
weight vs mpg"). Jump to Step 1 and author the spec. Otherwise, work down this
list before choosing a chartType.
0.1 One-sentence message — the Big Idea
Before choosing a chart, establish the message it should carry. Load the
chart-big-idea skill and run it now — look in
.github/skills/local/chart-big-idea/SKILL.md first (heir-installed), then
.github/skills/chart-big-idea/SKILL.md (baseline).
It does four things this step cannot do inline:
- Reads the surrounding context first — the prose next to the insertion
point, the ticket, the section heading, prior captions — so you do not ask the
user to re-articulate a claim they already wrote.
- Questions the intent — whether the chart should exist at all, and whether
the stated purpose is the real one. If the intended message and the data
disagree, that surfaces here rather than after rendering.
- Elicits the Big Idea with a three-question ladder, one question at a time,
when it is not written down anywhere.
- Asks the TRADITIONAL vs INNOVATIVE style stance, which changes the
chartType you pick in §0.2.
The output is a compact Chart Brief. Treat it as the constraint on everything
below: §0.2 selection, §0.4 coverage, and the spec you author in Steps 1-3.
If that skill is not available, do the compact version inline
(Knaflic — Storytelling with Data):
- What is your unique point of view?
- What is at stake?
- Express it as a complete sentence, not a phrase.
If you cannot write the sentence, ask the user for context before drawing.
"Show sales" is a phrase; "Q4 sales dropped 18% in APAC — that's where our
attention should go this quarter" is a sentence. The sentence shape drives the
chart choice.
0.2 Question → family → chart
Deeper reference: the chart-vocabulary skill carries the full 7-goal × ~30-chart catalog, CSAR evaluation loop, override decision table, and 5-visual rule. Reach for it when the 7 rows below aren't enough, when evaluating an AI-suggested chart, or when the artifact will be hand-authored (not rendered via Flint).
| Analytical question | Family | Primary chart | Alternates |
|---|
| Rank or compare categories? | Comparison | Bar Chart (2-15 items; horizontal orientation for long labels) | Grouped Bar Chart (2-4 series), Stacked Bar Chart (composition + total; use stackMode: normalize for 100% stacked), Slope Chart (before/after 2 periods), Bar Chart with row/column facet (many items, aka Small Multiples), Waterfall Chart (sequential adds/subtracts) |
| Change over continuous time? | Trend | Line Chart | Area Chart (volume emphasis), Bar Chart + Line Chart combo via multi-encoding y: ["bars", "line"] (dual metric with different scales), Sparkline (in-table trend) |
| How are values distributed? | Distribution | Histogram (one variable) | Boxplot (compare groups + stats), Violin Plot (compare + shape, Vega-Lite), Strip Plot (every point matters), Density Plot (smooth shape), ECDF Plot (cumulative) |
| Correlation between variables? | Relationship | Scatter Plot | Scatter Plot with size channel (3 vars, aka Bubble), Regression (with fit line), Connected Scatter Plot (trajectory over time), Parallel Coordinates (many vars, ECharts) |
| Part of a whole? | Proportion | Bar Chart (most accurate) or Stacked Bar Chart with stackMode: normalize | Pie Chart (only if one slice dominates ≥60% OR comparing to 50%), donut (use the center for a KPI) — Donut Chart on Vega-Lite, Doughnut Chart on Chart.js, Pie Chart + innerRadius on ECharts, Treemap (many/hierarchy, ECharts), (interactive hierarchy, ECharts), (sequential stages, ECharts) |
0.3 Anti-patterns — don't recommend
- Pie with >5 slices — humans can't compare angles; use
Bar Chart or Stacked Bar Chart with stackMode: normalize
- Pie without a dominant slice — if no category is ≥60% or the story isn't "X vs the rest", use a
Bar Chart
- Word cloud for real analysis — position and word length distort; use
Bar Chart of top-N terms (not in Flint; export to another tool)
- Dual-axis combo without justification — dual axes mislead by aligning unrelated scales; consider two separate charts
- Truncated Y-axis on bars — exaggerates differences; always start
Bar Chart at zero (Flint does this by default; don't override)
- Streamgraph when precise values matter — the flowing baseline sacrifices readability; use
Area Chart instead
- Gauge over Bullet —
Bullet Chart packs actual + target + qualitative bands in less space with more precision
- More than 5 series on a Line Chart — becomes a spaghetti chart; use
row/column facet (Small Multiples) or highlight one series and gray out the rest
0.4 Flint coverage — substitutes when the ideal chart isn't native
Some charts from wider visualization literature aren't in Flint's registry.
Recommend the substitute, not the missing chart:
| Ideal chart | Flint substitute | How |
|---|
| Waffle Chart (10×10 grid %) | Bar Chart or Stacked Bar Chart with stackMode: normalize | Labeled percentage bar communicates the same "N out of 100" |
| Chord Diagram (circular flows) | Sankey Diagram (ECharts backend) | Linear flow is easier to read anyway |
| Pareto Chart (bars + cumulative %) | Bar Chart + Line Chart via multi-encoding | Sort bars descending, overlay cumulative-% line |
| Beeswarm Plot (every point) | Strip Plot with stepWidth, pointSize, opacity | Jittered points instead of packed; same "every dot is real" story |
| Ridgeline Plot (many densities) | Violin Plot with row facet | Density curves stacked per group |
| Small Multiples | any chart with row or column encoding | Native facet support |
| Word Cloud / Sentiment / NPS Gauge / Likert / Mind Map | Not in Flint's scope | Export prepared data to Power BI / Tableau / dedicated tool |
| Hierarchy visualization | "Tree" (ECharts only) | Use detail for node labels, color for parent grouping, and size for values; validate the installed catalog before authoring |
| Control Chart / Run Chart / Pareto / Process Capability (SPC) | Not in Flint's scope | Use a dedicated SPC / Six Sigma tool; Flint isn't built for statistical process control |
| Decomposition Tree / Key Influencers / Smart Narrative (AI-Powered) | Not in Flint's scope | These are Power BI features; Flint is a chart compiler, not an analytics engine |
| Table / Matrix (precise value lookup) | Use a data table (not Flint) | Stephen Few's rule — tables for lookup, graphs for pattern |
This table is a catalog boundary: Flint does not offer these chart types.
ascii-chart carries a separate medium boundary
listing forms ASCII cannot carry at all, such as pie, donut, sunburst, violin,
and streamgraph, because angle and smooth curves need resolution a character
cell does not have. The two lists barely overlap and are not interchangeable: a
chart absent here may render fine in ASCII, and a chart Flint renders well may
be impossible in ASCII.
0.5 When to fetch a deep reference
Four reference layers, ordered by cost. Fetch the cheapest one that answers the question.
Chart selection — "which chart for which analytical question?"
Fetch The Defensible Decision — Complete Chart Gallery when:
- The user asks about a chart not in §0.2 or §0.4
- The user asks "what other charts could work here?"
- The user needs per-chart design tips (axis handling, color, labeling, accessibility)
- The compact table above is ambiguous for the case at hand
The gallery has 48 charts across 10 families with per-chart 💡 tips, distilled from Knaflic / Kirk / Few / Wexler.
Chart capability and language — runtime (fastest, always matches the pinned server version)
Two runtime paths that need no fetch and always reflect the actual server the user has installed:
list_chart_types MCP tool — call with { backend: 'vegalite' | 'echarts' | 'chartjs' } to get the current server's chart-type catalog and encoding channels. Zero-fetch; matches whatever flint-chart-mcp version is pinned.
flint://chart-types MCP resource — a browsable version of the same catalog, exposed as an MCP resource for hosts that surface resources in their UI (e.g., Claude Desktop). Same data, host-native display.
flint://agent-skill MCP resource — the complete version-matched chart
language and authoring contract. Prefer it over copied type and property tables.
list_themes + flint://theme-skill — discover preset IDs and load the
version-matched ThemeSpec grammar. Illustrator's flint-theme skill adds the
Theme Lab and visual-QA loop.
Prefer these over external references when the question is "does the server I'm actually talking to render <chartType> on <backend>?" They cost nothing and cannot go stale.
0.5.x note. The underlying library's backend-neutral recommendations,
named views, and chart transformations surface through the interactive
Vega-Lite's create_chart_view MCP App rather than separate tools. Use it to
explore compatible arrangements without changing the data or semantic truth
layer. ECharts and Chart.js workflows use static rendering or compilation;
headless workflows keep using §0.2 plus list_chart_types.
Chart capability — gallery (canonical live examples)
Fetch the canonical Flint gallery (maintained by the microsoft/flint-chart team; always tracks the current release) when:
- You need to confirm Flint actually renders a specific
chartType on a specific backend. Swap the trailing /vegalite → /echarts or /chartjs to view the same catalog for other backends.
- The user is deciding between Vega-Lite vs ECharts vs Chart.js and wants to see the same chart family rendered natively on each backend.
- You need a live example of a chart variant (e.g. a faceted boxplot, a dodge = local grouped bar, a sparse streamgraph) — the gallery shows multiple named variants per
chartType.
- You want the canonical semantic grouping (Bar & Column / Line & Area / Scatter & Points / Distributions / Circular & Radial / Tables & Multi-Dimensional / Maps) that Flint itself uses to organize its chart registry.
This is the authoritative reference for what Flint actually does; §0.2–0.4 above is the compact map, but the gallery is the source of truth for edge cases and backend-specific behavior.
Chart capability — developer documentation
Use the official Flint documentation
for language design, semantics, layout, APIs, and backend architecture. Runtime
resources remain authoritative for the installed server's exact vocabulary.
The library documents Plotly and Excel, but the 0.5.1 MCP tool schema exposes
only Vega-Lite, ECharts, and Chart.js; treat other backends as project-code
integration, not MCP capability.
Rule of thumb: Defensible Decision answers "should I use a bar or a boxplot?"; list_chart_types and the Flint gallery answer "will Flint's Bar Chart on ECharts backend do what I need?"; the deep reference (docs/reference-*.md) answers "what exact channels and properties does that combination support?".
0.6 Design principles (invoke, don't substitute for reading)
Short-form pointers to the underlying literature. Invoke these when justifying
a choice; read the books themselves for depth.
- Trustworthy · Accessible · Elegant (Kirk) — check the chart against all three before shipping
- Tables for lookup, graphs for pattern (Few) — if the user wants exact values, a data table beats any chart; use
Bar Table (Flint) only when you want compact bars with labels
- Explanatory vs exploratory (Knaflic) — for stakeholder communication, show the pearl, not the oyster bed; strip clutter aggressively
- Bullet > Gauge Chart (Few) — always prefer
Bullet Chart for KPI-vs-target; reserve Gauge Chart for large single-KPI tiles
- Gestalt (Knaflic Ch. 3) — group with proximity, distinguish with color/shape, connect with lines, enclose with backgrounds
- Dashboard = one screen, no scrolling, reduce to essence (Few — Information Dashboard Design) — if it doesn't fit, cut, don't scroll
Step 1 — pick chartType
Use one of the registered names exactly. Vega-Lite is the default and
broadest backend; the table below lists each Vega-Lite chart type, the
channels it accepts, and its tuning properties (see "Chart-level
properties"). Required channels are noted.
| chartType | Channels | Notes / required |
|---|
"Scatter Plot" | x, y, color, size, opacity, column, row | x + y required |
"Regression" | x, y, size, color, column, row | scatter + fit line; props regressionMethod, polyOrder |
"Connected Scatter Plot" | x, y, order, color, detail, column, row | x + y required; order = connection sequence (time/index), so the line traces a trajectory and may self-cross |
"Ranged Dot Plot" | x, y, color | dumbbell of two x per category |
"Strip Plot" | x, y, color, size, column, row | jittered points; props stepWidth, pointSize, opacity |
"Bar Chart" | x, y, color, opacity, column, row | one discrete + one measure; prop cornerRadius |
"Grouped Bar Chart" | x, y, group, column, row | group = the clustering category; prop dodge |
"Stacked Bar Chart" | x, y, color, column, row | prop stackMode |
"Pyramid Chart" | x, y, color | diverging horizontal bars |
"Lollipop Chart" | x, y, color, column, row | prop dotSize |
"Waterfall Chart" | x, y, color, column, row | color = Type column, values start/delta/end only; omit it for auto sign coloring; props cornerRadius, totals |
"Gantt Chart" | y, x, x2, color, detail, column, row | x = start, x2 = end |
"Bullet Chart" | y, x, goal, color, column, row |
Donut chart — the type name differs per backend. There is no single donut
type that works everywhere; pick by backend:
| Backend | Use | Hole behavior |
|---|
| Vega-Lite | "Donut Chart" (added in 0.4.1) | innerRadius defaults to 0 — without it the compiled arc mark has no hole and looks like a pie. Always set chartProperties.innerRadius. |
| Chart.js | "Doughnut Chart" (note the spelling) | innerRadius defaults to 55, so it is a donut out of the box |
| ECharts | "Pie Chart" + chartProperties.innerRadius > 0 | no donut type is registered; the hole compiles to a radius range |
"Pie Chart" with innerRadius remains valid on all three backends, so it is
the safe choice when the backend is not yet decided.
Choosing a bar chart (most common mix-up). All three take one discrete
category on x (or y) plus one measure. They differ in how a second
category is shown — and each reads that second category from a different
channel:
"Bar Chart" — use for a single series. When multiple rows share an x, a
second category on color produces stacked segments. For side-by-side bars,
use "Grouped Bar Chart" with the second category on group.
"Stacked Bar Chart" — second category on color, drawn as stacked
segments within each bar (totals matter). Tune with stackMode
(stacked / normalize / layered).
"Grouped Bar Chart" — second category on the group channel, drawn as
side-by-side (dodged) bars within each x cluster (compare values
directly). Put the clustering category on group, not color.
Rule of thumb: comparing parts-to-whole → Stacked; comparing values
side-by-side → Grouped (use group); single series → Bar.
Waterfall color is a special "Type" column, not a free category. On a
"Waterfall Chart" the color channel is reserved for a type field whose
values are literally start, delta, and end — it drives which bars anchor
to zero, not an arbitrary grouping. Do not bind color to an
Increase/Decrease (or up/down, gain/loss) category: the up/down direction is
already derived from the sign of the y value and colored automatically
(green up / red down). For the common case, omit color entirely and let
Flint infer the start/delta/end and per-bar sign coloring. To force which bars
are anchored totals, use the totals property (first/last/both), not a
color field. Only bind color when you genuinely have a start/delta/end
type column.
Backend coverage. Vega-Lite supports all of the above. Other backends
support a subset (verify if targeting a non-VL backend):
- ECharts adds:
"Calendar Heatmap", "Gauge Chart",
"Funnel Chart", "Treemap", "Sunburst Chart", "Sankey Diagram",
"Parallel Coordinates", "Network Graph", "Tree".
- Chart.js supports these 22: Scatter Plot, Connected Scatter Plot, Bubble
Chart, Strip Plot, Bar Chart, Grouped Bar Chart, Stacked Bar Chart, Lollipop
Chart, Combo
Chart, Histogram, Waterfall Chart, Gantt Chart, Line Chart, Bump Chart, Slope
Chart, Area Chart, Range Area Chart, ECDF Plot, Pie Chart, Doughnut Chart,
Radar Chart, Rose Chart.
You do not need to call the library or inspect its source to author the
input — pick from this table.
Step 2 — map fields to channels
Each channel maps to an encoding object { field, ... } (or a bare
string shorthand, expanded to { field: "<string>" }):
"encodings": {
"x": { "field": "weight" },
"y": "mpg",
"color": { "field": "origin" }
}
Encoding object fields (all optional except field):
| Field | Values | Purpose |
|---|
field | column name | Bind the channel to a data column |
type | quantitative, nominal, ordinal, temporal | Override the inferred encoding type (rarely needed) |
aggregate | count, sum, average, mean | Force an aggregation on a measure channel |
sortOrder | ascending, descending | Sort direction for a discrete/sorted axis |
sortBy | channel name (e.g. "y") or field | Sort a category axis by another channel's measure |
scheme | Vega scheme name (e.g. viridis, redblue) | Color scheme for the color channel |
You usually don't need type, aggregate, or sortOrder — they're
inferred from the semantic type. Set them only with specific intent.
Multi-series (wide → long). To plot several measure columns as series,
pass an array on x or y (only those two channels). The library
folds them into long form and synthesizes a series/legend field:
"encodings": { "x": { "field": "month" }, "y": ["sales", "profit"] }
All array fields must be quantitative, and you cannot also bind color
when using the array form (the fold owns the color/legend). This is the
only built-in reshape — there is no transforms/fold property. For any
other shape (long↔wide, an aggregate the encodings can't express, a derived
column, a pivot, a join), reshape the data first with a host tool — pandas/polars,
Arquero/Array.map/SQL, or a data/MCP tool — and pass the result as
data.values. If you have no way to transform, surface the gap to the developer
rather than inventing a transform property that does not exist.
Step 3 — annotate with semantic types
This is the most important step. Semantic types drive all downstream
decisions — formatting, zero baseline, color scheme, scale direction, and
more. Pick the most specific type for each field. Full registered set:
| Family | Semantic types |
|---|
| Temporal (point) | DateTime, Date, Time, Timestamp |
| Temporal (granule) | Year, Quarter, Month, Week, Day, Hour, YearMonth, YearQuarter, YearWeek, Decade |
| Temporal (span) | Duration |
| Measure (amount) | Amount, Price, Quantity, Count, Number |
| Measure (proportion) | Percentage |
| Measure (signed/diverging) | Profit, PercentageChange, Sentiment, Correlation |
| Measure (physical) | Temperature |
| Discrete / rank | Rank, Score, ID |
| Geographic (coord) | Latitude, Longitude |
| Geographic (place) | Country, State, City, Region, Address, ZipCode |
| Categorical | Category, Name, Status, Boolean, Direction, Range |
| Fallback | Unknown |
What choosing well gets you (automatically):
Price / Amount → currency formatting, zero baseline, sequential color
Temperature → diverging color scheme, no forced zero baseline
Correlation → fixed [-1, 1] diverging domain
Rank → reversed axis (1 on top), discrete color
Date / DateTime → temporal axis with auto-granularity formatting
Percentage → percent formatting, 0–100 domain awareness
If you don't know, use Quantity for numbers, Category for strings,
Date/DateTime for date-shaped values. Do not invent type names.
Chart-level properties (chartProperties)
chartProperties is an optional per-chart tuning map. Set a property only
when the user asks for that behavior — defaults are sensible. These are
design choices, not styling overrides (colors/fonts/ticks are still
derived). Values are clamped to the ranges shown.
| Chart type | Property | Type / range (default) | Effect |
|---|
| Bar Chart | cornerRadius | 0–15 (0) | Round bar corners (px) |
| Area / Stacked Bar | stackMode | stacked | normalize | center | layered (unset) | Stacking behavior; normalize = 100%, center = streamgraph |
| Grouped Bar / Boxplot | dodge | auto | local | global (auto) | local compacts sparse groups per category; global preserves aligned group lanes; leave auto unless the user requests one |
| Line / Area / Sparkline | interpolate | linear | monotone | step | step-before | step-after | basis | cardinal | catmull-rom (linear) | Curve shape |
| Line / ECDF Plot | showPoints | boolean (false) | Draw point markers on the line |
| Sparkline | baseline | mean | zero | median | none (mean) | Reference line per spark row |
| Sparkline | trendWidth | 80–600 (240) | Mini line-plot width (px) |
| Boxplot | whiskerMethod | iqr | minmax (iqr) | Whisker rule (Tukey 1.5×IQR vs min–max) |
| Boxplot | showOutliers | boolean (true) | Show outlier points (Tukey only) |
|
Cross-cutting properties (apply to position/faceted charts when
relevant; set only to force non-default behavior):
independentYAxis (boolean) — faceted charts: give each panel its own
y-scale. Not for Sparkline — 0.3.0 removed this for Sparkline (rows
always self-scale now); it still applies to other faceted charts.
logScale_x / logScale_y (boolean) — force a logarithmic axis.
includeZero_x / includeZero_y (boolean) — force the axis to include 0.
xAxisType / yAxisType (temporal | nominal) — force a temporal
field to render as discrete bands (or vice-versa).
Parameter overrides — when to reach for them
Overrides exist, but prefer letting semantic types drive decisions. Reach
for an override only when the user's intent genuinely conflicts with the
default:
- Force an aggregation:
encodings.y = { field: "sales", aggregate: "sum" }.
- Sort a category axis by its measure:
encodings.x = { field: "name", sortBy: "y", sortOrder: "descending" }.
- Pick a color scheme:
encodings.color = { field: "region", scheme: "tableau10" }.
- Override an inferred type:
encodings.x = { field: "year", type: "ordinal" } (e.g. treat a year as discrete bands).
- Resize the chart: Flint sizes from two numbers —
baseSize (the target
it aims for, default 400×320) and canvasSize (a hard ceiling it may never
exceed). With dense data the chart stretches from base toward the ceiling.
- Want a comfortable size that may grow for dense data → set
chart_spec.baseSize = { width, height }.
- Want a fixed slot it must fit inside → set
chart_spec.canvasSize = { width, height } alone; the chart fills it and shrinks to fit, never overflowing. What you ask for is what you get.
- Both → aims for
baseSize, grows toward canvasSize, never beyond.
- Force log / zero baseline: the
logScale_* / includeZero_* chart
properties above.
Global layout tuning lives in the top-level options object (e.g.
addTooltips, band padding, facet sizing). It is rarely needed for
authoring — omit it unless asked.
Worked examples
In each example data is a placeholder — the host binds real rows or a
URL. You author only chart_spec and semantic_types.
Scatter plot
User: "Plot car weight vs fuel economy, colored by origin."
{
"data": { "values": [] },
"semantic_types": {
"weight": "Quantity",
"mpg": "Quantity",
"origin": "Country"
},
"chart_spec": {
"chartType": "Scatter Plot",
"encodings": {
"x": { "field": "weight" },
"y": { "field": "mpg" },
"color": { "field": "origin" }
},
"baseSize": { "width": 400, "height": 300 }
}
}
Revenue bar chart with facets, sorted by value
User: "Show revenue by product line, biggest first, one panel per region."
{
"data": { "values": [] },
"semantic_types": {
"product_line": "Category",
"revenue": "Amount",
"region": "Region"
},
"chart_spec": {
"chartType": "Bar Chart",
"encodings": {
"x": {
"field": "product_line",
"sortBy": "y",
"sortOrder": "descending"
},
"y": { "field": "revenue" },
"column": { "field": "region" }
}
}
}
Time series, multiple series (wide → long via array)
User: "Line chart of monthly sales and profit."
{
"data": { "values": [] },
"semantic_types": {
"month": "YearMonth",
"sales": "Amount",
"profit": "Profit"
},
"chart_spec": {
"chartType": "Line Chart",
"encodings": {
"x": { "field": "month" },
"y": ["sales", "profit"]
},
"chartProperties": { "interpolate": "monotone", "showPoints": true }
}
}
Donut chart, value on size
User: "Show market share by vendor as a donut."
Pie/donut maps the slice value to size (rendered as angle) and the
category to color. Data is already long (one row per vendor).
On Vega-Lite, use the first-class "Donut Chart" type. innerRadius
defaults to 0, so set it — without it the chart compiles to a plain arc mark
with no hole:
{
"data": { "values": [] },
"semantic_types": {
"vendor": "Category",
"share": "Percentage"
},
"chart_spec": {
"chartType": "Donut Chart",
"encodings": {
"size": { "field": "share" },
"color": { "field": "vendor" }
},
"chartProperties": { "innerRadius": 60 }
}
}
On ECharts, no donut type is registered — use "Pie Chart" with the same
innerRadius. On Chart.js, use "Doughnut Chart" (that spelling), which
already has a hole by default:
{
"chart_spec": {
"chartType": "Doughnut Chart",
"encodings": {
"size": { "field": "share" },
"color": { "field": "vendor" }
}
}
}
Bullet chart (KPI vs target)
User: "Show each rep's sales against their quota."
{
"data": { "values": [] },
"semantic_types": {
"rep": "Name",
"sales": "Amount",
"quota": "Amount"
},
"chart_spec": {
"chartType": "Bullet Chart",
"encodings": {
"y": { "field": "rep" },
"x": { "field": "sales" },
"goal": { "field": "quota" }
}
}
}
What you should NOT do
- Don't re-emit the data. Reference columns by name; let the host bind
data (url, variable, or small literal). Never paste large datasets.
- Don't write backend specs directly — write the
ChartAssemblyInput,
then call the assembler. That's the whole point.
- Don't invent transforms. The only built-in reshape is the array form
on
x/y. If the data shape is wrong for the chart, say so and ask the
host to reshape it.
- Don't invent field names. Reference only columns that exist, spelled
exactly. If the data is the wrong shape for the chart, reshape it upstream
rather than guessing column names that aren't there.
- Don't set
type/aggregate/sortOrder unless intent conflicts
with the default.
- Don't pass colors, font sizes, axis tick counts — the compiler
derives these. Users fine-tune the output spec.
- Don't invent semantic type names. If none fit, use the family
default (
Quantity, Category, Date).
- Don't call the library to discover channels/types — this document is
the authoring reference.
Validation checklist
Before returning, verify:
chartType is an exact registered name supported by the target backend.
- Every
field referenced in encodings is a real column name.
- Every encoded field has an entry in
semantic_types (specific type).
- Required channels for the chart type are present (e.g. Bullet→
goal,
Candlestick→open/high/low/close, Pie→size+color).
- Any
chartProperties keys are valid for that chart type and in range.
- You did not inline large data or hand-tune derived styling.
- The data carries no embedded total/subtotal level (e.g. an
all / total
row) mixed with its components on a stacked, grouped, or colored channel.
- Compiler warnings were inspected; a successful render may still have
truncated categories to fit the layout budget.
- Any ThemeSpec was checked on a representative corpus and is supported by the
selected backend.
Would Revise If
Revise this skill by 2026-10-22 (90 days) or sooner if any of the following fires:
- The Defensible Decision chart gallery 404s or restructures such that the §0.5 deep-reference URLs no longer resolve — refresh the escalation targets or fold the needed tips into §0 directly.
flint://agent-skill or flint://theme-skill disappears, diverges from the
runtime tool schema, or cannot be read on a supported host — restore only the
minimum local reference needed while the upstream defect is active.
flint-chart-mcp ships a breaking change (chart-type rename, ChartAssemblyInput shape change, tool signature change) that this skill doesn't account for — sync §0.4 (Flint coverage) and the worked examples to the new version.
- Any recommendation in §0.2 (question → family → chartType) is refuted by a source we trust (a case study, a Knaflic/Kirk/Few/Wexler update, or field feedback from ≥2 heir workspaces) — retire or rework that row.
- The plugin gets ≥3 heir installs and none of them exercise §0.5 (deep-reference escalation) — that signals the compact table alone is sufficient and §0.5 is decorative; prune it.
- The upstream fork base (
microsoft/flint-chart/agent-skills/flint-chart-author/SKILL.md) publishes a materially revised body — decide whether to rebase §1-N onto the new upstream or hold on the current fork point. Do not rebase blindly. As of 2026-08-05 upstream's skill is itself behind its own generated references: it still teaches "Donut chart: use Pie Chart with innerRadius" and never names the Donut Chart type that reference-vegalite.md documents. A straight rebase would regress the backend-specific donut guidance and the corrected binCount / polyOrder ranges in §Chart-level properties. Diff against the generated reference-*.md files, not against upstream's skill.
- Upstream absorbs chart selection itself. Recommendations, named views,
and transformations are available through
create_chart_view, but not as
headless tools. If they become tool-reachable and match or beat §0.2 on the
same question, call upstream and keep only Alex framing on top.
- The backend list changes. §0.4 and the worked examples assume Vega-Lite /
ECharts / Chart.js because those are the
0.5.1 MCP enum values. If a future
server accepts Plotly or Excel, rework coverage and ThemeSpec boundaries.