microsoft/flint-chart · Archived

flint-chart-author

Use when: the user asks to make or render charts with flint-chart, visualize tabular data, generate a ChartAssemblyInput, validate/render through MCP, or add Flint to a JS/TS project.

First seen Jul 10, 2026

Installation

$ npx skills add microsoft/flint-chart --skill flint-chart-author

Summary

  • Use when: the user asks to make or render charts with flint-chart, visualize tabular data, generate a ChartAssemblyInput, validate/render through MCP, or add Flint to a JS/TS project.
  • Author the semantic spec, transform data before Flint when needed, install/import Flint only when executable code is needed, and reserve backend-specific style tweaks for after compiling from Flint.

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from microsoft/flint-chart.

npx skills add microsoft/flint-chart

Browse all from microsoft/flint-chart

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 4.1K
License LICENSE
Default branch main
Open issues 13
Status Archived

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 34,034 B
  • docs SUMMARY.md 408 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 87 installs

SKILL.md

flint-chart: authoring and using a chart spec

What you produce (and what you do NOT)

Your output is the spec: the chartspec and semantictypes 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.

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

semantictypes + chartspec pieces. Do not install packages or write renderer code unless asked.

  • MCP chart output: if Flint MCP tools are available, **default to

createchartview** 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 renderchart (PNG/SVG) when the host has no App UI support or the user explicitly wants a static image. Use validatechart to check a spec without rendering, compilechart when the user wants backend-native JSON, and listchart_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 | SemanticAnnotation>;  // field → type ( ← you write this)
  chart_spec: {                               //                        ← you write this
    chartType: string;                        // e.g. "Scatter Plot"
    title?: string;                           // the headline — write one
    subtitle?: string;                        // what is measured, of whom, when, in what 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>; // field → readable axis/legend title
  theme_spec?: string | { extends: string; [key: string]: any }; // preset or preset override (Vega-Lite only)
}

How data gets bound

Use the binding mode that matches the runtime. Do not mix them.

  1. Direct MCP rendering: embed rows. When calling render_chart,

compilechart, or validatechart, 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.

  1. 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 FLINTMCPDISABLEFILEREFERENCE), 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.

  1. 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 semantictypes and chartspec 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 chartspec and semantictypes.

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:

  1. Author and validate the Flint ChartAssemblyInput.
  2. Render or inspect the Flint chart first, when possible.
  3. Call compile_chart with backend: "vegalite".
  4. Make the smallest necessary style/presentation edit to the returned

Vega-Lite spec.

  1. 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 renderchart; use renderchart only for Flint ChartAssemblyInput.

Write a headline

Set chartspec.title to the finding, in a sentence, and chartspec.subtitle to the reading of it — what is measured, of whom, when, in what units:

title:    "A pyramid that is no longer a pyramid"
subtitle: "United States population by age and sex, 2020, millions"

Jan, Cairo, Chrome name their own kind; 26, 5,300, 0.42 do not, and the headline is where they get named. Leave it out only where the chart is not read on its own — a sparkline in a cell, a tile under its own caption. Nothing breaks: with no headline to lean on, the compiler keeps the axis titles instead.

Visual themes (theme_spec)

Use one of two forms. Prefer a preset unless the user asks for a specific brand adjustment.

1. Use a preset

Call listthemes to choose an id, then place it beside chartspec:

{ "chart_spec": { ... }, "theme_spec": "economist" }
id what it is for
nyt Newsroom graphics: headline states the finding, values on the marks, series named at their ends.
economist Print weekly: compact, flat headline over a deck, units repeated down the ruler.
swiss International Typographic Style: strong grid structure, black typography, and a focused red accent.
nature Journal figure: small panel, axis titles with units, statistics beside the fit.
mckinsey Consulting deck: wide bands, every value printed, headline states the takeaway.
datawrapper Embedded web chart: narrow column, plain headline and deck, rule under the footer.
powerbi Dashboard tile: compact, legend to the right, latest point emphasised.
powerbi-light Light dashboard tile: white canvas, fine gridlines, and bright categorical color.
cartoon Playful illustration: warm paper, rounded type, bold outlines, and bright color.

2. Override a preset

Keep overrides narrow and state only what the user wants to change:

{
  "theme_spec": {
    "extends": "economist",
    "id": "our-brand",
    "ink": {
      "series": {
        "single": "#6b3fa0"
      }
    }
  }
}

Common simple overrides are ink.surface.canvas, ink.series.single, ink.series.categorical, type.headline.family, and layout.density ("compact", "normal", or "airy"). If replacing ink.series.categorical, also replace categoricalExtended so charts with many series keep the requested brand palette.

Do not copy an entire preset or invent theme keys. A theme controls presentation; fields, aggregation, filtering, and sorting still belong in the chart input. ThemeSpec currently affects Vega-Lite only.

Full reference: https://microsoft.github.io/flint-chart/#/documentation/theme-spec

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
"Calendar Heatmap" x, color x = date; color = daily value (summed per day); GitHub-style week × weekday grid
"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 centre-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 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: "Gauge",

"Funnel", "Treemap", "Sunburst", "Sankey", "Parallel Coordinates", "Graph", "Tree".

  • Chart.js supports: Scatter, Bubble, Bar, Grouped Bar, Stacked Bar,

Lollipop, Bump, Combo, Line, Area, Range Area, Pie, Doughnut, Histogram, Radar, Rose, Slope, Connected Scatter.

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.

Saying more than the type name

A field's entry can be an object instead of a string when the type alone understates what you know:

"semantic_types": {
  "anomaly": { "semanticType": "Quantity", "unit": "°C", "divergingMidpoint": 0 },
  "rating":  { "semanticType": "Score", "intrinsicDomain": [1, 5] }
}
  • unit — the unit or currency code: "USD", "°C", "kg".
  • intrinsicDomain — the field's own bounds, for bounded scales only: [1, 5]

for a five-star rating, [0, 100] for a percentage score. Not for open-ended measures.

  • divergingMidpoint — where the middle colour of a diverging scale sits.

Set it if you can tell what the reader is comparing against; leave it out if you can't.

  • sortOrder — the order the categories should appear in, when the order in

the data is not the one you want and it isn't alphabetical either: ["Low", "Medium", "High"]. For a handful of categories, not a long list.

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)
Area opacity 0.1–1 (0.7) Fill opacity
Scatter opacity 0.1–1 (1) Point opacity
Strip Plot stepWidth 10–100 (20) Jitter spread
Strip Plot pointSize 0–150 (0=auto) Point size
Strip Plot opacity 0–1 (0=auto) Point opacity
Histogram binCount 5–50 (10) Number of bins
Density Plot bandwidth 0.05–2 (0=auto) Kernel bandwidth
Pie Chart innerRadius 0–100 (0) Donut hole size (>0 → donut)
Pie / Rose sortSlices none \ descending \ ascending (none) Order wedges and their legend by slice value
Rose Chart alignment left \ center (left) Wedge alignment
Rose Chart padAngle 0–0.1 (0) Gap between slices
Lollipop dotSize 20–300 (80) Circle size (px)
Waterfall cornerRadius 0–8 (0) Round bar corners
Waterfall totals auto \ none \ first \ last \ both (auto) Which bars anchor to zero as totals (only when no Type column)
Waterfall showTextLabels boolean (false) Legacy spelling of showValueLabels; still accepted
Bar / Grouped Bar / Stacked Bar / Lollipop / Pyramid / Pie / Donut / Heatmap / Waterfall showValueLabels boolean Print the numbers on the marks. Works with or without a theme: unset, it follows the house's own habit at this density (and with no house named, stays off), so the default the compiler reports is always the honest one. Set it to overrule that for one chart. Reported inapplicable (and ignored) where the marks are too dense to carry readable numbers, or where the template already writes its own text, so it is never a control that does nothing. On a stacked bar each segment prints its own value in the middle of the segment (at the edge it would read as the running total); segments too thin to hold a line of text go unlabelled, and a normalized stack prints each segment's share rather than its raw value, since the share is what the length shows. The printed number is rounded to roughly three significant figures — with a k/M suffix once the values get long, and enough decimals that the smallest value in the series still says something — so a raw 3.14159265 lands as 3.14 and a series of 0.001 to 5000 reads at both ends. Rounding never goes so far that two marks of different size print the same number, or that a non-zero value prints as 0; where a house asked for a coarser precision than that, the digits are raised until the labels agree with the marks.
Regression regressionMethod linear \ log \ exp \ pow \ quad \ poly (linear) Fit method
Regression polyOrder 1–5 (3) Polynomial order (when poly)
Radar filled boolean (true) Fill the polygon
Radar fillOpacity 0–0.5 (0.15) Polygon fill opacity
Radar strokeWidth 0.5–4 (1.5) Line width
KPI Card behindThreshold 0–1 (0.5) Value/goal ratio cutoff for color
Map region us \ world \ auto (auto) Geographic scope
Map projection mercator \ equalEarth \ orthographic \ stereographic \ conic \ mollweide Map projection

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.

  • logScalex / logScaley (boolean) — force a logarithmic axis.
  • includeZerox / includeZeroy (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).
  • Use readable field titles: fielddisplaynames = { percentageOfCountries: "Percentage of countries" }.

Keep encodings bound to the real column name; Flint uses the display name for axis titles and legend headers.

  • 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 chartspec.baseSize = { width, height }. - Want a fixed slot it must fit inside → set chartspec.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 chartspec and semantictypes.

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 (Pie + innerRadius), 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).

{
  "data": { "values": [] },
  "semantic_types": {
    "vendor": "Category",
    "share": "Percentage"
  },
  "chart_spec": {
    "chartType": "Pie Chart",
    "encodings": {
      "size": { "field": "share" },
      "color": { "field": "vendor" }
    },
    "chartProperties": { "innerRadius": 60 }
  }
}

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:

  1. chartType is an exact registered name supported by the target backend.
  2. Every field referenced in encodings is a real column name.
  3. Every encoded field has an entry in semantic_types (specific type).
  4. Required channels for the chart type are present (e.g. Bullet→goal,

Candlestick→open/high/low/close, Pie→size+color).

  1. Any chartProperties keys are valid for that chart type and in range.
  2. You did not inline large data or hand-tune derived styling.
  3. 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.