flint-chart: pick, author, and render a chart
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).
- 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": "npx",
"args": ["-y", "flint-chart-mcp@^0.2.2"],
},
},
}
"type": "stdio" is optional in some hosts but always declare it —
omitting it makes transport-related failures harder to diagnose.
npx -y fetches the package on first use and caches it (~5-10 MB in the
npm cache; ~1-2 s cold start).
- The
^0.2.2 pin is held deliberately, not through neglect. Public npm
latest is 0.4.0, but it is unreachable from Microsoft corporate machines,
whose npm mirror stops at 0.2.2. Do not advise a user to bump the pin
without checking npm config get registry first — a corporate mirror can
report a stale latest that is not the public one, and on such a machine a
^0.4.0 pin fails with ETARGET.
- Corporate / air-gapped: if
npx cannot reach the npm registry, ask
the user to run npm install -g flint-chart-mcp once from a machine that
can, then change "command": "npx", "args": ["-y", "flint-chart-mcp"] to
"command": "flint-chart-mcp", "args": [].
- Hardened deployment (only inline
data.values accepted, no local
data.url files): append "--disable-file-reference" to args.
After adding, the host must reload for MCP servers to spawn. VS Code:
Ctrl+Shift+P → "Developer: Reload Window". Claude Desktop / Cursor:
restart the app.
Verify. Call list_chart_types with { "backend": "vegalite" }. If it
returns the chart catalog, the server is up.
If the tools still do not appear, isolate which half is broken before
guessing. The server and the client fail identically from chat. If the
user has the plugin repo checked out, node scripts/verify-install.mjs
does this in one step; otherwise probe the server yourself — pipe a
handshake plus a tools/list into the binary over stdio and read the
response. A serverInfo block followed by a tools array proves the server
is healthy and the fault is config, trust, or session staleness. Then work
down this list:
- Trust prompt. VS Code will not start a local stdio server until you
approve it.
Ctrl+Shift+P → MCP: List Servers → pick flint →
Start, and watch for the approval dialog.
- Server output. Same menu → Show Output. Startup crashes surface
there and nowhere else.
- Restart the chat session. A window reload is not always enough — the
agent's tool inventory can stay stale until the session itself restarts.
- HTTP transport only: an HTTP server needs OAuth authorization after
starting, which is a separate step from trust. A server can be
configured and started yet still unauthorized.
For deeper MCP config — HTTP transport, allowed-host lists, deployment
patterns, full CLI reference — see the canonical MCP doc:
https://microsoft.github.io/flint-chart/#/mcp. Point the user there for
anything beyond the stdio install path documented above.
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, install it and the renderer peer deps for the target backend:
npm install flint-chart
# Then ONE of these based on the backend you'll actually render:
npm install vega vega-lite vega-embed # Vega-Lite
npm install echarts # ECharts
npm install chart.js # Chart.js
Import in code:
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 whenever the user asks to see a chart — it opens an
interactive, live-rendered view with a customization panel, and it validates
the spec for you. Only fall back to render_chart (PNG/SVG) when the host has
no App UI support or the user explicitly wants a static image. Use
validate_chart to check a spec without rendering, compile_chart when the
user wants backend-native JSON, 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 can run with npx:
npx -y flint-chart-mcp
For JavaScript or TypeScript projects, install Flint first and add only the
renderer peer dependencies needed by the backend you will render:
npm install flint-chart
npm install vega vega-lite vega-embed # browser Vega-Lite rendering
npm install echarts # ECharts rendering
npm install chart.js # Chart.js rendering
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"
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)
}
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.
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
| 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%), Pie Chart with innerRadius > 0 (Donut — use center for a KPI), Treemap (many/hierarchy, ECharts), Sunburst (interactive hierarchy, ECharts), Funnel (sequential stages, ECharts) |
| Flow between stages? |
Flow |
Sankey (linear flow, ECharts) |
Streamgraph (aesthetic, precision sacrificed), Heatmap (matrix pattern), Chord-like flows → use Sankey instead |
| Progress toward a target? |
KPI |
Bullet Chart (Few's superior alternative to gauges: actual + target + qualitative ranges in one horizontal bar) |
KPI Card (single number with delta), Sparkline (in-table trend), Gauge (ECharts — reserve for high-visibility single-KPI tiles only) |
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 (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 / Hierarchy Tree |
Not in Flint's scope |
Export prepared data to Power BI / Tableau / dedicated tool |
| 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 |
0.5 When to fetch a deep reference
Two authoritative external references, each with a distinct role. Fetch the one that matches 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 — "does Flint render this? which backend?"
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.
Rule of thumb: Defensible Decision answers "should I use a bar or a boxplot?"; the Flint gallery answers "will Flint's Bar Chart on ECharts backend do what I need?"
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 (Few) — always prefer
Bullet Chart for KPI-vs-target; reserve Gauge 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 |
goal required (target) |
"Histogram" |
x, color, column, row |
x = measure to bin; prop binCount |
"Boxplot" |
x, y, color, opacity, column, row |
category + measure; props whiskerMethod, showOutliers, dodge |
"ECDF Plot" |
x, color, detail, column, row |
x = measure; cumulative distribution (step line); prop showPoints |
"Heatmap" |
x, y, color, column, row |
color = the measure |
"Line Chart" |
x, y, color, strokeDash, detail, opacity, column, row |
props interpolate, showPoints |
"Sparkline" |
x, y, color, detail, row, column |
x + y required; small-multiple mini trend lines, one per series (series from color or detail); props interpolate, baseline, trendWidth |
"Bump Chart" |
x, y, color, detail, column, row |
rank-over-time lines |
"Slope Chart" |
x, y, color, detail, column, row |
two-period value change; straight segments + end points, one line per category |
"Area Chart" |
x, y, color, opacity, column, row |
props interpolate, opacity, stackMode |
"Range Area Chart" |
x, y, y2, color, column, row |
x + y + y2 required; translucent band from y (low) to y2 (high), value axis fits the band (not zero) |
"Violin Plot" |
x, y, color, row |
x (category) + y (measure) required; mirrored KDE density per category, prop bandwidth; Vega-Lite only; a genuine color subgroup splits two groups or grids 3+ groups |
"Streamgraph" |
x, y, color, column, row |
center-stacked areas |
"Density Plot" |
x, color, column, row |
prop bandwidth |
"Pie Chart" |
size, color, column, row |
size = slice value (→ angle), color = category; props innerRadius, sortSlices |
"Rose Chart" |
x, y, color, column, row |
polar bars; props alignment, padAngle, sortSlices |
"Radar Chart" |
x, y, color, column, row |
props filled, fillOpacity, strokeWidth |
"Candlestick Chart" |
x, open, high, low, close, column, row |
OHLC all required |
"Bar Table" |
y, x, color, column, row |
compact bars + value labels |
"KPI Card" |
metric, value, goal |
big-number tile; prop behindThreshold |
"Map" |
longitude, latitude, color, size, opacity |
bubble map; props region, projection |
"Choropleth" |
id, color, detail |
id = geographic key |
Donut chart: use "Pie Chart" with chartProperties.innerRadius > 0.
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
…(truncated)
1---2name: flint-chart3description: 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.4---5
6# flint-chart: pick, author, and render a chart
7
8## What you produce (and what you do NOT)
9
10Your output is the **spec**: the `chart_spec` and `semantic_types` of a
11`ChartAssemblyInput`. You reference data columns **by name**. The host
12passes the resulting input to `assembleVegaLite`, `assembleECharts`,
13or `assembleChartjs` to get a backend spec.
14
15**You write the input spec, not the output spec.** And critically:
16
17- **DO** emit `chart_spec` (chart type, channel→field mapping, properties)
18 and `semantic_types` (field → semantic type).
19- **Reference columns by name.** How `data` itself gets bound depends on
20 the situation — a URL, a host-side variable, or embedded rows (see "How
21 data gets bound"). Embedding is fine for small tables; just don't
22 re-serialize a _large_ dataset by hand, since that risks truncation and
23 silent value corruption and wastes tokens.
24- **Transform data before Flint.** If the requested chart needs aggregation,
25 filtering, joins, pivots, derived columns, or long/wide reshaping beyond
26 Flint's built-in static-series fold, use a coding, notebook, SQL, or data tool
27 first. Then author the Flint spec against the transformed table.
28- **Style after Flint, only when needed.** Author structure in Flint. For a
29 presentation tweak Flint does not express (a reference line, annotation, or
30 shaded band), use the Vega-Lite escape hatch — see "Post-Flint style
31 customization". Never feed edited Vega-Lite JSON back to `render_chart`.
32- **Look at what you rendered.** A chart with a collapsed scale, a merged color
33 scale, or an empty data binding renders as a valid image that tells the wrong
34 story — `validate_chart` cannot catch that. Load the `render-verify` skill
35 after rendering, and always after a post-Flint Vega-Lite edit.
36
37## Verify Flint is available before rendering
38
39Before you promise to render a chart, confirm the tools exist. If they don't,
40install them (or ask the user to). Failing loudly early is cheaper than
41authoring a spec no one can render.
42
43### For MCP rendering (default in this skill)
44
451. **Check for the `flint` MCP server.** Look in your available tool inventory
46 for `render_chart`, `compile_chart`, `validate_chart`, `list_chart_types`,
47 or `create_chart_view`. If any of them are present, the server is registered
48 and reachable — skip to step 4.
49
502. **If the tools are missing, `flint-chart-mcp` is not registered.** Ask the
51 user to add it, or add it yourself if you can edit their workspace config.
52
53 **Put the file in the right place — this is the single most common failure.**
54
55 | Host | Path | Top-level key |
56 | ---------------------------- | ----------------------------- | ------------- |
57 | **VS Code** (workspace) | `.vscode/mcp.json` | `servers` |
58 | Claude Code / Claude Desktop | `.mcp.json` at workspace root | `servers` |
59 | Cursor | `.cursor/mcp.json` | `servers` |
60 | GitHub Copilot CLI | `~/.copilot/mcp-config.json` | `mcpServers` |
61
62 The schema is identical across the first three, which is exactly why the
63 wrong path looks like it should work. **VS Code never reads a workspace-root
64 `.mcp.json`** — and it reports no error, because it isn't parsing a broken
65 file, it's reading no file at all. If a user says "I added the config and
66 nothing happened," check the path before anything else.
67
68 **Copilot CLI differs twice over:** different path _and_ a different
69 top-level key (`mcpServers`, not `servers`). A `servers` block pasted there
70 fails just as silently. Prefer telling the user to run `/mcp add` inside a
71 CLI session and let it write the file. The path is overridable via
72 `$COPILOT_HOME`.
73
74 Always **merge** into any existing config rather than overwriting it —
75 clobbering the file destroys whatever other servers the user had.
76
77 ```jsonc
78 // .vscode/mcp.json (VS Code) — merge with any existing "servers" map
79 {
80 "servers": {
81 "flint": {
82 "type": "stdio",
83 "command": "npx",
84 "args": ["-y", "flint-chart-mcp@^0.2.2"],
85 },
86 },
87 }
88 ```
89
90 - `"type": "stdio"` is optional in some hosts but always declare it —
91 omitting it makes transport-related failures harder to diagnose.
92 - `npx -y` fetches the package on first use and caches it (~5-10 MB in the
93 npm cache; ~1-2 s cold start).
94 - The `^0.2.2` pin is held deliberately, not through neglect. Public npm
95 `latest` is 0.4.0, but it is unreachable from Microsoft corporate machines,
96 whose npm mirror stops at 0.2.2. Do not advise a user to bump the pin
97 without checking `npm config get registry` first — a corporate mirror can
98 report a stale `latest` that is not the public one, and on such a machine a
99 `^0.4.0` pin fails with `ETARGET`.
100 - **Corporate / air-gapped:** if `npx` cannot reach the npm registry, ask
101 the user to run `npm install -g flint-chart-mcp` once from a machine that
102 can, then change `"command": "npx", "args": ["-y", "flint-chart-mcp"]` to
103 `"command": "flint-chart-mcp", "args": []`.
104 - **Hardened deployment** (only inline `data.values` accepted, no local
105 `data.url` files): append `"--disable-file-reference"` to `args`.
106
1073. **After adding, the host must reload for MCP servers to spawn.** VS Code:
108 `Ctrl+Shift+P` → "Developer: Reload Window". Claude Desktop / Cursor:
109 restart the app.
110
1114. **Verify.** Call `list_chart_types` with `{ "backend": "vegalite" }`. If it
112 returns the chart catalog, the server is up.
113
1145. **If the tools still do not appear, isolate which half is broken before
115 guessing.** The server and the client fail identically from chat. If the
116 user has the plugin repo checked out, `node scripts/verify-install.mjs`
117 does this in one step; otherwise probe the server yourself — pipe a
118 handshake plus a `tools/list` into the binary over stdio and read the
119 response. A `serverInfo` block followed by a `tools` array proves the server
120 is healthy and the fault is config, trust, or session staleness. Then work
121 down this list:
122 1. **Trust prompt.** VS Code will not start a local stdio server until you
123 approve it. `Ctrl+Shift+P` → **MCP: List Servers** → pick `flint` →
124 **Start**, and watch for the approval dialog.
125 2. **Server output.** Same menu → **Show Output**. Startup crashes surface
126 there and nowhere else.
127 3. **Restart the chat session.** A window reload is not always enough — the
128 agent's tool inventory can stay stale until the session itself restarts.
129 4. **HTTP transport only:** an HTTP server needs OAuth authorization after
130 starting, which is a separate step from trust. A server can be
131 configured and started yet still unauthorized.
132
1336. **For deeper MCP config** — HTTP transport, allowed-host lists, deployment
134 patterns, full CLI reference — see the canonical MCP doc:
135 <https://microsoft.github.io/flint-chart/#/mcp>. Point the user there for
136 anything beyond the stdio install path documented above.
137
138### For project code integration
139
140Only needed if the user asked you to write code that **imports** `flint-chart`
141directly (not to render via MCP).
142
1431. **Check the project's `package.json`** for `flint-chart` in `dependencies`
144 or `devDependencies`. If present, skip to step 3.
145
1462. **If missing, install it and the renderer peer deps for the target backend:**
147
148 ```bash
149 npm install flint-chart
150 # Then ONE of these based on the backend you'll actually render:
151 npm install vega vega-lite vega-embed # Vega-Lite
152 npm install echarts # ECharts
153 npm install chart.js # Chart.js
154 ```
155
1563. **Import in code:**
157
158 ```ts
159 import {
160 assembleChartjs,
161 assembleECharts,
162 assembleVegaLite,
163 } from "flint-chart";
164 const spec = assembleVegaLite(input); // or assembleECharts / assembleChartjs
165 ```
166
167### For Python
168
169Not yet published to PyPI (as of 2026-07-24). Use the npm package or MCP
170server for released workflows until the Python release lands.
171
172## When the user wants more than a spec
173
174First decide which workflow the user is asking for:
175
176- **Spec authoring only:** return a `ChartAssemblyInput` or its
177 `semantic_types` + `chart_spec` pieces. Do not install packages or write
178 renderer code unless asked.
179- **MCP chart output:** if Flint MCP tools are available, **default to
180 `create_chart_view`** whenever the user asks to see a chart — it opens an
181 interactive, live-rendered view with a customization panel, and it validates
182 the spec for you. Only fall back to `render_chart` (PNG/SVG) when the host has
183 no App UI support or the user explicitly wants a static image. Use
184 `validate_chart` to check a spec without rendering, `compile_chart` when the
185 user wants backend-native JSON, and `list_chart_types` when you need the
186 supported chart catalog.
187- **Project integration, only when the user asks for code:** add Flint to an
188 app, notebook, script, or agentic product, install/import the library, and
189 call an assembler in code. Keep the same `ChartAssemblyInput` contract, then
190 let the host render the backend result.
191
192For MCP clients, the server can run with `npx`:
193
194```bash
195npx -y flint-chart-mcp
196```
197
198For JavaScript or TypeScript projects, install Flint first and add only the
199renderer peer dependencies needed by the backend you will render:
200
201```bash
202npm install flint-chart
203npm install vega vega-lite vega-embed # browser Vega-Lite rendering
204npm install echarts # ECharts rendering
205npm install chart.js # Chart.js rendering
206```
207
208Then compile with the requested backend:
209
210```ts
211import {
212 assembleChartjs,
213 assembleECharts,
214 assembleVegaLite,
215} from "flint-chart";
216
217const vegaLiteSpec = assembleVegaLite(input);
218const echartsOption = assembleECharts(input);
219const chartjsConfig = assembleChartjs(input);
220```
221
222Python support is planned for a later release. Until the PyPI package is
223published, use the npm package or MCP server for released workflows.
224
225```ts
226interface ChartAssemblyInput {
227 // Bound by the HOST or by you, depending on the situation (see below).
228 data: { values: any[] } | { url: string };
229 semantic_types?: Record<string, string>; // field → semantic type ← you write this
230 chart_spec: {
231 // ← you write this
232 chartType: string; // e.g. "Scatter Plot"
233 encodings: Record<string, EncodingValue>; // channel → { field, ... } (or array)
234 baseSize?: { width: number; height: number }; // target layout size, default 400×320
235 canvasSize?: { width: number; height: number }; // optional hard ceiling on stretch
236 chartProperties?: Record<string, any>; // per-chart tuning (optional)
237 };
238 options?: Record<string, any>; // global layout options (rarely needed)
239}
240```
241
242## How data gets bound
243
244Use the binding mode that matches the runtime. Do not mix them.
245
2461. **Direct MCP rendering: embed rows.** When calling `render_chart`,
247 `compile_chart`, or `validate_chart`, the tool arguments are JSON. If the
248 data is small or already transformed by another tool, pass it as
249 `data: { values: [...] }`. Do not pass runtime variable names in
250 MCP tool calls — the MCP server cannot see your local variables.
2512. **Direct MCP rendering: reference a local file.**
252 The `flint-chart-mcp` server can load `data: { url: "..." }` from local
253 `.json`, `.csv`, or `.tsv` files. By default any local file the agent can
254 name is readable (relative paths resolve against the working directory); a
255 hardened deployment may reject local file references entirely via
256 `--disable-file-reference` (or `FLINT_MCP_DISABLE_FILE_REFERENCE`), in which
257 case pass rows inline with `data.values`. Remote URL
258 fetching is disabled. If the data must be transformed first, use a
259 coding/data tool to write a small prepared file, then reference that file.
2603. **Generated application or notebook code: bind runtime variables.** If the
261 user asks you to add Flint to code, write normal data-loading code first and
262 pass a real runtime value, e.g. `data: { values: rows }`, to
263 `assembleVegaLite`, `assembleECharts`, or `assembleChartjs`. This variable
264 pattern is for generated code, not for MCP tool calls.
265
266For spec-only answers, return the `semantic_types` and `chart_spec` pieces and
267state how the host should bind data. In the worked examples below, `data` is
268shown as `{ values: [] }` to signal "host binds this" — focus on `chart_spec`
269and `semantic_types`.
270
271## Data transformation before charting
272
273Flint is a chart compiler, not a data-wrangling layer. If the chart needs grouped
274totals, time buckets, filters, joins, pivots, derived ratios, or a long-form
275table, transform the data first with a host tool, then bind the prepared table
276(see "How data gets bound"). Pick semantic types and channels for the transformed
277columns, not for columns that no longer exist.
278
279**Sanity-read the values first — don't chart blind.** Inspect the actual data
280with your data tool (distinct values per category column, min/max per measure),
281not just the column names, and watch for:
282
283- **Embedded totals.** A category column may mix an aggregate level with its
284 parts (e.g. `all` alongside `cage-free`/`caged`, or a `Total` region). Charting
285 the total with its parts double-counts and flattens the parts — keep one or the
286 other on a stacked/grouped/colored channel, not both.
287- **Units.** Check whether a rate is a fraction (0–1) or already a percent
288 (0–100) before tagging it `Percentage`; don't scale twice.
289- **One real entity.** If your breakdown column has a single distinct value, the
290 per-group chart collapses to one mark — the intended breakdown is likely a
291 different column.
292
293## Post-Flint style customization
294
295Stay at the Flint level for structure (data, chart type, channels, transforms,
296sizing, properties) — Flint specs stay portable and regenerate safely. Drop to
297backend JSON only after a valid Flint chart exists, and only for a narrow
298presentation change Flint does not expose (exact axis/legend/mark styling,
299titles, annotations, reference lines, layout polish). Never use it to change the
300data, chart type, field mappings, or transforms — fix those upstream.
301
302For a Vega-Lite-specific style tweak:
303
3041. Author and validate the Flint `ChartAssemblyInput`.
3052. Render or inspect the Flint chart first, when possible.
3063. Call `compile_chart` with `backend: "vegalite"`.
3074. Make the smallest necessary style/presentation edit to the returned
308 Vega-Lite spec.
3095. Render the edited spec in the host environment with a Vega-Lite renderer.
310
311This edited Vega-Lite spec is no longer a portable Flint spec. Do not send it to
312`render_chart`; use `render_chart` only for Flint `ChartAssemblyInput`.
313
314**Verification is mandatory here.** Once you leave the Flint level, the MCP
315server's validation no longer protects you — an edited spec can render a
316plausible-looking chart that is silently wrong. Load the `render-verify` skill:
317open the result, read its console errors, and check it against the failure
318catalog before declaring it done.
319
320## Attribution
321
322Chart-selection framework (§0 below) distilled from standard visualization
323literature — Cole Nussbaumer Knaflic (_Storytelling with Data_), Andy Kirk
324(_Data Visualisation_), Stephen Few (_Show Me the Numbers_, _Information
325Dashboard Design_), Wexler / Shaffer / Cotgreave (_Big Book of Dashboards_). For
326per-chart design tips and the full 48-chart catalog, see _The Defensible
327Decision_ chart gallery: <https://www.thedefensibledecision.com/gallery/chart-gallery.html>.
328For live examples of every Flint `chartType` across all backends, organized by
329semantic category (Bar & Column / Line & Area / Scatter & Points / Distributions
330/ Circular & Radial / Tables & Multi-Dimensional / Maps), see the canonical
331Flint gallery at <https://microsoft.github.io/flint-chart/#/gallery/vegalite>
332(swap `/vegalite` for `/echarts` or `/chartjs` to view the other backends).
333flint-chart itself is a Microsoft Research + IDEAS Lab (Renmin University)
334project — canonical docs:
335<https://microsoft.github.io/flint-chart/#/documentation/getting-started>
336(getting started, API reference, architecture, chart-template extension),
337<https://microsoft.github.io/flint-chart/#/mcp> (MCP server deployment + full
338CLI), and <https://microsoft.github.io/flint-chart/> (project home + live
339editor).
340
341## Step 0 — pick the chart (when the user hasn't said which one)
342
343**Skip this step** if the user already named the chart type (e.g. "scatter of
344weight vs mpg"). Jump to Step 1 and author the spec. Otherwise, work down this
345list before choosing a `chartType`.
346
347### 0.1 One-sentence message — the Big Idea
348
349Before choosing a chart, establish the message it should carry. **Load the
350`chart-big-idea` skill and run it now** — look in
351`.github/skills/local/chart-big-idea/SKILL.md` first (heir-installed), then
352`.github/skills/chart-big-idea/SKILL.md` (baseline).
353
354It does four things this step cannot do inline:
355
356- **Reads the surrounding context first** — the prose next to the insertion
357 point, the ticket, the section heading, prior captions — so you do not ask the
358 user to re-articulate a claim they already wrote.
359- **Questions the intent** — whether the chart should exist at all, and whether
360 the stated purpose is the real one. If the intended message and the data
361 disagree, that surfaces here rather than after rendering.
362- **Elicits the Big Idea** with a three-question ladder, one question at a time,
363 when it is not written down anywhere.
364- **Asks the TRADITIONAL vs INNOVATIVE style stance**, which changes the
365 chartType you pick in §0.2.
366
367The output is a compact Chart Brief. Treat it as the constraint on everything
368below: §0.2 selection, §0.4 coverage, and the spec you author in Steps 1-3.
369
370**If that skill is not available**, do the compact version inline
371(Knaflic — _Storytelling with Data_):
372
373- What is your unique point of view?
374- What is at stake?
375- Express it as a complete sentence, not a phrase.
376
377If you cannot write the sentence, ask the user for context before drawing.
378"Show sales" is a phrase; "Q4 sales dropped 18% in APAC — that's where our
379attention should go this quarter" is a sentence. The sentence shape drives the
380chart choice.
381
382### 0.2 Question → family → chart
383
384| Analytical question | Family | Primary chart | Alternates |
385| ------------------------------ | ------------ | ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
386| 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) |
387| 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) |
388| 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) |
389| 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) |
390| 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%), `Pie Chart` with `innerRadius` > 0 (Donut — use center for a KPI), `Treemap` (many/hierarchy, ECharts), `Sunburst` (interactive hierarchy, ECharts), `Funnel` (sequential stages, ECharts) |
391| Flow between stages? | Flow | `Sankey` (linear flow, ECharts) | `Streamgraph` (aesthetic, precision sacrificed), `Heatmap` (matrix pattern), `Chord`-like flows → use `Sankey` instead |
392| Progress toward a target? | KPI | `Bullet Chart` (Few's superior alternative to gauges: actual + target + qualitative ranges in one horizontal bar) | `KPI Card` (single number with delta), `Sparkline` (in-table trend), `Gauge` (ECharts — reserve for high-visibility single-KPI tiles only) |
393
394### 0.3 Anti-patterns — don't recommend
395
396- **Pie with >5 slices** — humans can't compare angles; use `Bar Chart` or `Stacked Bar Chart` with `stackMode: normalize`
397- **Pie without a dominant slice** — if no category is ≥60% or the story isn't "X vs the rest", use a `Bar Chart`
398- **Word cloud for real analysis** — position and word length distort; use `Bar Chart` of top-N terms (not in Flint; export to another tool)
399- **Dual-axis combo without justification** — dual axes mislead by aligning unrelated scales; consider two separate charts
400- **Truncated Y-axis on bars** — exaggerates differences; always start `Bar Chart` at zero (Flint does this by default; don't override)
401- **Streamgraph when precise values matter** — the flowing baseline sacrifices readability; use `Area Chart` instead
402- **Gauge over Bullet** — `Bullet Chart` packs actual + target + qualitative bands in less space with more precision
403- **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
404
405### 0.4 Flint coverage — substitutes when the ideal chart isn't native
406
407Some charts from wider visualization literature aren't in Flint's registry.
408Recommend the substitute, not the missing chart:
409
410| Ideal chart | Flint substitute | How |
411| ----------------------------------------------------------------------- | -------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
412| Waffle Chart (10×10 grid %) | `Bar Chart` or `Stacked Bar Chart` with `stackMode: normalize` | Labeled percentage bar communicates the same "N out of 100" |
413| Chord Diagram (circular flows) | `Sankey` (ECharts backend) | Linear flow is easier to read anyway |
414| Pareto Chart (bars + cumulative %) | `Bar Chart` + `Line Chart` via multi-encoding | Sort bars descending, overlay cumulative-% line |
415| Beeswarm Plot (every point) | `Strip Plot` with `stepWidth`, `pointSize`, `opacity` | Jittered points instead of packed; same "every dot is real" story |
416| Ridgeline Plot (many densities) | `Violin Plot` with `row` facet | Density curves stacked per group |
417| Small Multiples | any chart with `row` or `column` encoding | Native facet support |
418| Word Cloud / Sentiment / NPS Gauge / Likert / Mind Map / Hierarchy Tree | Not in Flint's scope | Export prepared data to Power BI / Tableau / dedicated tool |
419| 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 |
420| 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 |
421| Table / Matrix (precise value lookup) | Use a data table (not Flint) | Stephen Few's rule — tables for lookup, graphs for pattern |
422
423### 0.5 When to fetch a deep reference
424
425Two authoritative external references, each with a distinct role. Fetch the one that matches the question:
426
427**Chart selection — "which chart for which analytical question?"**
428
429Fetch [The Defensible Decision — Complete Chart Gallery](https://www.thedefensibledecision.com/gallery/chart-gallery.html) when:
430
431- The user asks about a chart not in §0.2 or §0.4
432- The user asks "what other charts could work here?"
433- The user needs per-chart design tips (axis handling, color, labeling, accessibility)
434- The compact table above is ambiguous for the case at hand
435
436The gallery has 48 charts across 10 families with per-chart 💡 tips, distilled from Knaflic / Kirk / Few / Wexler.
437
438**Chart capability — "does Flint render this? which backend?"**
439
440Fetch the canonical [Flint gallery](https://microsoft.github.io/flint-chart/#/gallery/vegalite) (maintained by the microsoft/flint-chart team; always tracks the current release) when:
441
442- 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.
443- 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.
444- 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`.
445- 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.
446
447This 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.
448
449**Rule of thumb**: Defensible Decision answers "should I use a bar or a boxplot?"; the Flint gallery answers "will Flint's `Bar Chart` on ECharts backend do what I need?"
450
451### 0.6 Design principles (invoke, don't substitute for reading)
452
453Short-form pointers to the underlying literature. Invoke these when justifying
454a choice; read the books themselves for depth.
455
456- **Trustworthy · Accessible · Elegant** (Kirk) — check the chart against all three before shipping
457- **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
458- **Explanatory vs exploratory** (Knaflic) — for stakeholder communication, show the pearl, not the oyster bed; strip clutter aggressively
459- **Bullet > Gauge** (Few) — always prefer `Bullet Chart` for KPI-vs-target; reserve `Gauge` for large single-KPI tiles
460- **Gestalt** (Knaflic Ch. 3) — group with proximity, distinguish with color/shape, connect with lines, enclose with backgrounds
461- **Dashboard = one screen, no scrolling, reduce to essence** (Few — _Information Dashboard Design_) — if it doesn't fit, cut, don't scroll
462
463---
464
465## Step 1 — pick `chartType`
466
467Use one of the registered names **exactly**. Vega-Lite is the default and
468broadest backend; the table below lists each Vega-Lite chart type, the
469channels it accepts, and its tuning properties (see "Chart-level
470properties"). Required channels are noted.
471
472| chartType | Channels | Notes / required |
473| -------------------------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
474| `"Scatter Plot"` | x, y, color, size, opacity, column, row | x + y required |
475| `"Regression"` | x, y, size, color, column, row | scatter + fit line; props `regressionMethod`, `polyOrder` |
476| `"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 |
477| `"Ranged Dot Plot"` | x, y, color | dumbbell of two x per category |
478| `"Strip Plot"` | x, y, color, size, column, row | jittered points; props `stepWidth`, `pointSize`, `opacity` |
479| `"Bar Chart"` | x, y, color, opacity, column, row | one discrete + one measure; prop `cornerRadius` |
480| `"Grouped Bar Chart"` | x, y, group, column, row | `group` = the clustering category; prop `dodge` |
481| `"Stacked Bar Chart"` | x, y, color, column, row | prop `stackMode` |
482| `"Pyramid Chart"` | x, y, color | diverging horizontal bars |
483| `"Lollipop Chart"` | x, y, color, column, row | prop `dotSize` |
484| `"Waterfall Chart"` | x, y, color, column, row | `color` = Type column, values `start`/`delta`/`end` only; omit it for auto sign coloring; props `cornerRadius`, `totals` |
485| `"Gantt Chart"` | y, x, x2, color, detail, column, row | x = start, x2 = end |
486| `"Bullet Chart"` | y, x, goal, color, column, row | `goal` required (target) |
487| `"Histogram"` | x, color, column, row | x = measure to bin; prop `binCount` |
488| `"Boxplot"` | x, y, color, opacity, column, row | category + measure; props `whiskerMethod`, `showOutliers`, `dodge` |
489| `"ECDF Plot"` | x, color, detail, column, row | x = measure; cumulative distribution (step line); prop `showPoints` |
490| `"Heatmap"` | x, y, color, column, row | color = the measure |
491| `"Line Chart"` | x, y, color, strokeDash, detail, opacity, column, row | props `interpolate`, `showPoints` |
492| `"Sparkline"` | x, y, color, detail, row, column | x + y required; small-multiple mini trend lines, one per series (series from `color` or `detail`); props `interpolate`, `baseline`, `trendWidth` |
493| `"Bump Chart"` | x, y, color, detail, column, row | rank-over-time lines |
494| `"Slope Chart"` | x, y, color, detail, column, row | two-period value change; straight segments + end points, one line per category |
495| `"Area Chart"` | x, y, color, opacity, column, row | props `interpolate`, `opacity`, `stackMode` |
496| `"Range Area Chart"` | x, y, y2, color, column, row | x + y + y2 required; translucent band from `y` (low) to `y2` (high), value axis fits the band (not zero) |
497| `"Violin Plot"` | x, y, color, row | x (category) + y (measure) required; mirrored KDE density per category, prop `bandwidth`; **Vega-Lite only**; a genuine `color` subgroup splits two groups or grids 3+ groups |
498| `"Streamgraph"` | x, y, color, column, row | center-stacked areas |
499| `"Density Plot"` | x, color, column, row | prop `bandwidth` |
500| `"Pie Chart"` | size, color, column, row | `size` = slice value (→ angle), `color` = category; props `innerRadius`, `sortSlices` |
501| `"Rose Chart"` | x, y, color, column, row | polar bars; props `alignment`, `padAngle`, `sortSlices` |
502| `"Radar Chart"` | x, y, color, column, row | props `filled`, `fillOpacity`, `strokeWidth` |
503| `"Candlestick Chart"` | x, open, high, low, close, column, row | OHLC all required |
504| `"Bar Table"` | y, x, color, column, row | compact bars + value labels |
505| `"KPI Card"` | metric, value, goal | big-number tile; prop `behindThreshold` |
506| `"Map"` | longitude, latitude, color, size, opacity | bubble map; props `region`, `projection` |
507| `"Choropleth"` | id, color, detail | `id` = geographic key |
508
509**Donut chart:** use `"Pie Chart"` with `chartProperties.innerRadius > 0`.
510
511**Choosing a bar chart (most common mix-up).** All three take one discrete
512category on `x` (or `y`) plus one measure. They differ in how a **second**
513category is shown — and each reads that second category from a **different
514channel**:
515
516- `"Bar Chart"` — use for a single series. When multiple rows share an `x`, a
517 second category on `color` produces stacked segments. For side-by-side bars,
518 use `"Grouped Bar Chart"` with the second category on `group`.
519- `"Stacked Bar Chart"` — second category on `color`, drawn as **stacked**
520 segments within each bar (totals matter). Tune with `stackMode`
521 (`stacked` / `normalize` / `layered`).
522- `"Grouped Bar Chart"` — second category on the **`group`
523
524…(truncated)