[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M69AXMC64Z7NCCRZKFENZ1AE":3,"skill-same:01M69AXMC64Z7NCCRZKFENZ1AE":69},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":34,"publishedAt":40,"linkOnly":41,"markdownContent":42,"markdownBody":43,"frontMatter":44,"refName":45,"commitSha":46,"skillDirectory":47,"forksCount":48,"repositoryHtmlUrl":49,"repositoryHomepageUrl":50,"ownerHtmlUrl":51,"ownerBlogUrl":52,"ownerDescription":53,"updatedAt":54,"install":55,"resources":68},"01M69AXMC64Z7NCCRZKFENZ1AE","flint-chart-author",null,"Use when: the user asks to make or render charts with flint-chart, visualize tabular data, generate a ChartAssemblyInput, validate\u002Frender through MCP, or add Flint to a JS\u002FTS project. Author the semantic spec, transform data before Flint when needed, install\u002Fimport Flint only when executable code is needed, and reserve backend-specific style tweaks for after compiling from Flint.","microsoft\u002Fflint-chart","microsoft","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F6154722?v=4","01M698VYDXZ6JW5WH3CRV650R8","Microsoft","01M69AXB6SP1R3N8FK0Y1CNSDW",4571,"MIT","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fflint-chart\u002Fblob\u002Fmain\u002Fagent-skills\u002Fflint-chart-author\u002FSKILL.md",[18,23,26,29],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000013","分析与可视化",{"categoryNo":27,"categoryName":28,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":30,"categoryName":31,"taxonomyType":32,"taxonomyTypeDesc":33},"01JBSEEDCAT000000000000033","数据分析师","PROFESSION","适用职业",[35,36,37,38,39],"flint chart author","MCP","spec","visualize","validate","2026-11-10 10:12:30",false,"---\nname: flint-chart-author\ndescription: \"Use when: the user asks to make or render charts with flint-chart, visualize tabular data, generate a ChartAssemblyInput, validate\u002Frender through MCP, or add Flint to a JS\u002FTS project. Author the semantic spec, transform data before Flint when needed, install\u002Fimport Flint only when executable code is needed, and reserve backend-specific style tweaks for after compiling from Flint.\"\n---\n\n# flint-chart: authoring and using a chart spec\n\n## What you produce (and what you do NOT)\n\nYour output is the **spec**: the `chart_spec` and `semantic_types` of a\n`ChartAssemblyInput`. You reference data columns **by name**. The host\npasses the resulting input to `assembleVegaLite`, `assembleECharts`,\nor `assembleChartjs` to get a backend spec.\n\n**You write the input spec, not the output spec.** And critically:\n\n- **DO** emit `chart_spec` (chart type, channel→field mapping, properties)\n  and `semantic_types` (field → semantic type).\n- **DO** add `interaction_spec` when the user asks for behaviour (highlight,\n  legend toggle, pan and zoom, brush), asks for an interactive chart, or the\n  session's goal is to explore the data. List presets by name; see\n  \"Interactions\", and load the `flint-interaction-author` skill for an intent,\n  an interactive chart with nothing named, or a combination.\n- **Reference columns by name.** How `data` itself gets bound depends on\n  the situation — a URL, a host-side variable, or embedded rows (see \"How\n  data gets bound\"). Embedding is fine for small tables; just don't\n  re-serialize a *large* dataset by hand, since that risks truncation and\n  silent value corruption and wastes tokens.\n- **Transform data before Flint.** If the requested chart needs aggregation,\n  filtering, joins, pivots, derived columns, or long\u002Fwide reshaping beyond\n  Flint's built-in static-series fold, use a coding, notebook, SQL, or data tool\n  first. Then author the Flint spec against the transformed table.\n- **Style after Flint, only when needed.** Author structure in Flint. For a\n  presentation tweak Flint does not express (a reference line, annotation, or\n  shaded band), use the Vega-Lite escape hatch — see \"Post-Flint style\n  customization\". Never feed edited Vega-Lite JSON back to `render_chart`.\n\n## When the user wants more than a spec\n\nFirst decide which workflow the user is asking for:\n\n- **Spec authoring only:** return a `ChartAssemblyInput` or its\n  `semantic_types` + `chart_spec` pieces. Do not install packages or write\n  renderer code unless asked.\n- **MCP chart output:** if Flint MCP tools are available, **default to\n  `validate_chart` then `show`** whenever the user asks to see a chart.\n  `validate_chart` checks the spec and returns a report without drawing; fix\n  what it asks in the spec, then call `show({ \"chart\": … })` once with the\n  whole spec, which opens an interactive, live-rendered card with a\n  customization panel. The server keeps nothing between calls: the spec in\n  the conversation is the chart. Only fall back to `render_chart` (PNG\u002FSVG)\n  when the host has no App UI support or the user explicitly wants a static\n  image. Use `compile_chart` when the user wants backend-native JSON, and\n  `list_chart_types` when you need the supported chart catalog.\n- **DO** pass `updates` beside the input to `validate_chart` and `show` when the user\n  asks the chart to open in a state: rows to stand out, a note on a mark, a\n  framed range. It is a list of ChartUpdate layers, state not behaviour; the\n  shapes are in `flint:\u002F\u002Finteraction-skill` under \"Update the chart\". To change a\n  chart in a later turn, change its spec or `updates` and call `show` again: a\n  new card shows it, earlier cards stay as they were. The reader's edits in the\n  card's panel live on that card; its context gives the `chart_spec` that keeps\n  them, so pass it unless the user asked for something else.\n- **DO** pass `view: \"table\"` to `show` when the user wants to see\n  a chart's numbers: the card opens on the table of the rows the chart draws,\n  and the reader can switch to the chart. When the user wants records listed and\n  no chart at all, use `chartType: \"Table\"` instead.\n- **Project integration, only when the user asks for code:** add Flint to an\n  app, notebook, script, or agentic product, install\u002Fimport the library, and\n  call an assembler in code. Keep the same `ChartAssemblyInput` contract, then\n  let the host render the backend result.\n\nFor MCP clients, the server can run with `npx`:\n\n```bash\nnpx -y flint-chart-mcp\n```\n\nFor JavaScript or TypeScript projects, install Flint first and add only the\nrenderer peer dependencies needed by the backend you will render:\n\n```bash\nnpm install flint-chart\nnpm install vega vega-lite vega-embed  # browser Vega-Lite rendering\nnpm install echarts                    # ECharts rendering\nnpm install chart.js                   # Chart.js rendering\n```\n\nThen compile with the requested backend:\n\n```ts\nimport { assembleChartjs, assembleECharts, assembleVegaLite } from 'flint-chart';\n\nconst vegaLiteSpec = assembleVegaLite(input);\nconst echartsOption = assembleECharts(input);\nconst chartjsConfig = assembleChartjs(input);\n```\n\nPython support is planned for a later release. Until the PyPI package is\npublished, use the npm package or MCP server for released workflows.\n\n```ts\ninterface ChartAssemblyInput {\n  \u002F\u002F Bound by the HOST or by you, depending on the situation (see below).\n  data: { values: any[] } | { url: string };\n  semantic_types?: Record\u003Cstring, string | SemanticAnnotation>;  \u002F\u002F field → type ( ← you write this)\n  chart_spec: {                               \u002F\u002F                        ← you write this\n    chartType: string;                        \u002F\u002F e.g. \"Scatter Plot\"\n    title?: string;                           \u002F\u002F the headline — write one\n    subtitle?: string;                        \u002F\u002F what is measured, of whom, when, in what units\n    encodings: Record\u003Cstring, EncodingValue>; \u002F\u002F channel → { field, ... } (or array)\n    baseSize?: { width: number; height: number };    \u002F\u002F target layout size, default 400×320\n    canvasSize?: { width: number; height: number };  \u002F\u002F optional hard ceiling on stretch\n    chartProperties?: Record\u003Cstring, any>;    \u002F\u002F per-chart tuning (optional)\n  };\n  options?: Record\u003Cstring, any>;              \u002F\u002F global layout options (rarely needed)\n  field_display_names?: Record\u003Cstring, string>; \u002F\u002F field → readable axis\u002Flegend title\n  theme_spec?: string | { extends: string; [key: string]: any }; \u002F\u002F preset or preset override (Vega-Lite only)\n}\n```\n\n## How data gets bound\n\nUse the binding mode that matches the runtime. Do not mix them.\n\n1. **Direct MCP rendering: embed rows.** When calling `render_chart`,\n  `compile_chart`, or `validate_chart`, the tool arguments are JSON. If the\n  data is small or already transformed by another tool, pass it as\n  `data: { values: [...] }`. Do not pass runtime variable names in\n  MCP tool calls — the MCP server cannot see your local variables.\n2. **Direct MCP rendering: reference a local file.**\n  The `flint-chart-mcp` server can load `data: { url: \"...\" }` from local\n  `.json`, `.csv`, or `.tsv` files. By default any local file the agent can\n  name is readable (relative paths resolve against the working directory); a\n  hardened deployment may reject local file references entirely via\n  `--disable-file-reference` (or `FLINT_MCP_DISABLE_FILE_REFERENCE`), in which\n  case pass rows inline with `data.values`. Remote URL\n  fetching is disabled. If the data must be transformed first, use a\n  coding\u002Fdata tool to write a small prepared file, then reference that file.\n3. **Generated application or notebook code: bind runtime variables.** If the\n  user asks you to add Flint to code, write normal data-loading code first and\n  pass a real runtime value, e.g. `data: { values: rows }`, to\n  `assembleVegaLite`, `assembleECharts`, or `assembleChartjs`. This variable\n  pattern is for generated code, not for MCP tool calls.\n  - Parse CSV with a real parser (`d3.csvParse`, Papa Parse, `pandas`), never\n    `line.split(',')`: quoted values such as `\"Korea, Rep.\"` shift every later\n    column, and a shifted measure silently changes the chart (a wrong domain,\n    an unwanted log scale).\n  - Keep values as the data has them and let `semantic_types` read them: a\n    number column `year` typed `Year`, and a `YearMonth` column as `202501`,\n    `\"202501\"` or `\"2025-01\"`, are labelled as years and months; don't convert\n    them to date strings first.\n\nFor spec-only answers, return the `semantic_types` and `chart_spec` pieces and\nstate how the host should bind data. In the worked examples below, `data` is\nshown as `{ values: [] }` to signal \"host binds this\" — focus on `chart_spec`\nand `semantic_types`.\n\n## Data transformation before charting\n\nFlint is a chart compiler, not a data-wrangling layer. If the chart needs grouped\ntotals, time buckets, filters, joins, pivots, derived ratios, or a long-form\ntable, transform the data first with a host tool, then bind the prepared table\n(see \"How data gets bound\"). Pick semantic types and channels for the transformed\ncolumns, not for columns that no longer exist.\n\n**Sanity-read the values first — don't chart blind.** Inspect the actual data\nwith your data tool (distinct values per category column, min\u002Fmax per measure),\nnot just the column names, and watch for:\n\n- **Embedded totals.** A category column may mix an aggregate level with its\n  parts (e.g. `all` alongside `cage-free`\u002F`caged`, or a `Total` region). Charting\n  the total with its parts double-counts and flattens the parts — keep one or the\n  other on a stacked\u002Fgrouped\u002Fcolored channel, not both.\n- **Units.** `Percentage` accepts either a fraction (0–1) or a percent (0–100):\n  Flint reads which from the values and formats both as `%`. Don't rescale a\n  column for Flint, and don't scale it twice.\n- **One real entity.** If your breakdown column has a single distinct value, the\n  per-group chart collapses to one mark — the intended breakdown is likely a\n  different column.\n\n## Post-Flint style customization\n\nStay at the Flint level for structure (data, chart type, channels, transforms,\nsizing, properties) — Flint specs stay portable and regenerate safely. Drop to\nbackend JSON only after a valid Flint chart exists, and only for a narrow\npresentation change Flint does not expose (exact axis\u002Flegend\u002Fmark styling,\ntitles, annotations, reference lines, layout polish). Never use it to change the\ndata, chart type, field mappings, or transforms — fix those upstream.\n\nFor a Vega-Lite-specific style tweak:\n\n1. Author and validate the Flint `ChartAssemblyInput`.\n2. Render or inspect the Flint chart first, when possible.\n3. Call `compile_chart` with `backend: \"vegalite\"`.\n4. Make the smallest necessary style\u002Fpresentation edit to the returned\n  Vega-Lite spec.\n5. Render the edited spec in the host environment with a Vega-Lite renderer.\n\nThis edited Vega-Lite spec is no longer a portable Flint spec. Do not send it to\n`render_chart`; use `render_chart` only for Flint `ChartAssemblyInput`.\n\n## Write a headline\n\nSet `chart_spec.title` to the finding, in a sentence, and `chart_spec.subtitle`\nto the reading of it — what is measured, of whom, when, in what units:\n\n```\ntitle:    \"A pyramid that is no longer a pyramid\"\nsubtitle: \"United States population by age and sex, 2020, millions\"\n```\n\nAdd `chart_spec.label` only when the chart sits in a numbered sequence or\nunder a section name: `label: \"Figure 2\"`. It is drawn above the headline in\nthe theme's kicker style.\n\n`chart_spec.source` is optional. Add it only when the user names the source or\nasks for a source line, or the data itself states where it comes from:\n`source: \"UN World Population Prospects 2024\"` (or `{ text, href }` to link\nit). Never invent or guess one. The theme decides how it reads (\"Source:\",\nside, rule, credit); a chart without one draws no footer.\n\n`chart_spec.chrome` is the house decoration the theme draws around the chart:\nheader rules, tabs, wordmarks, logos, the footer rule and the house credit. It is\nnot the chart's own title, label or source. The theme draws it by default; leave\n`chrome` out unless the user asks to drop it: `chrome: false`\ndrops all of it; `chrome: { logos: false }` (or `header`, `footer: false`) drops\none part. Without logos a credit keeps its words.\n\n`Jan`, `Cairo`, `Chrome` name their own kind; `26`, `5,300`, `0.42` do not, and\nthe headline is where they get named. Leave it out only where the chart is not\nread on its own — a sparkline in a cell, a tile under its own caption. Nothing\nbreaks: with no headline to lean on, the compiler keeps the axis titles instead.\n\n## Visual themes (`theme_spec`)\n\nUse one of two forms. Prefer a preset unless the user asks for a specific\nbrand adjustment.\n\n### 1. Use a preset\n\nCall `list_themes` to choose an id, then place it beside `chart_spec`:\n\n```json\n{ \"chart_spec\": { ... }, \"theme_spec\": \"economist\" }\n```\n\n| id | what it is for |\n| --- | --- |\n| `nyt` | Newsroom graphics: headline states the finding, values on the marks, series named at their ends. |\n| `economist` | Print weekly: compact, flat headline over a deck, units repeated down the ruler. |\n| `swiss` | International Typographic Style: strong grid structure, black typography, and a focused red accent. |\n| `nature` | Journal figure: small panel, axis titles with units, statistics beside the fit. |\n| `mckinsey` | Consulting deck: wide bands, every value printed, headline states the takeaway. |\n| `datawrapper` | Embedded web chart: narrow column, plain headline and deck, rule under the footer. |\n| `powerbi` | Dashboard tile: compact, legend to the right, latest point emphasised. |\n| `powerbi-light` | Light dashboard tile: white canvas, fine gridlines, and bright categorical color. |\n| `cartoon` | Playful illustration: warm paper, rounded type, bold outlines, and bright color. |\n| `apple` | App-interface chart: light labels, trailing value axis, symbols beside color. |\n| `de-stijl` | De Stijl composition: primary planes in heavy black lines, steps not slopes, title under the picture. |\n| `bbc` | BBC News graphic: big bold headline, gridlines only, heavy zero rule, footer rule with the source. |\n| `material` | Material Design 3 product chart: purple-led tonal palette, Roboto type, light gridlines, rounded bars. |\n\n### 2. Override a preset\n\nKeep overrides narrow and state only what the user wants to change:\n\n```json\n{\n  \"theme_spec\": {\n    \"extends\": \"economist\",\n    \"id\": \"our-brand\",\n    \"ink\": {\n      \"series\": {\n        \"single\": \"#6b3fa0\"\n      }\n    }\n  }\n}\n```\n\nCommon simple overrides are `ink.surface.canvas`, `ink.series.single`,\n`ink.series.categorical`, `type.headline.family`, and `layout.density`\n(`\"compact\"`, `\"normal\"`, or `\"airy\"`). If replacing\n`ink.series.categorical`, also replace `categoricalExtended` so charts with\nmany series keep the requested brand palette.\n\nDo not copy an entire preset or invent theme keys. A theme controls\npresentation; fields, aggregation, filtering, and sorting still belong in the\nchart input. ThemeSpec applies to Vega-Lite and Plotly; ECharts, Chart.js, and Excel ignore it.\n\nFull reference:\nhttps:\u002F\u002Fmicrosoft.github.io\u002Fflint-chart\u002F#\u002Fdocumentation\u002Ftheme-spec\n\n## Interactions (`interaction_spec`)\n\nAdd `interaction_spec` beside `chart_spec` when the user asks for behaviour,\nor when the goal of the session is to explore or analyse the data. A static\nimage never needs it.\n\n```json\n{\n  \"chart_spec\": { \"chartType\": \"Line Chart\", \"encodings\": { \"x\": \"date\", \"y\": \"value\", \"color\": \"series\" } },\n  \"interaction_spec\": { \"interactions\": [ { \"type\": \"brush-x\" } ] }\n}\n```\n\nEvery entry is `{ \"type\": \u003Cpreset>, \"options\": { ... } }`, one per behaviour.\nTake the presets and their options for the chart type from\n`list_interaction_presets` with `chartType`; never invent one. Options nest under\n`options`; `id` sits on the entry.\n\nWhich presets answer a request, how they combine, and what the warnings mean\nis the `flint-interaction-author` skill (`flint:\u002F\u002Finteraction-skill` on the\nMCP server). Load it for anything beyond a single named gesture: an intent\n(explore, compare, analyse, find outliers), \"make it interactive\" with\nnothing named, several presets together, behaviour on a chart that already\nexists, or an update to a chart on screen.\n\nFull reference:\nhttps:\u002F\u002Fmicrosoft.github.io\u002Fflint-chart\u002F#\u002Fdocumentation\u002Finteraction-spec\n\n## Step 1 — pick `chartType`\n\nUse one of the registered names **exactly**. Vega-Lite is the default and\nbroadest backend; the table below lists each Vega-Lite chart type, the\nchannels it accepts, and its tuning properties (see \"Chart-level\nproperties\"). Required channels are noted.\n\n| chartType | Channels | Notes \u002F required |\n|---|---|---|\n| `\"Scatter Plot\"` | x, y, color, size, shape, detail, opacity, column, row | x + y required; `detail` carries a field (e.g. `country`) on each point without a visual channel, for tooltips and linking |\n| `\"Regression\"` | x, y, size, color, column, row | scatter + fit line; props `regressionMethod`, `polyOrder` |\n| `\"Connected Scatter Plot\"` | x, y, order, color, detail, column, row | x + y required; `order` = connection sequence (time\u002Findex), so the line traces a trajectory and may self-cross |\n| `\"Ranged Dot Plot\"` | x, y, color | dumbbell of two x per category |\n| `\"Strip Plot\"` | x, y, color, size, column, row | jittered points; props `stepWidth`, `pointSize`, `opacity` |\n| `\"Bar Chart\"` | x, y, color, opacity, column, row | one discrete + one measure; prop `cornerRadius` |\n| `\"Grouped Bar Chart\"` | x, y, group, column, row | `group` = the clustering category; prop `dodge` |\n| `\"Stacked Bar Chart\"` | x, y, color, column, row | prop `stackMode` |\n| `\"Pyramid Chart\"` | x, y, color | diverging horizontal bars |\n| `\"Lollipop Chart\"` | x, y, color, column, row | prop `dotSize` |\n| `\"Waterfall Chart\"` | x, y, color, column, row | `color` = Type column, values `start`\u002F`delta`\u002F`end` only; omit it for auto sign coloring; props `cornerRadius`, `totals` |\n| `\"Gantt Chart\"` | y, x, x2, color, detail, column, row | x = start, x2 = end |\n| `\"Bullet Chart\"` | y, x, goal, color, column, row | `goal` required (target) |\n| `\"Histogram\"` | x, color, column, row | x = measure to bin; prop `binCount` |\n| `\"Boxplot\"` | x, y, color, opacity, column, row | category + measure; props `whiskerMethod`, `showOutliers`, `dodge` |\n| `\"ECDF Plot\"` | x, color, detail, column, row | x = measure; cumulative distribution (step line); prop `showPoints` |\n| `\"Heatmap\"` | x, y, color, column, row | color = the measure |\n| `\"Calendar Heatmap\"` | x, color | x = date; color = daily value (summed per day); GitHub-style week × weekday grid |\n| `\"Line Chart\"` | x, y, color, strokeDash, detail, opacity, column, row | props `interpolate`, `showPoints` |\n| `\"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` |\n| `\"Bump Chart\"` | x, y, color, detail, column, row | rank-over-time lines |\n| `\"Slope Chart\"` | x, y, color, detail, column, row | two-period value change; straight segments + end points, one line per category |\n| `\"Area Chart\"` | x, y, color, opacity, column, row | props `interpolate`, `opacity`, `stackMode` |\n| `\"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) |\n| `\"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 |\n| `\"Streamgraph\"` | x, y, color, column, row | centre-stacked areas |\n| `\"Density Plot\"` | x, color, column, row | props `bandwidth`, `stackMode` (series stack; `layered` overlaps them), `opacity` |\n| `\"Ridgeline Plot\"` | x, y, color | x = measure; `color` (or a discrete `y`) names the ridges, each the KDE of x on its own baseline; a measure `y` draws each series' curve instead; props `overlap`, `order`, `tailCut`, `colorBy`, `perspective` (plans unset `skew`, `tilt`, `overlap` from the ridge count so the depth takes about 40% of the plot), `skew`, `tilt`, `valueMax`, `markAt` (`true` marks the x ticks, or a list of x values; each ridge's value is dotted and labelled, joined across ridges), `opacity`, `interpolate`, `bandwidth`; named ridges share one ink unless `colorBy` asks otherwise; a numeric `color` keeps a gradient key; **Vega-Lite only** |\n| `\"Marginal Plot\"` | x, y, color, size, shape, opacity | the plot follows the axis types (measure × measure = scatter, category × measure = strip, category × category = heatmap of counts) with each axis's distribution along it (density, histogram or boxplot on a measure axis, a bar of counts on a category axis, split by `color`; density margins layer by default); props `marginal`, `stackMode`, `bandwidth`, `binCount`, `whiskerMethod`, `showOutliers`; **Vega-Lite only** |\n| `\"Pie Chart\"` | size, color, column, row | `size` = slice value (→ angle), `color` = category; props `innerRadius`, `sortSlices` |\n| `\"Rose Chart\"` | x, y, color, column, row | polar bars; props `alignment`, `padAngle`, `sortSlices` |\n| `\"Radar Chart\"` | x, y, color, column, row | props `filled`, `fillOpacity`, `strokeWidth` |\n| `\"Candlestick Chart\"` | x, open, high, low, close, column, row | OHLC all required |\n| `\"Bar Table\"` | y, x, color, column, row | compact bars + value labels |\n| `\"KPI Card\"` | metric, value, goal | big-number tile; prop `behindThreshold` |\n| `\"Table\"` | none (`encodings: {}`) | the rows as given, one column per field in row order; shape `data.values` to the rows and fields to list, name columns with `field_display_names`; numbers and dates print by `semantic_types`; receives links and `updates`, sends no selection |\n| `\"Map\"` | longitude, latitude, color, size, opacity | bubble map; props `region`, `projection` |\n| `\"Choropleth\"` | id, color, detail | `id` = region key (see **Maps** below); props `region` (`\"world\"`\u002F`\"us\"`), `level` (US: `\"state\"`\u002F`\"county\"`\u002F`\"auto\"`), `projection` (world) |\n\n**Maps: what `id` accepts.** Flint joins the key to its own base map; you never\nsupply geometry or a lookup. A **world** map takes country names (\"India\",\n\"DR Congo\", \"United Kingdom\"), ISO 3166 alpha-2 \u002F alpha-3 codes (\"IN\", \"IND\"),\nor ISO numeric codes (356), all in one column. A **US** map takes state names,\nUSPS codes (\"CA\") or FIPS codes (6), and county FIPS (6037) with\n`level: \"county\"`. Declare the key `Country` or `State` in `semantic_types`\n(or set `chartProperties.region`) whenever codes could collide: \"CA\", \"IN\",\n\"Georgia\", or small numbers. A key that matches no region is not drawn. Map\nmarks carry only `id`, `color` and `detail`: to link a map with charts keyed by\na name column, encode that column as `id` (names resolve too) or put it on\n`detail` (e.g. `id: \"map_id\", detail: \"country\"`), which also labels the tooltip.\n\n**Donut chart:** use `\"Pie Chart\"` with `chartProperties.innerRadius > 0`.\n\n**Choosing a bar chart (most common mix-up).** All three take one discrete\ncategory on `x` (or `y`) plus one measure. They differ in how a **second**\ncategory is shown — and each reads that second category from a **different\nchannel**:\n\n- `\"Bar Chart\"` — use for a single series. When multiple rows share an `x`, a\n  second category on `color` produces stacked segments. For side-by-side bars,\n  use `\"Grouped Bar Chart\"` with the second category on `group`.\n- `\"Stacked Bar Chart\"` — second category on `color`, drawn as **stacked**\n  segments within each bar (totals matter). Tune with `stackMode`\n  (`stacked` \u002F `normalize` \u002F `layered`).\n- `\"Grouped Bar Chart\"` — second category on the **`group`** channel, drawn as\n  **side-by-side (dodged)** bars within each `x` cluster (compare values\n  directly). Put the clustering category on `group`, *not* `color`.\n\nRule of thumb: comparing parts-to-whole → Stacked; comparing values\nside-by-side → Grouped (use `group`); single series → Bar.\n\n**Waterfall color is a special \"Type\" column, not a free category.** On a\n`\"Waterfall Chart\"` the `color` channel is reserved for a *type* field whose\nvalues are literally `start`, `delta`, and `end` — it drives which bars anchor\nto zero, not an arbitrary grouping. Do **not** bind `color` to an\n`Increase`\u002F`Decrease` (or up\u002Fdown, gain\u002Floss) category: the up\u002Fdown direction is\nalready derived from the **sign** of the `y` value and colored automatically\n(green up \u002F red down). For the common case, **omit `color` entirely** and let\nFlint infer the start\u002Fdelta\u002Fend and per-bar sign coloring. To force which bars\nare anchored totals, use the `totals` property (`first`\u002F`last`\u002F`both`), not a\ncolor field. Only bind `color` when you genuinely have a `start`\u002F`delta`\u002F`end`\ntype column.\n\n**Backend coverage.** Vega-Lite supports all of the above. Other backends\nsupport a subset (verify if targeting a non-VL backend):\n\n- **ECharts** adds: `\"Gauge\"`,\n  `\"Funnel\"`, `\"Treemap\"`, `\"Sunburst\"`, `\"Sankey\"`,\n  `\"Parallel Coordinates\"`, `\"Graph\"`, `\"Tree\"`.\n- **Chart.js** supports: Scatter, Bubble, Bar, Grouped Bar, Stacked Bar,\n  Lollipop, Bump, Combo, Line, Area, Range Area, Pie, Doughnut, Histogram,\n  Radar, Rose, Slope, Connected Scatter.\n\nYou do not need to call the library or inspect its source to author the\ninput — pick from this table.\n\n## Step 2 — map fields to channels\n\nEach channel maps to an **encoding object** `{ field, ... }` (or a bare\nstring shorthand, expanded to `{ field: \"\u003Cstring>\" }`):\n\n```json\n\"encodings\": {\n  \"x\": { \"field\": \"weight\" },\n  \"y\": \"mpg\",\n  \"color\": { \"field\": \"origin\" }\n}\n```\n\n**Encoding object fields** (all optional except `field`):\n\n| Field | Values | Purpose |\n|---|---|---|\n| `field` | column name | Bind the channel to a data column |\n| `type` | `quantitative`, `nominal`, `ordinal`, `temporal` | Override the inferred encoding type (rarely needed) |\n| `aggregate` | `count`, `sum`, `average`, `mean` | Force an aggregation on a measure channel. A heatmap that gets several rows per cell aggregates its colour on its own (sum for additive types such as `Quantity` or `Profit`, average otherwise) and warns; set `aggregate` to choose |\n| `sortOrder` | `ascending`, `descending` | Sort direction; in Vega-Lite, direction alone preserves\u002Freverses the semantic sequence |\n| `sortBy` | mapped `\"x\"`, `\"y\"`, `\"color\"`; Vega-Lite also accepts a data field or JSON-array string | Explicit category ordering; see the Vega-Lite contract below |\n| `scheme` | Vega scheme name (e.g. `viridis`, `redblue`), or a list of stops low end first (`[\"#c0392b\", \"#f2f2f2\", \"#2e75b6\"]`) | Color scheme for the `color` channel; wins over the theme's ramp |\n| `reverse` | `true` \u002F `false` | `color` only: run the color scale the other way round |\n| `mid` | number | `color` only: where a diverging scale pivots, for this chart (the field's `divergingMidpoint`) |\n| `domain` | `[low, high]` | `color` only: the measure extent the ramp spans, in place of the data's |\n| `bins` | integer ≥ 2, or `0` | `color` only: quantize a continuous measure into that many steps; `0` keeps the ramp continuous |\n| `colors` | `{ value: color }` | `color` only: colors bound to category values, for this chart (the field's `colors`) |\n| `title` | a string, or `null` | The name the channel's axis or key prints in place of the field's; `null` prints none |\n\nYou usually don't need `type`, `aggregate`, or `sortOrder` — they're\ninferred from the semantic type. Set them only with specific intent.\n\n**Vega-Lite sorting.** Omit sorting for canonical Month\u002FDay\u002FQuarter order.\n`sortOrder` alone follows that semantic sequence (descending reverses it),\nnot alphabetical month names. Without a semantic sequence, direction alone\nuses the field's native value order.\n\n- `sortBy: \"month_number\"` sorts by that existing data field, ascending by\n  default. Multiple rows per category use the minimum sort-field value.\n- `sortBy: \"y\"` sorts by the mapped channel; cross-channel sorts default to\n  descending, while self-channel sorts default to ascending. `x`, `y`, and\n  `color` are reserved channel names and must be mapped.\n- `sortBy: \"[\\\"Mar\\\",\\\"Jan\\\",\\\"Feb\\\"]\"` supplies an explicit category order;\n  `sortOrder: \"descending\"` reverses it. Use a JSON-array **string**, not an array\n  or a Vega-Lite sort object. Members must be strings, numbers, or booleans.\n- For explicit lexical ordering of month labels, sort by the category field\n  itself: `{ \"field\": \"month\", \"sortBy\": \"month\", \"sortOrder\": \"ascending\" }`.\n\nUnknown fields, unmapped channels, invalid directions, and malformed sort\narrays are errors, not fallback orders. Raw field-name sorting is currently\nsupported by the Vega-Lite backend; for other backends use their supported\nchannel-sort controls. Validate inputs against the selected backend.\n\n**Multi-series (wide → long).** To plot several measure columns as series,\npass an **array** on `x` or `y` (only those two channels). The library\nfolds them into long form and synthesizes a series\u002Flegend field:\n\n```json\n\"encodings\": { \"x\": { \"field\": \"month\" }, \"y\": [\"sales\", \"profit\"] }\n```\n\nAll array fields must be quantitative, and you cannot also bind `color`\nwhen using the array form (the fold owns the color\u002Flegend). This is the\n**only** built-in reshape — there is no `transforms`\u002F`fold` property. For any\nother shape (long↔wide, an aggregate the encodings can't express, a derived\ncolumn, a pivot, a join), reshape the data first with a host tool — pandas\u002Fpolars,\nArquero\u002F`Array.map`\u002FSQL, or a data\u002FMCP tool — and pass the result as\n`data.values`. If you have no way to transform, surface the gap to the developer\nrather than inventing a transform property that does not exist.\n\n## Step 3 — annotate with semantic types\n\n**This is the most important step.** Semantic types drive all downstream\ndecisions — formatting, zero baseline, color scheme, scale direction, and\nmore. Pick the most specific type for each field. Full registered set:\n\n| Family | Semantic types |\n|---|---|\n| Temporal (point) | `DateTime`, `Date`, `Time`, `Timestamp` |\n| Temporal (granule) | `Year`, `Quarter`, `Month`, `Week`, `Day`, `Hour`, `YearMonth`, `YearQuarter`, `YearWeek`, `Decade` |\n| Temporal (span) | `Duration` |\n| Measure (amount) | `Amount`, `Price`, `Quantity`, `Count`, `Number` |\n| Measure (proportion) | `Percentage` |\n| Measure (signed\u002Fdiverging) | `Profit`, `PercentageChange`, `Sentiment`, `Correlation` |\n| Measure (physical) | `Temperature` |\n| Discrete \u002F rank | `Rank`, `Score`, `ID` |\n| Geographic (coord) | `Latitude`, `Longitude` |\n| Geographic (place) | `Country`, `State`, `City`, `Region`, `Address`, `ZipCode` |\n| Categorical | `Category`, `Name`, `Status`, `Boolean`, `Direction`, `Range` |\n| Fallback | `Unknown` |\n\nA temporal type also tells Flint how to read the values: a `YearMonth` accepts `202501`, `2025-01`, `2025\u002F1`, `01\u002F2025`, `2025M01`, `Jan 2025`, `2025-Jul`, `July-2025` or `Jul '25`; a `Date` accepts `20250115`, `2025\u002F01\u002F15`, `Jan 15, 2025` or `15-Jan-2025`. Pass the values as they are; an untyped field is never reinterpreted.\n\nWhat choosing well gets you (automatically):\n\n- `Price` \u002F `Amount` → currency formatting, zero baseline, sequential color\n- `Temperature` → diverging color scheme, no forced zero baseline\n- `Correlation` → fixed `[-1, 1]` diverging domain\n- `Rank` → reversed axis (1 on top), discrete color\n- `Date` \u002F `DateTime` → temporal axis with auto-granularity formatting\n- `Percentage` → percent formatting, 0–100 domain awareness\n\nIf you don't know, use `Quantity` for numbers, `Category` for strings,\n`Date`\u002F`DateTime` for date-shaped values. Do **not** invent type names.\n\n### Saying more than the type name\n\nA field's entry can be an object instead of a string when the type alone\nunderstates what you know:\n\n```json\n\"semantic_types\": {\n  \"anomaly\": { \"semanticType\": \"Quantity\", \"unit\": \"°C\", \"divergingMidpoint\": 0 },\n  \"rating\":  { \"semanticType\": \"Score\", \"intrinsicDomain\": [1, 5] }\n}\n```\n\n- `unit` — an optional assertion that authorizes Flint to display a unit. Add\n  it only when the data or surrounding context establishes the measurement\n  and seeing it materially changes how a reader interprets the number. A type\n  such as `Duration`, a field name such as `life_expectancy`, or values that\n  merely look plausible are not enough evidence by themselves.\n  - Prefer canonical codes: `\"USD\"`, `\"°C\"`, `\"kg\"`, `\"km\u002Fh\"`, `\"min\"`.\n  - Conventional compact units are normalized and may appear beside values\n    (`USD` → `$`, `hours` → `hr`).\n  - Lexical units such as `\"years\"` are stated once beside the field name as\n    `field (years)`, not repeated after every value.\n  - Do not put explanatory phrases in `unit`. Put qualifications such as\n    `\"per working-age resident\"` or `\"constant 2024 prices\"` in the subtitle.\n  - Omit `unit` when its meaning, scale, or denominator is uncertain. Flint\n    does not infer a visible unit from the semantic type or field name.\n- `intrinsicDomain` — the field's own bounds, for bounded scales only: `[1, 5]`\n  for a five-star rating, `[0, 100]` for a percentage score. Not for\n  open-ended measures.\n- `divergingMidpoint` — where the middle colour of a diverging scale sits.\n  Set it if you can tell what the reader is comparing against; leave it out if\n  you can't.\n- `sortOrder` — the order the categories should appear in, when the order in\n  the data is not the one you want and it isn't alphabetical either:\n  `[\"Low\", \"Medium\", \"High\"]`. For a handful of categories, not a long list.\n- `colors` — colours that belong to particular values, when the data has a\n  convention the reader expects: `{ \"DFL\": \"#0793AB\", \"Republican\": \"#A1000F\" }`.\n  Matched on the value (case-insensitive); values left out take the palette,\n  which then skips these colours. Only for real conventions (parties, brands,\n  asset classes), not to restyle a chart — the theme owns the palette.\n- `polarity` — `\"higher-is-better\"` or `\"lower-is-better\"`: which end of a\n  diverging colour is the alarming one. Signed measures (`Profit`,\n  `PercentageChange`) read as higher-is-better on their own; set\n  `\"lower-is-better\"` for costs, errors, churn or delays, so their highs, not\n  their lows, take the warm end, under any theme.\n\n## Step 4 — write the spec\n\nPut the three steps into one `ChartAssemblyInput`, with the data bound as in\n\"How data gets bound\" and a headline as in \"Write a headline\":\n\n```json\n{\n  \"data\": { \"url\": \"sales.csv\" },\n  \"semantic_types\": { \"month\": \"YearMonth\", \"revenue\": \"Amount\", \"region\": \"Region\" },\n  \"chart_spec\": {\n    \"chartType\": \"Line Chart\",\n    \"title\": \"Europe climbed every month after the April dip\",\n    \"subtitle\": \"Revenue by region, $m\",\n    \"encodings\": { \"x\": { \"field\": \"month\" }, \"y\": { \"field\": \"revenue\" }, \"color\": { \"field\": \"region\" } }\n  },\n  \"theme_spec\": \"economist\"\n}\n```\n\nAdd `theme_spec`, `chartProperties`, `interaction_spec` or `updates` only when\nthe request asks for them. With the MCP tools, check it with `validate_chart`,\nfix what the report asks in the spec, then pass the whole spec to `show` as\n`chart`. Without them, run the \"Validation checklist\".\n\n## Chart-level properties (`chartProperties`)\n\n`chartProperties` is an optional per-chart tuning map. Set a property only\nwhen the user asks for that behavior — defaults are sensible. These are\n**design choices**, not styling overrides (colors\u002Ffonts\u002Fticks are still\nderived). Values are clamped to the ranges shown.\n\n| Chart type | Property | Type \u002F range (default) | Effect |\n|---|---|---|---|\n| Bar Chart | `cornerRadius` | 0–15 (0) | Round bar corners (px) |\n| Area \u002F Stacked Bar | `stackMode` | `stacked` \\| `normalize` \\| `center` \\| `layered` (unset) | Stacking behavior; `normalize` = 100%, `center` = streamgraph |\n| Density Plot | `stackMode` | `normalize` \\| `layered` (unset = stacked) | `layered` overlaps the series, translucent |\n| Density Plot | `opacity` | 0.1–1 (0.7) | Layered series opacity |\n| Marginal Plot | `stackMode` | `stacked` (unset = layered) | Stack the density margins instead of layering them |\n| Grouped Bar \u002F Boxplot | `dodge` | `auto` \\| `local` \\| `global` (`auto`) | `local` compacts sparse groups per category; `global` preserves aligned group lanes; leave `auto` unless the user requests one |\n| Line \u002F Area \u002F Sparkline | `interpolate` | `linear` \\| `monotone` \\| `step` \\| `step-before` \\| `step-after` \\| `basis` \\| `cardinal` \\| `catmull-rom` (`linear`) | Curve shape |\n| Line \u002F ECDF Plot | `showPoints` | boolean (false) | Draw point markers on the line |\n| Sparkline | `baseline` | `mean` \\| `zero` \\| `median` \\| `none` (`mean`) | Reference line per spark row |\n| Sparkline | `trendWidth` | 80–600 (240) | Mini line-plot width (px) |\n| Boxplot | `whiskerMethod` | `iqr` \\| `minmax` (`iqr`) | Whisker rule (Tukey 1.5×IQR vs min–max) |\n| Boxplot | `showOutliers` | boolean (true) | Show outlier points (Tukey only) |\n| Area | `opacity` | 0.1–1 (0.7) | Fill opacity |\n| Scatter | `opacity` | 0.1–1 (1) | Point opacity |\n| Strip Plot | `stepWidth` | 10–100 (20) | Jitter spread |\n| Strip Plot | `pointSize` | 0–150 (0=auto) | Point size |\n| Strip Plot | `opacity` | 0–1 (0=auto) | Point opacity |\n| Histogram | `binCount` | 5–50 (10) | Number of bins |\n| Density Plot \u002F Ridgeline \u002F Marginal Plot | `bandwidth` | 0.05–2 (0=auto) | Kernel bandwidth |\n| Ridgeline | `overlap` | 0.5–4 (1.8) | How many baselines up the tallest ridge reaches |\n| Ridgeline | `order` | `mean`, `reverse` (unset = as given; a quantitative series lowest at the front) | Highest mean on top, or reversed |\n| Ridgeline | `tailCut` | 0–0.2 (0) | Trim each ridge to where its density is at least this share of its peak |\n| Ridgeline | `colorBy` | `series`, `x` (unset = one colour) | Named ridges share one ink by default; `series` colours each ridge, `x` fills each with a ramp along x |\n| Ridgeline | `perspective` | boolean (false) | Stand the ridges on a floor seen in perspective: `skew`, `tilt`, `overlap` left unset are planned so the depth takes about 40% of the plot's width and more of its height as ridges are added; the depth names, edge and value ticks give way (along the axis, then level, then every n-th) before they touch |\n| Ridgeline | `skew` | 0–0.4 (0) | Perspective: shift the back ridges left by this share of the x range |\n| Ridgeline | `tilt` | 0–6 (0) | Perspective: the x axis rises this many baselines across its range, labels following its edge |\n| Ridgeline | `opacity` | 0.1–1 (0.5) | Ridge opacity, as the Range Area Chart; solid in perspective unless set |\n| Ridgeline | `valueMax` | number (0 = tallest peak) | Perspective: the value the full ridge height stands for, read on the back-left value axis |\n| Ridgeline | `markAt` | boolean \\| number[] | Mark and label every ridge's value at the x ticks inside the range (`true`) or at these x; a dashed line joins the marks at one x across the ridges |\n| Marginal Plot | `marginal` | `density` \\| `histogram` \\| `boxplot` (`density`) | What each margin draws along a measure axis; a category axis gets a bar of counts |\n| Pie Chart | `innerRadius` | 0–100 (0) | Donut hole size (>0 → donut) |\n| Pie \u002F Rose | `sortSlices` | `none` \\| `descending` \\| `ascending` (`none`) | Order wedges and their legend by slice value |\n| Rose Chart | `alignment` | `left` \\| `center` (`left`) | Wedge alignment |\n| Rose Chart | `padAngle` | 0–0.1 (0) | Gap between slices |\n| Lollipop | `dotSize` | 20–300 (80) | Circle size (px) |\n| Waterfall | `cornerRadius` | 0–8 (0) | Round bar corners |\n| Waterfall | `totals` | `auto` \\| `none` \\| `first` \\| `last` \\| `both` (`auto`) | Which bars anchor to zero as totals (only when no Type column) |\n| Waterfall | `showTextLabels` | boolean (false) | Legacy spelling of `showValueLabels`; still accepted |\n| Bar \u002F Grouped Bar \u002F Stacked Bar \u002F Lollipop \u002F Pyramid \u002F Pie \u002F Donut \u002F Heatmap \u002F 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\u002FM 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. |\n| Regression | `regressionMethod` | `linear` \\| `log` \\| `exp` \\| `pow` \\| `quad` \\| `poly` (`linear`) | Fit method |\n| Regression | `polyOrder` | 1–5 (3) | Polynomial order (when `poly`) |\n| Radar | `filled` | boolean (true) | Fill the polygon |\n| Radar | `fillOpacity` | 0–0.5 (0.15) | Polygon fill opacity |\n| Radar | `strokeWidth` | 0.5–4 (1.5) | Line width |\n| KPI Card | `behindThreshold` | 0–1 (0.5) | Value\u002Fgoal ratio cutoff for color |\n| Map | `region` | `us` \\| `world` \\| `auto` (`auto`) | Geographic scope |\n| Map | `projection` | `mercator` \\| `equalEarth` \\| `orthographic` \\| `stereographic` \\| `conic` \\| `mollweide` | Map projection |\n\n**Cross-cutting properties** (apply to position\u002Ffaceted charts when\nrelevant; set only to force non-default behavior):\n\n- `independentYAxis` (boolean) — faceted charts: give each panel its own\n  y-scale.\n- `logScale_x` \u002F `logScale_y` (boolean) — force a logarithmic axis.\n- `includeZero_x` \u002F `includeZero_y` (boolean) — force the axis to include 0.\n- `xAxisType` \u002F `yAxisType` (`temporal` | `nominal`) — force a temporal\n  field to render as discrete bands (or vice-versa).\n- `labelOverflow` (list of steps) — the path x category names that do not\n  fit their band take. Leave it out: names wrap, then stand upright (trimmed\n  if need be), every name printed; a run of times, dates, years or ranks\n  wraps, then prints only round values (16:45, 17:00) flat. Set it only when\n  the user asks, e.g. `[\"wrap\", \"rotate90\"]` to label every year, or\n  `[\"wrap\", \"rotate45\", \"rotate90\"]` for 45° labels. Steps: `wrap`, `skip`\n  (sequences only), `rotate45`, `rotate90`.\n\n## Parameter overrides — when to reach for them\n\nOverrides exist, but prefer letting semantic types drive decisions. Reach\nfor an override only when the user's intent genuinely conflicts with the\ndefault:\n\n- **Force an aggregation:** `encodings.y = { field: \"sales\", aggregate: \"sum\" }`.\n- **Sort a category axis by its measure:** `encodings.x = { field: \"name\", sortBy: \"y\", sortOrder: \"descending\" }`.\n- **Pick a color scheme:** `encodings.color = { field: \"region\", scheme: \"tableau10\" }`.\n- **Control a color ramp when the user asks for it:** `encodings.color = { field: \"net\", scheme: [\"#c0392b\", \"#f2f2f2\", \"#2e75b6\"], mid: 0, bins: 5 }`; add `reverse: true` to flip it or `domain: [-50000, 50000]` to fix its extent. These win over the theme.\n- **Override an inferred type:** `encodings.x = { field: \"year\", type: \"ordinal\" }` (e.g. treat a year as discrete bands).\n- **Use readable field titles:** `field_display_names = { percentageOfCountries: \"Percentage of countries\" }`.\n  Keep encodings bound to the real column name; Flint uses the display name for axis titles and legend headers.\n- **Resize the chart:** Flint sizes from two numbers — `baseSize` (the *target*\n  it aims for, default 400×320) and `canvasSize` (a *hard ceiling* it may never\n  exceed). With dense data the chart stretches from base toward the ceiling.\n  - Want a comfortable size that may grow for dense data → set `chart_spec.baseSize = { width, height }`.\n  - Want a fixed slot it must fit inside → set `chart_spec.canvasSize = { width, height }` alone; the chart fills it and shrinks to fit, never overflowing. *What you ask for is what you get.*\n  - Both → aims for `baseSize`, grows toward `canvasSize`, never beyond.\n- **Force log \u002F zero baseline:** the `logScale_*` \u002F `includeZero_*` chart\n  properties above.\n\nGlobal layout tuning lives in the top-level `options` object (e.g.\n`addTooltips`, band padding, facet sizing). It is rarely needed for\nauthoring — omit it unless asked.\n\n## Worked examples\n\nIn each example `data` is a placeholder — the host binds real rows or a\nURL. You author only `chart_spec` and `semantic_types`.\n\n### Scatter plot\n\nUser: \"Plot car weight vs fuel economy, colored by origin.\"\n\n```json\n{\n  \"data\": { \"values\": [] },\n  \"semantic_types\": {\n    \"weight\": \"Quantity\",\n    \"mpg\": \"Quantity\",\n    \"origin\": \"Country\"\n  },\n  \"chart_spec\": {\n    \"chartType\": \"Scatter Plot\",\n    \"encodings\": {\n      \"x\": { \"field\": \"weight\" },\n      \"y\": { \"field\": \"mpg\" },\n      \"color\": { \"field\": \"origin\" }\n    },\n    \"baseSize\": { \"width\": 400, \"height\": 300 }\n  }\n}\n```\n\n### Revenue bar chart with facets, sorted by value\n\nUser: \"Show revenue by product line, biggest first, one panel per region.\"\n\n```json\n{\n  \"data\": { \"values\": [] },\n  \"semantic_types\": {\n    \"product_line\": \"Category\",\n    \"revenue\": \"Amount\",\n    \"region\": \"Region\"\n  },\n  \"chart_spec\": {\n    \"chartType\": \"Bar Chart\",\n    \"encodings\": {\n      \"x\": { \"field\": \"product_line\", \"sortBy\": \"y\", \"sortOrder\": \"descending\" },\n      \"y\": { \"field\": \"revenue\" },\n      \"column\": { \"field\": \"region\" }\n    }\n  }\n}\n```\n\n### Time series, multiple series (wide → long via array)\n\nUser: \"Line chart of monthly sales and profit.\"\n\n```json\n{\n  \"data\": { \"values\": [] },\n  \"semantic_types\": {\n    \"month\": \"YearMonth\",\n    \"sales\": \"Amount\",\n    \"profit\": \"Profit\"\n  },\n  \"chart_spec\": {\n    \"chartType\": \"Line Chart\",\n    \"encodings\": {\n      \"x\": { \"field\": \"month\" },\n      \"y\": [\"sales\", \"profit\"]\n    },\n    \"chartProperties\": { \"interpolate\": \"monotone\", \"showPoints\": true }\n  }\n}\n```\n\n### Donut chart (Pie + innerRadius), value on `size`\n\nUser: \"Show market share by vendor as a donut.\"\n\nPie\u002Fdonut maps the slice value to `size` (rendered as angle) and the\ncategory to `color`. Data is already long (one row per vendor).\n\n```json\n{\n  \"data\": { \"values\": [] },\n  \"semantic_types\": {\n    \"vendor\": \"Category\",\n    \"share\": \"Percentage\"\n  },\n  \"chart_spec\": {\n    \"chartType\": \"Pie Chart\",\n    \"encodings\": {\n      \"size\": { \"field\": \"share\" },\n      \"color\": { \"field\": \"vendor\" }\n    },\n    \"chartProperties\": { \"innerRadius\": 60 }\n  }\n}\n```\n\n### Bullet chart (KPI vs target)\n\nUser: \"Show each rep's sales against their quota.\"\n\n```json\n{\n  \"data\": { \"values\": [] },\n  \"semantic_types\": {\n    \"rep\": \"Name\",\n    \"sales\": \"Amount\",\n    \"quota\": \"Amount\"\n  },\n  \"chart_spec\": {\n    \"chartType\": \"Bullet Chart\",\n    \"encodings\": {\n      \"y\": { \"field\": \"rep\" },\n      \"x\": { \"field\": \"sales\" },\n      \"goal\": { \"field\": \"quota\" }\n    }\n  }\n}\n```\n\n## What you should NOT do\n\n- **Don't re-emit the data.** Reference columns by name; let the host bind\n  `data` (url, variable, or small literal). Never paste large datasets.\n- **Don't write backend specs directly** — write the `ChartAssemblyInput`,\n  then call the assembler. That's the whole point.\n- **Don't invent transforms.** The only built-in reshape is the array form\n  on `x`\u002F`y`. If the data shape is wrong for the chart, say so and ask the\n  host to reshape it.\n- **Don't invent field names.** Reference only columns that exist, spelled\n  exactly. If the data is the wrong shape for the chart, reshape it upstream\n  rather than guessing column names that aren't there.\n- **Don't set `type`\u002F`aggregate`\u002F`sortOrder`** unless intent conflicts\n  with the default.\n- **Don't pass colors, font sizes, axis tick counts** — the compiler\n  derives these. Users fine-tune the *output* spec. The exception is a colour\n  mapping the user asked for (a brand ramp, fixed bins, a pivot): use the\n  `color` encoding's `scheme`, `reverse`, `mid`, `domain`, `bins` and `colors`.\n- **Don't invent semantic type names.** If none fit, use the family\n  default (`Quantity`, `Category`, `Date`).\n- **Don't call the library to discover channels\u002Ftypes** — this document is\n  the authoring reference.\n\n## Validation checklist\n\nBefore returning, verify:\n\n1. `chartType` is an exact registered name supported by the target backend.\n2. Every `field` referenced in `encodings` is a real column name.\n3. Every encoded field has an entry in `semantic_types` (specific type).\n4. Required channels for the chart type are present (e.g. Bullet→`goal`,\n   Candlestick→`open\u002Fhigh\u002Flow\u002Fclose`, Pie→`size`+`color`).\n5. Any `chartProperties` keys are valid for that chart type and in range.\n6. You did **not** inline large data or hand-tune derived styling.\n7. The data carries no embedded total\u002Fsubtotal level (e.g. an `all` \u002F `total`\n   row) mixed with its components on a stacked, grouped, or colored channel.\n","# flint-chart: authoring and using a chart spec\n\n## What you produce (and what you do NOT)\n\nYour output is the **spec**: the `chart_spec` and `semantic_types` of a\n`ChartAssemblyInput`. You reference data columns **by name**. The host\npasses the resulting input to `assembleVegaLite`, `assembleECharts`,\nor `assembleChartjs` to get a backend spec.\n\n**You write the input spec, not the output spec.** And critically:\n\n- **DO** emit `chart_spec` (chart type, channel→field mapping, properties)\n  and `semantic_types` (field → semantic type).\n- **DO** add `interaction_spec` when the user asks for behaviour (highlight,\n  legend toggle, pan and zoom, brush), asks for an interactive chart, or the\n  session's goal is to explore the data. List presets by name; see\n  \"Interactions\", and load the `flint-interaction-author` skill for an intent,\n  an interactive chart with nothing named, or a combination.\n- **Reference columns by name.** How `data` itself gets bound depends on\n  the situation — a URL, a host-side variable, or embedded rows (see \"How\n  data gets bound\"). Embedding is fine for small tables; just don't\n  re-serialize a *large* dataset by hand, since that risks truncation and\n  silent value corruption and wastes tokens.\n- **Transform data before Flint.** If the requested chart needs aggregation,\n  filtering, joins, pivots, derived columns, or long\u002Fwide reshaping beyond\n  Flint's built-in static-series fold, use a coding, notebook, SQL, or data tool\n  first. Then author the Flint spec against the transformed table.\n- **Style after Flint, only when needed.** Author structure in Flint. For a\n  presentation tweak Flint does not express (a reference line, annotation, or\n  shaded band), use the Vega-Lite escape hatch — see \"Post-Flint style\n  customization\". Never feed edited Vega-Lite JSON back to `render_chart`.\n\n## When the user wants more than a spec\n\nFirst decide which workflow the user is asking for:\n\n- **Spec authoring only:** return a `ChartAssemblyInput` or its\n  `semantic_types` + `chart_spec` pieces. Do not install packages or write\n  renderer code unless asked.\n- **MCP chart output:** if Flint MCP tools are available, **default to\n  `validate_chart` then `show`** whenever the user asks to see a chart.\n  `validate_chart` checks the spec and returns a report without drawing; fix\n  what it asks in the spec, then call `show({ \"chart\": … })` once with the\n  whole spec, which opens an interactive, live-rendered card with a\n  customization panel. The server keeps nothing between calls: the spec in\n  the conversation is the chart. Only fall back to `render_chart` (PNG\u002FSVG)\n  when the host has no App UI support or the user explicitly wants a static\n  image. Use `compile_chart` when the user wants backend-native JSON, and\n  `list_chart_types` when you need the supported chart catalog.\n- **DO** pass `updates` beside the input to `validate_chart` and `show` when the user\n  asks the chart to open in a state: rows to stand out, a note on a mark, a\n  framed range. It is a list of ChartUpdate layers, state not behaviour; the\n  shapes are in `flint:\u002F\u002Finteraction-skill` under \"Update the chart\". To change a\n  chart in a later turn, change its spec or `updates` and call `show` again: a\n  new card shows it, earlier cards stay as they were. The reader's edits in the\n  card's panel live on that card; its context gives the `chart_spec` that keeps\n  them, so pass it unless the user asked for something else.\n- **DO** pass `view: \"table\"` to `show` when the user wants to see\n  a chart's numbers: the card opens on the table of the rows the chart draws,\n  and the reader can switch to the chart. When the user wants records listed and\n  no chart at all, use `chartType: \"Table\"` instead.\n- **Project integration, only when the user asks for code:** add Flint to an\n  app, notebook, script, or agentic product, install\u002Fimport the library, and\n  call an assembler in code. Keep the same `ChartAssemblyInput` contract, then\n  let the host render the backend result.\n\nFor MCP clients, the server can run with `npx`:\n\n```bash\nnpx -y flint-chart-mcp\n```\n\nFor JavaScript or TypeScript projects, install Flint first and add only the\nrenderer peer dependencies needed by the backend you will render:\n\n```bash\nnpm install flint-chart\nnpm install vega vega-lite vega-embed  # browser Vega-Lite rendering\nnpm install echarts                    # ECharts rendering\nnpm install chart.js                   # Chart.js rendering\n```\n\nThen compile with the requested backend:\n\n```ts\nimport { assembleChartjs, assembleECharts, assembleVegaLite } from 'flint-chart';\n\nconst vegaLiteSpec = assembleVegaLite(input);\nconst echartsOption = assembleECharts(input);\nconst chartjsConfig = assembleChartjs(input);\n```\n\nPython support is planned for a later release. Until the PyPI package is\npublished, use the npm package or MCP server for released workflows.\n\n```ts\ninterface ChartAssemblyInput {\n  \u002F\u002F Bound by the HOST or by you, depending on the situation (see below).\n  data: { values: any[] } | { url: string };\n  semantic_types?: Record\u003Cstring, string | SemanticAnnotation>;  \u002F\u002F field → type ( ← you write this)\n  chart_spec: {                               \u002F\u002F                        ← you write this\n    chartType: string;                        \u002F\u002F e.g. \"Scatter Plot\"\n    title?: string;                           \u002F\u002F the headline — write one\n    subtitle?: string;                        \u002F\u002F what is measured, of whom, when, in what units\n    encodings: Record\u003Cstring, EncodingValue>; \u002F\u002F channel → { field, ... } (or array)\n    baseSize?: { width: number; height: number };    \u002F\u002F target layout size, default 400×320\n    canvasSize?: { width: number; height: number };  \u002F\u002F optional hard ceiling on stretch\n    chartProperties?: Record\u003Cstring, any>;    \u002F\u002F per-chart tuning (optional)\n  };\n  options?: Record\u003Cstring, any>;              \u002F\u002F global layout options (rarely needed)\n  field_display_names?: Record\u003Cstring, string>; \u002F\u002F field → readable axis\u002Flegend title\n  theme_spec?: string | { extends: string; [key: string]: any }; \u002F\u002F preset or preset override (Vega-Lite only)\n}\n```\n\n## How data gets bound\n\nUse the binding mode that matches the runtime. Do not mix them.\n\n1. **Direct MCP rendering: embed rows.** When calling `render_chart`,\n  `compile_chart`, or `validate_chart`, the tool arguments are JSON. If the\n  data is small or already transformed by another tool, pass it as\n  `data: { values: [...] }`. Do not pass runtime variable names in\n  MCP tool calls — the MCP server cannot see your local variables.\n2. **Direct MCP rendering: reference a local file.**\n  The `flint-chart-mcp` server can load `data: { url: \"...\" }` from local\n  `.json`, `.csv`, or `.tsv` files. By default any local file the agent can\n  name is readable (relative paths resolve against the working directory); a\n  hardened deployment may reject local file references entirely via\n  `--disable-file-reference` (or `FLINT_MCP_DISABLE_FILE_REFERENCE`), in which\n  case pass rows inline with `data.values`. Remote URL\n  fetching is disabled. If the data must be transformed first, use a\n  coding\u002Fdata tool to write a small prepared file, then reference that file.\n3. **Generated application or notebook code: bind runtime variables.** If the\n  user asks you to add Flint to code, write normal data-loading code first and\n  pass a real runtime value, e.g. `data: { values: rows }`, to\n  `assembleVegaLite`, `assembleECharts`, or `assembleChartjs`. This variable\n  pattern is for generated code, not for MCP tool calls.\n  - Parse CSV with a real parser (`d3.csvParse`, Papa Parse, `pandas`), never\n    `line.split(',')`: quoted values such as `\"Korea, Rep.\"` shift every later\n    column, and a shifted measure silently changes the chart (a wrong domain,\n    an unwanted log scale).\n  - Keep values as the data has them and let `semantic_types` read them: a\n    number column `year` typed `Year`, and a `YearMonth` column as `202501`,\n    `\"202501\"` or `\"2025-01\"`, are labelled as years and months; don't convert\n    them to date strings first.\n\nFor spec-only answers, return the `semantic_types` and `chart_spec` pieces and\nstate how the host should bind data. In the worked examples below, `data` is\nshown as `{ values: [] }` to signal \"host binds this\" — focus on `chart_spec`\nand `semantic_types`.\n\n## Data transformation before charting\n\nFlint is a chart compiler, not a data-wrangling layer. If the chart needs grouped\ntotals, time buckets, filters, joins, pivots, derived ratios, or a long-form\ntable, transform the data first with a host tool, then bind the prepared table\n(see \"How data gets bound\"). Pick semantic types and channels for the transformed\ncolumns, not for columns that no longer exist.\n\n**Sanity-read the values first — don't chart blind.** Inspect the actual data\nwith your data tool (distinct values per category column, min\u002Fmax per measure),\nnot just the column names, and watch for:\n\n- **Embedded totals.** A category column may mix an aggregate level with its\n  parts (e.g. `all` alongside `cage-free`\u002F`caged`, or a `Total` region). Charting\n  the total with its parts double-counts and flattens the parts — keep one or the\n  other on a stacked\u002Fgrouped\u002Fcolored channel, not both.\n- **Units.** `Percentage` accepts either a fraction (0–1) or a percent (0–100):\n  Flint reads which from the values and formats both as `%`. Don't rescale a\n  column for Flint, and don't scale it twice.\n- **One real entity.** If your breakdown column has a single distinct value, the\n  per-group chart collapses to one mark — the intended breakdown is likely a\n  different column.\n\n## Post-Flint style customization\n\nStay at the Flint level for structure (data, chart type, channels, transforms,\nsizing, properties) — Flint specs stay portable and regenerate safely. Drop to\nbackend JSON only after a valid Flint chart exists, and only for a narrow\npresentation change Flint does not expose (exact axis\u002Flegend\u002Fmark styling,\ntitles, annotations, reference lines, layout polish). Never use it to change the\ndata, chart type, field mappings, or transforms — fix those upstream.\n\nFor a Vega-Lite-specific style tweak:\n\n1. Author and validate the Flint `ChartAssemblyInput`.\n2. Render or inspect the Flint chart first, when possible.\n3. Call `compile_chart` with `backend: \"vegalite\"`.\n4. Make the smallest necessary style\u002Fpresentation edit to the returned\n  Vega-Lite spec.\n5. Render the edited spec in the host environment with a Vega-Lite renderer.\n\nThis edited Vega-Lite spec is no longer a portable Flint spec. Do not send it to\n`render_chart`; use `render_chart` only for Flint `ChartAssemblyInput`.\n\n## Write a headline\n\nSet `chart_spec.title` to the finding, in a sentence, and `chart_spec.subtitle`\nto the reading of it — what is measured, of whom, when, in what units:\n\n```\ntitle:    \"A pyramid that is no longer a pyramid\"\nsubtitle: \"United States population by age and sex, 2020, millions\"\n```\n\nAdd `chart_spec.label` only when the chart sits in a numbered sequence or\nunder a section name: `label: \"Figure 2\"`. It is drawn above the headline in\nthe theme's kicker style.\n\n`chart_spec.source` is optional. Add it only when the user names the source or\nasks for a source line, or the data itself states where it comes from:\n`source: \"UN World Population Prospects 2024\"` (or `{ text, href }` to link\nit). Never invent or guess one. The theme decides how it reads (\"Source:\",\nside, rule, credit); a chart without one draws no footer.\n\n`chart_spec.chrome` is the house decoration the theme draws around the chart:\nheader rules, tabs, wordmarks, logos, the footer rule and the house credit. It is\nnot the chart's own title, label or source. The theme draws it by default; leave\n`chrome` out unless the user asks to drop it: `chrome: false`\ndrops all of it; `chrome: { logos: false }` (or `header`, `footer: false`) drops\none part. Without logos a credit keeps its words.\n\n`Jan`, `Cairo`, `Chrome` name their own kind; `26`, `5,300`, `0.42` do not, and\nthe headline is where they get named. Leave it out only where the chart is not\nread on its own — a sparkline in a cell, a tile under its own caption. Nothing\nbreaks: with no headline to lean on, the compiler keeps the axis titles instead.\n\n## Visual themes (`theme_spec`)\n\nUse one of two forms. Prefer a preset unless the user asks for a specific\nbrand adjustment.\n\n### 1. Use a preset\n\nCall `list_themes` to choose an id, then place it beside `chart_spec`:\n\n```json\n{ \"chart_spec\": { ... }, \"theme_spec\": \"economist\" }\n```\n\n| id | what it is for |\n| --- | --- |\n| `nyt` | Newsroom graphics: headline states the finding, values on the marks, series named at their ends. |\n| `economist` | Print weekly: compact, flat headline over a deck, units repeated down the ruler. |\n| `swiss` | International Typographic Style: strong grid structure, black typography, and a focused red accent. |\n| `nature` | Journal figure: small panel, axis titles with units, statistics beside the fit. |\n| `mckinsey` | Consulting deck: wide bands, every value printed, headline states the takeaway. |\n| `datawrapper` | Embedded web chart: narrow column, plain headline and deck, rule under the footer. |\n| `powerbi` | Dashboard tile: compact, legend to the right, latest point emphasised. |\n| `powerbi-light` | Light dashboard tile: white canvas, fine gridlines, and bright categorical color. |\n| `cartoon` | Playful illustration: warm paper, rounded type, bold outlines, and bright color. |\n| `apple` | App-interface chart: light labels, trailing value axis, symbols beside color. |\n| `de-stijl` | De Stijl composition: primary planes in heavy black lines, steps not slopes, title under the picture. |\n| `bbc` | BBC News graphic: big bold headline, gridlines only, heavy zero rule, footer rule with the source. |\n| `material` | Material Design 3 product chart: purple-led tonal palette, Roboto type, light gridlines, rounded bars. |\n\n### 2. Override a preset\n\nKeep overrides narrow and state only what the user wants to change:\n\n```json\n{\n  \"theme_spec\": {\n    \"extends\": \"economist\",\n    \"id\": \"our-brand\",\n    \"ink\": {\n      \"series\": {\n        \"single\": \"#6b3fa0\"\n      }\n    }\n  }\n}\n```\n\nCommon simple overrides are `ink.surface.canvas`, `ink.series.single`,\n`ink.series.categorical`, `type.headline.family`, and `layout.density`\n(`\"compact\"`, `\"normal\"`, or `\"airy\"`). If replacing\n`ink.series.categorical`, also replace `categoricalExtended` so charts with\nmany series keep the requested brand palette.\n\nDo not copy an entire preset or invent theme keys. A theme controls\npresentation; fields, aggregation, filtering, and sorting still belong in the\nchart input. ThemeSpec applies to Vega-Lite and Plotly; ECharts, Chart.js, and Excel ignore it.\n\nFull reference:\nhttps:\u002F\u002Fmicrosoft.github.io\u002Fflint-chart\u002F#\u002Fdocumentation\u002Ftheme-spec\n\n## Interactions (`interaction_spec`)\n\nAdd `interaction_spec` beside `chart_spec` when the user asks for behaviour,\nor when the goal of the session is to explore or analyse the data. A static\nimage never needs it.\n\n```json\n{\n  \"chart_spec\": { \"chartType\": \"Line Chart\", \"encodings\": { \"x\": \"date\", \"y\": \"value\", \"color\": \"series\" } },\n  \"interaction_spec\": { \"interactions\": [ { \"type\": \"brush-x\" } ] }\n}\n```\n\nEvery entry is `{ \"type\": \u003Cpreset>, \"options\": { ... } }`, one per behaviour.\nTake the presets and their options for the chart type from\n`list_interaction_presets` with `chartType`; never invent one. Options nest under\n`options`; `id` sits on the entry.\n\nWhich presets answer a request, how they combine, and what the warnings mean\nis the `flint-interaction-author` skill (`flint:\u002F\u002Finteraction-skill` on the\nMCP server). Load it for anything beyond a single named gesture: an intent\n(explore, compare, analyse, find outliers), \"make it interactive\" with\nnothing named, several presets together, behaviour on a chart that already\nexists, or an update to a chart on screen.\n\nFull reference:\nhttps:\u002F\u002Fmicrosoft.github.io\u002Fflint-chart\u002F#\u002Fdocumentation\u002Finteraction-spec\n\n## Step 1 — pick `chartType`\n\nUse one of the registered names **exactly**. Vega-Lite is the default and\nbroadest backend; the table below lists each Vega-Lite chart type, the\nchannels it accepts, and its tuning properties (see \"Chart-level\nproperties\"). Required channels are noted.\n\n| chartType | Channels | Notes \u002F required |\n|---|---|---|\n| `\"Scatter Plot\"` | x, y, color, size, shape, detail, opacity, column, row | x + y required; `detail` carries a field (e.g. `country`) on each point without a visual channel, for tooltips and linking |\n| `\"Regression\"` | x, y, size, color, column, row | scatter + fit line; props `regressionMethod`, `polyOrder` |\n| `\"Connected Scatter Plot\"` | x, y, order, color, detail, column, row | x + y required; `order` = connection sequence (time\u002Findex), so the line traces a trajectory and may self-cross |\n| `\"Ranged Dot Plot\"` | x, y, color | dumbbell of two x per category |\n| `\"Strip Plot\"` | x, y, color, size, column, row | jittered points; props `stepWidth`, `pointSize`, `opacity` |\n| `\"Bar Chart\"` | x, y, color, opacity, column, row | one discrete + one measure; prop `cornerRadius` |\n| `\"Grouped Bar Chart\"` | x, y, group, column, row | `group` = the clustering category; prop `dodge` |\n| `\"Stacked Bar Chart\"` | x, y, color, column, row | prop `stackMode` |\n| `\"Pyramid Chart\"` | x, y, color | diverging horizontal bars |\n| `\"Lollipop Chart\"` | x, y, color, column, row | prop `dotSize` |\n| `\"Waterfall Chart\"` | x, y, color, column, row | `color` = Type column, values `start`\u002F`delta`\u002F`end` only; omit it for auto sign coloring; props `cornerRadius`, `totals` |\n| `\"Gantt Chart\"` | y, x, x2, color, detail, column, row | x = start, x2 = end |\n| `\"Bullet Chart\"` | y, x, goal, color, column, row | `goal` required (target) |\n| `\"Histogram\"` | x, color, column, row | x = measure to bin; prop `binCount` |\n| `\"Boxplot\"` | x, y, color, opacity, column, row | category + measure; props `whiskerMethod`, `showOutliers`, `dodge` |\n| `\"ECDF Plot\"` | x, color, detail, column, row | x = measure; cumulative distribution (step line); prop `showPoints` |\n| `\"Heatmap\"` | x, y, color, column, row | color = the measure |\n| `\"Calendar Heatmap\"` | x, color | x = date; color = daily value (summed per day); GitHub-style week × weekday grid |\n| `\"Line Chart\"` | x, y, color, strokeDash, detail, opacity, column, row | props `interpolate`, `showPoints` |\n| `\"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` |\n| `\"Bump Chart\"` | x, y, color, detail, column, row | rank-over-time lines |\n| `\"Slope Chart\"` | x, y, color, detail, column, row | two-period value change; straight segments + end points, one line per category |\n| `\"Area Chart\"` | x, y, color, opacity, column, row | props `interpolate`, `opacity`, `stackMode` |\n| `\"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) |\n| `\"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 |\n| `\"Streamgraph\"` | x, y, color, column, row | centre-stacked areas |\n| `\"Density Plot\"` | x, color, column, row | props `bandwidth`, `stackMode` (series stack; `layered` overlaps them), `opacity` |\n| `\"Ridgeline Plot\"` | x, y, color | x = measure; `color` (or a discrete `y`) names the ridges, each the KDE of x on its own baseline; a measure `y` draws each series' curve instead; props `overlap`, `order`, `tailCut`, `colorBy`, `perspective` (plans unset `skew`, `tilt`, `overlap` from the ridge count so the depth takes about 40% of the plot), `skew`, `tilt`, `valueMax`, `markAt` (`true` marks the x ticks, or a list of x values; each ridge's value is dotted and labelled, joined across ridges), `opacity`, `interpolate`, `bandwidth`; named ridges share one ink unless `colorBy` asks otherwise; a numeric `color` keeps a gradient key; **Vega-Lite only** |\n| `\"Marginal Plot\"` | x, y, color, size, shape, opacity | the plot follows the axis types (measure × measure = scatter, category × measure = strip, category × category = heatmap of counts) with each axis's distribution along it (density, histogram or boxplot on a measure axis, a bar of counts on a category axis, split by `color`; density margins layer by default); props `marginal`, `stackMode`, `bandwidth`, `binCount`, `whiskerMethod`, `showOutliers`; **Vega-Lite only** |\n| `\"Pie Chart\"` | size, color, column, row | `size` = slice value (→ angle), `color` = category; props `innerRadius`, `sortSlices` |\n| `\"Rose Chart\"` | x, y, color, column, row | polar bars; props `alignment`, `padAngle`, `sortSlices` |\n| `\"Radar Chart\"` | x, y, color, column, row | props `filled`, `fillOpacity`, `strokeWidth` |\n| `\"Candlestick Chart\"` | x, open, high, low, close, column, row | OHLC all required |\n| `\"Bar Table\"` | y, x, color, column, row | compact bars + value labels |\n| `\"KPI Card\"` | metric, value, goal | big-number tile; prop `behindThreshold` |\n| `\"Table\"` | none (`encodings: {}`) | the rows as given, one column per field in row order; shape `data.values` to the rows and fields to list, name columns with `field_display_names`; numbers and dates print by `semantic_types`; receives links and `updates`, sends no selection |\n| `\"Map\"` | longitude, latitude, color, size, opacity | bubble map; props `region`, `projection` |\n| `\"Choropleth\"` | id, color, detail | `id` = region key (see **Maps** below); props `region` (`\"world\"`\u002F`\"us\"`), `level` (US: `\"state\"`\u002F`\"county\"`\u002F`\"auto\"`), `projection` (world) |\n\n**Maps: what `id` accepts.** Flint joins the key to its own base map; you never\nsupply geometry or a lookup. A **world** map takes country names (\"India\",\n\"DR Congo\", \"United Kingdom\"), ISO 3166 alpha-2 \u002F alpha-3 codes (\"IN\", \"IND\"),\nor ISO numeric codes (356), all in one column. A **US** map takes state names,\nUSPS codes (\"CA\") or FIPS codes (6), and county FIPS (6037) with\n`level: \"county\"`. Declare the key `Country` or `State` in `semantic_types`\n(or set `chartProperties.region`) whenever codes could collide: \"CA\", \"IN\",\n\"Georgia\", or small numbers. A key that matches no region is not drawn. Map\nmarks carry only `id`, `color` and `detail`: to link a map with charts keyed by\na name column, encode that column as `id` (names resolve too) or put it on\n`detail` (e.g. `id: \"map_id\", detail: \"country\"`), which also labels the tooltip.\n\n**Donut chart:** use `\"Pie Chart\"` with `chartProperties.innerRadius > 0`.\n\n**Choosing a bar chart (most common mix-up).** All three take one discrete\ncategory on `x` (or `y`) plus one measure. They differ in how a **second**\ncategory is shown — and each reads that second category from a **different\nchannel**:\n\n- `\"Bar Chart\"` — use for a single series. When multiple rows share an `x`, a\n  second category on `color` produces stacked segments. For side-by-side bars,\n  use `\"Grouped Bar Chart\"` with the second category on `group`.\n- `\"Stacked Bar Chart\"` — second category on `color`, drawn as **stacked**\n  segments within each bar (totals matter). Tune with `stackMode`\n  (`stacked` \u002F `normalize` \u002F `layered`).\n- `\"Grouped Bar Chart\"` — second category on the **`group`** channel, drawn as\n  **side-by-side (dodged)** bars within each `x` cluster (compare values\n  directly). Put the clustering category on `group`, *not* `color`.\n\nRule of thumb: comparing parts-to-whole → Stacked; comparing values\nside-by-side → Grouped (use `group`); single series → Bar.\n\n**Waterfall color is a special \"Type\" column, not a free category.** On a\n`\"Waterfall Chart\"` the `color` channel is reserved for a *type* field whose\nvalues are literally `start`, `delta`, and `end` — it drives which bars anchor\nto zero, not an arbitrary grouping. Do **not** bind `color` to an\n`Increase`\u002F`Decrease` (or up\u002Fdown, gain\u002Floss) category: the up\u002Fdown direction is\nalready derived from the **sign** of the `y` value and colored automatically\n(green up \u002F red down). For the common case, **omit `color` entirely** and let\nFlint infer the start\u002Fdelta\u002Fend and per-bar sign coloring. To force which bars\nare anchored totals, use the `totals` property (`first`\u002F`last`\u002F`both`), not a\ncolor field. Only bind `color` when you genuinely have a `start`\u002F`delta`\u002F`end`\ntype column.\n\n**Backend coverage.** Vega-Lite supports all of the above. Other backends\nsupport a subset (verify if targeting a non-VL backend):\n\n- **ECharts** adds: `\"Gauge\"`,\n  `\"Funnel\"`, `\"Treemap\"`, `\"Sunburst\"`, `\"Sankey\"`,\n  `\"Parallel Coordinates\"`, `\"Graph\"`, `\"Tree\"`.\n- **Chart.js** supports: Scatter, Bubble, Bar, Grouped Bar, Stacked Bar,\n  Lollipop, Bump, Combo, Line, Area, Range Area, Pie, Doughnut, Histogram,\n  Radar, Rose, Slope, Connected Scatter.\n\nYou do not need to call the library or inspect its source to author the\ninput — pick from this table.\n\n## Step 2 — map fields to channels\n\nEach channel maps to an **encoding object** `{ field, ... }` (or a bare\nstring shorthand, expanded to `{ field: \"\u003Cstring>\" }`):\n\n```json\n\"encodings\": {\n  \"x\": { \"field\": \"weight\" },\n  \"y\": \"mpg\",\n  \"color\": { \"field\": \"origin\" }\n}\n```\n\n**Encoding object fields** (all optional except `field`):\n\n| Field | Values | Purpose |\n|---|---|---|\n| `field` | column name | Bind the channel to a data column |\n| `type` | `quantitative`, `nominal`, `ordinal`, `temporal` | Override the inferred encoding type (rarely needed) |\n| `aggregate` | `count`, `sum`, `average`, `mean` | Force an aggregation on a measure channel. A heatmap that gets several rows per cell aggregates its colour on its own (sum for additive types such as `Quantity` or `Profit`, average otherwise) and warns; set `aggregate` to choose |\n| `sortOrder` | `ascending`, `descending` | Sort direction; in Vega-Lite, direction alone preserves\u002Freverses the semantic sequence |\n| `sortBy` | mapped `\"x\"`, `\"y\"`, `\"color\"`; Vega-Lite also accepts a data field or JSON-array string | Explicit category ordering; see the Vega-Lite contract below |\n| `scheme` | Vega scheme name (e.g. `viridis`, `redblue`), or a list of stops low end first (`[\"#c0392b\", \"#f2f2f2\", \"#2e75b6\"]`) | Color scheme for the `color` channel; wins over the theme's ramp |\n| `reverse` | `true` \u002F `false` | `color` only: run the color scale the other way round |\n| `mid` | number | `color` only: where a diverging scale pivots, for this chart (the field's `divergingMidpoint`) |\n| `domain` | `[low, high]` | `color` only: the measure extent the ramp spans, in place of the data's |\n| `bins` | integer ≥ 2, or `0` | `color` only: quantize a continuous measure into that many steps; `0` keeps the ramp continuous |\n| `colors` | `{ value: color }` | `color` only: colors bound to category values, for this chart (the field's `colors`) |\n| `title` | a string, or `null` | The name the channel's axis or key prints in place of the field's; `null` prints none |\n\nYou usually don't need `type`, `aggregate`, or `sortOrder` — they're\ninferred from the semantic type. Set them only with specific intent.\n\n**Vega-Lite sorting.** Omit sorting for canonical Month\u002FDay\u002FQuarter order.\n`sortOrder` alone follows that semantic sequence (descending reverses it),\nnot alphabetical month names. Without a semantic sequence, direction alone\nuses the field's native value order.\n\n- `sortBy: \"month_number\"` sorts by that existing data field, ascending by\n  default. Multiple rows per category use the minimum sort-field value.\n- `sortBy: \"y\"` sorts by the mapped channel; cross-channel sorts default to\n  descending, while self-channel sorts default to ascending. `x`, `y`, and\n  `color` are reserved channel names and must be mapped.\n- `sortBy: \"[\\\"Mar\\\",\\\"Jan\\\",\\\"Feb\\\"]\"` supplies an explicit category order;\n  `sortOrder: \"descending\"` reverses it. Use a JSON-array **string**, not an array\n  or a Vega-Lite sort object. Members must be strings, numbers, or booleans.\n- For explicit lexical ordering of month labels, sort by the category field\n  itself: `{ \"field\": \"month\", \"sortBy\": \"month\", \"sortOrder\": \"ascending\" }`.\n\nUnknown fields, unmapped channels, invalid directions, and malformed sort\narrays are errors, not fallback orders. Raw field-name sorting is currently\nsupported by the Vega-Lite backend; for other backends use their supported\nchannel-sort controls. Validate inputs against the selected backend.\n\n**Multi-series (wide → long).** To plot several measure columns as series,\npass an **array** on `x` or `y` (only those two channels). The library\nfolds them into long form and synthesizes a series\u002Flegend field:\n\n```json\n\"encodings\": { \"x\": { \"field\": \"month\" }, \"y\": [\"sales\", \"profit\"] }\n```\n\nAll array fields must be quantitative, and you cannot also bind `color`\nwhen using the array form (the fold owns the color\u002Flegend). This is the\n**only** built-in reshape — there is no `transforms`\u002F`fold` property. For any\nother shape (long↔wide, an aggregate the encodings can't express, a derived\ncolumn, a pivot, a join), reshape the data first with a host tool — pandas\u002Fpolars,\nArquero\u002F`Array.map`\u002FSQL, or a data\u002FMCP tool — and pass the result as\n`data.values`. If you have no way to transform, surface the gap to the developer\nrather than inventing a transform property that does not exist.\n\n## Step 3 — annotate with semantic types\n\n**This is the most important step.** Semantic types drive all downstream\ndecisions — formatting, zero baseline, color scheme, scale direction, and\nmore. Pick the most specific type for each field. Full registered set:\n\n| Family | Semantic types |\n|---|---|\n| Temporal (point) | `DateTime`, `Date`, `Time`, `Timestamp` |\n| Temporal (granule) | `Year`, `Quarter`, `Month`, `Week`, `Day`, `Hour`, `YearMonth`, `YearQuarter`, `YearWeek`, `Decade` |\n| Temporal (span) | `Duration` |\n| Measure (amount) | `Amount`, `Price`, `Quantity`, `Count`, `Number` |\n| Measure (proportion) | `Percentage` |\n| Measure (signed\u002Fdiverging) | `Profit`, `PercentageChange`, `Sentiment`, `Correlation` |\n| Measure (physical) | `Temperature` |\n| Discrete \u002F rank | `Rank`, `Score`, `ID` |\n| Geographic (coord) | `Latitude`, `Longitude` |\n| Geographic (place) | `Country`, `State`, `City`, `Region`, `Address`, `ZipCode` |\n| Categorical | `Category`, `Name`, `Status`, `Boolean`, `Direction`, `Range` |\n| Fallback | `Unknown` |\n\nA temporal type also tells Flint how to read the values: a `YearMonth` accepts `202501`, `2025-01`, `2025\u002F1`, `01\u002F2025`, `2025M01`, `Jan 2025`, `2025-Jul`, `July-2025` or `Jul '25`; a `Date` accepts `20250115`, `2025\u002F01\u002F15`, `Jan 15, 2025` or `15-Jan-2025`. Pass the values as they are; an untyped field is never reinterpreted.\n\nWhat choosing well gets you (automatically):\n\n- `Price` \u002F `Amount` → currency formatting, zero baseline, sequential color\n- `Temperature` → diverging color scheme, no forced zero baseline\n- `Correlation` → fixed `[-1, 1]` diverging domain\n- `Rank` → reversed axis (1 on top), discrete color\n- `Date` \u002F `DateTime` → temporal axis with auto-granularity formatting\n- `Percentage` → percent formatting, 0–100 domain awareness\n\nIf you don't know, use `Quantity` for numbers, `Category` for strings,\n`Date`\u002F`DateTime` for date-shaped values. Do **not** invent type names.\n\n### Saying more than the type name\n\nA field's entry can be an object instead of a string when the type alone\nunderstates what you know:\n\n```json\n\"semantic_types\": {\n  \"anomaly\": { \"semanticType\": \"Quantity\", \"unit\": \"°C\", \"divergingMidpoint\": 0 },\n  \"rating\":  { \"semanticType\": \"Score\", \"intrinsicDomain\": [1, 5] }\n}\n```\n\n- `unit` — an optional assertion that authorizes Flint to display a unit. Add\n  it only when the data or surrounding context establishes the measurement\n  and seeing it materially changes how a reader interprets the number. A type\n  such as `Duration`, a field name such as `life_expectancy`, or values that\n  merely look plausible are not enough evidence by themselves.\n  - Prefer canonical codes: `\"USD\"`, `\"°C\"`, `\"kg\"`, `\"km\u002Fh\"`, `\"min\"`.\n  - Conventional compact units are normalized and may appear beside values\n    (`USD` → `$`, `hours` → `hr`).\n  - Lexical units such as `\"years\"` are stated once beside the field name as\n    `field (years)`, not repeated after every value.\n  - Do not put explanatory phrases in `unit`. Put qualifications such as\n    `\"per working-age resident\"` or `\"constant 2024 prices\"` in the subtitle.\n  - Omit `unit` when its meaning, scale, or denominator is uncertain. Flint\n    does not infer a visible unit from the semantic type or field name.\n- `intrinsicDomain` — the field's own bounds, for bounded scales only: `[1, 5]`\n  for a five-star rating, `[0, 100]` for a percentage score. Not for\n  open-ended measures.\n- `divergingMidpoint` — where the middle colour of a diverging scale sits.\n  Set it if you can tell what the reader is comparing against; leave it out if\n  you can't.\n- `sortOrder` — the order the categories should appear in, when the order in\n  the data is not the one you want and it isn't alphabetical either:\n  `[\"Low\", \"Medium\", \"High\"]`. For a handful of categories, not a long list.\n- `colors` — colours that belong to particular values, when the data has a\n  convention the reader expects: `{ \"DFL\": \"#0793AB\", \"Republican\": \"#A1000F\" }`.\n  Matched on the value (case-insensitive); values left out take the palette,\n  which then skips these colours. Only for real conventions (parties, brands,\n  asset classes), not to restyle a chart — the theme owns the palette.\n- `polarity` — `\"higher-is-better\"` or `\"lower-is-better\"`: which end of a\n  diverging colour is the alarming one. Signed measures (`Profit`,\n  `PercentageChange`) read as higher-is-better on their own; set\n  `\"lower-is-better\"` for costs, errors, churn or delays, so their highs, not\n  their lows, take the warm end, under any theme.\n\n## Step 4 — write the spec\n\nPut the three steps into one `ChartAssemblyInput`, with the data bound as in\n\"How data gets bound\" and a headline as in \"Write a headline\":\n\n```json\n{\n  \"data\": { \"url\": \"sales.csv\" },\n  \"semantic_types\": { \"month\": \"YearMonth\", \"revenue\": \"Amount\", \"region\": \"Region\" },\n  \"chart_spec\": {\n    \"chartType\": \"Line Chart\",\n    \"title\": \"Europe climbed every month after the April dip\",\n    \"subtitle\": \"Revenue by region, $m\",\n    \"encodings\": { \"x\": { \"field\": \"month\" }, \"y\": { \"field\": \"revenue\" }, \"color\": { \"field\": \"region\" } }\n  },\n  \"theme_spec\": \"economist\"\n}\n```\n\nAdd `theme_spec`, `chartProperties`, `interaction_spec` or `updates` only when\nthe request asks for them. With the MCP tools, check it with `validate_chart`,\nfix what the report asks in the spec, then pass the whole spec to `show` as\n`chart`. Without them, run the \"Validation checklist\".\n\n## Chart-level properties (`chartProperties`)\n\n`chartProperties` is an optional per-chart tuning map. Set a property only\nwhen the user asks for that behavior — defaults are sensible. These are\n**design choices**, not styling overrides (colors\u002Ffonts\u002Fticks are still\nderived). Values are clamped to the ranges shown.\n\n| Chart type | Property | Type \u002F range (default) | Effect |\n|---|---|---|---|\n| Bar Chart | `cornerRadius` | 0–15 (0) | Round bar corners (px) |\n| Area \u002F Stacked Bar | `stackMode` | `stacked` \\| `normalize` \\| `center` \\| `layered` (unset) | Stacking behavior; `normalize` = 100%, `center` = streamgraph |\n| Density Plot | `stackMode` | `normalize` \\| `layered` (unset = stacked) | `layered` overlaps the series, translucent |\n| Density Plot | `opacity` | 0.1–1 (0.7) | Layered series opacity |\n| Marginal Plot | `stackMode` | `stacked` (unset = layered) | Stack the density margins instead of layering them |\n| Grouped Bar \u002F Boxplot | `dodge` | `auto` \\| `local` \\| `global` (`auto`) | `local` compacts sparse groups per category; `global` preserves aligned group lanes; leave `auto` unless the user requests one |\n| Line \u002F Area \u002F Sparkline | `interpolate` | `linear` \\| `monotone` \\| `step` \\| `step-before` \\| `step-after` \\| `basis` \\| `cardinal` \\| `catmull-rom` (`linear`) | Curve shape |\n| Line \u002F ECDF Plot | `showPoints` | boolean (false) | Draw point markers on the line |\n| Sparkline | `baseline` | `mean` \\| `zero` \\| `median` \\| `none` (`mean`) | Reference line per spark row |\n| Sparkline | `trendWidth` | 80–600 (240) | Mini line-plot width (px) |\n| Boxplot | `whiskerMethod` | `iqr` \\| `minmax` (`iqr`) | Whisker rule (Tukey 1.5×IQR vs min–max) |\n| Boxplot | `showOutliers` | boolean (true) | Show outlier points (Tukey only) |\n| Area | `opacity` | 0.1–1 (0.7) | Fill opacity |\n| Scatter | `opacity` | 0.1–1 (1) | Point opacity |\n| Strip Plot | `stepWidth` | 10–100 (20) | Jitter spread |\n| Strip Plot | `pointSize` | 0–150 (0=auto) | Point size |\n| Strip Plot | `opacity` | 0–1 (0=auto) | Point opacity |\n| Histogram | `binCount` | 5–50 (10) | Number of bins |\n| Density Plot \u002F Ridgeline \u002F Marginal Plot | `bandwidth` | 0.05–2 (0=auto) | Kernel bandwidth |\n| Ridgeline | `overlap` | 0.5–4 (1.8) | How many baselines up the tallest ridge reaches |\n| Ridgeline | `order` | `mean`, `reverse` (unset = as given; a quantitative series lowest at the front) | Highest mean on top, or reversed |\n| Ridgeline | `tailCut` | 0–0.2 (0) | Trim each ridge to where its density is at least this share of its peak |\n| Ridgeline | `colorBy` | `series`, `x` (unset = one colour) | Named ridges share one ink by default; `series` colours each ridge, `x` fills each with a ramp along x |\n| Ridgeline | `perspective` | boolean (false) | Stand the ridges on a floor seen in perspective: `skew`, `tilt`, `overlap` left unset are planned so the depth takes about 40% of the plot's width and more of its height as ridges are added; the depth names, edge and value ticks give way (along the axis, then level, then every n-th) before they touch |\n| Ridgeline | `skew` | 0–0.4 (0) | Perspective: shift the back ridges left by this share of the x range |\n| Ridgeline | `tilt` | 0–6 (0) | Perspective: the x axis rises this many baselines across its range, labels following its edge |\n| Ridgeline | `opacity` | 0.1–1 (0.5) | Ridge opacity, as the Range Area Chart; solid in perspective unless set |\n| Ridgeline | `valueMax` | number (0 = tallest peak) | Perspective: the value the full ridge height stands for, read on the back-left value axis |\n| Ridgeline | `markAt` | boolean \\| number[] | Mark and label every ridge's value at the x ticks inside the range (`true`) or at these x; a dashed line joins the marks at one x across the ridges |\n| Marginal Plot | `marginal` | `density` \\| `histogram` \\| `boxplot` (`density`) | What each margin draws along a measure axis; a category axis gets a bar of counts |\n| Pie Chart | `innerRadius` | 0–100 (0) | Donut hole size (>0 → donut) |\n| Pie \u002F Rose | `sortSlices` | `none` \\| `descending` \\| `ascending` (`none`) | Order wedges and their legend by slice value |\n| Rose Chart | `alignment` | `left` \\| `center` (`left`) | Wedge alignment |\n| Rose Chart | `padAngle` | 0–0.1 (0) | Gap between slices |\n| Lollipop | `dotSize` | 20–300 (80) | Circle size (px) |\n| Waterfall | `cornerRadius` | 0–8 (0) | Round bar corners |\n| Waterfall | `totals` | `auto` \\| `none` \\| `first` \\| `last` \\| `both` (`auto`) | Which bars anchor to zero as totals (only when no Type column) |\n| Waterfall | `showTextLabels` | boolean (false) | Legacy spelling of `showValueLabels`; still accepted |\n| Bar \u002F Grouped Bar \u002F Stacked Bar \u002F Lollipop \u002F Pyramid \u002F Pie \u002F Donut \u002F Heatmap \u002F 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\u002FM 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. |\n| Regression | `regressionMethod` | `linear` \\| `log` \\| `exp` \\| `pow` \\| `quad` \\| `poly` (`linear`) | Fit method |\n| Regression | `polyOrder` | 1–5 (3) | Polynomial order (when `poly`) |\n| Radar | `filled` | boolean (true) | Fill the polygon |\n| Radar | `fillOpacity` | 0–0.5 (0.15) | Polygon fill opacity |\n| Radar | `strokeWidth` | 0.5–4 (1.5) | Line width |\n| KPI Card | `behindThreshold` | 0–1 (0.5) | Value\u002Fgoal ratio cutoff for color |\n| Map | `region` | `us` \\| `world` \\| `auto` (`auto`) | Geographic scope |\n| Map | `projection` | `mercator` \\| `equalEarth` \\| `orthographic` \\| `stereographic` \\| `conic` \\| `mollweide` | Map projection |\n\n**Cross-cutting properties** (apply to position\u002Ffaceted charts when\nrelevant; set only to force non-default behavior):\n\n- `independentYAxis` (boolean) — faceted charts: give each panel its own\n  y-scale.\n- `logScale_x` \u002F `logScale_y` (boolean) — force a logarithmic axis.\n- `includeZero_x` \u002F `includeZero_y` (boolean) — force the axis to include 0.\n- `xAxisType` \u002F `yAxisType` (`temporal` | `nominal`) — force a temporal\n  field to render as discrete bands (or vice-versa).\n- `labelOverflow` (list of steps) — the path x category names that do not\n  fit their band take. Leave it out: names wrap, then stand upright (trimmed\n  if need be), every name printed; a run of times, dates, years or ranks\n  wraps, then prints only round values (16:45, 17:00) flat. Set it only when\n  the user asks, e.g. `[\"wrap\", \"rotate90\"]` to label every year, or\n  `[\"wrap\", \"rotate45\", \"rotate90\"]` for 45° labels. Steps: `wrap`, `skip`\n  (sequences only), `rotate45`, `rotate90`.\n\n## Parameter overrides — when to reach for them\n\nOverrides exist, but prefer letting semantic types drive decisions. Reach\nfor an override only when the user's intent genuinely conflicts with the\ndefault:\n\n- **Force an aggregation:** `encodings.y = { field: \"sales\", aggregate: \"sum\" }`.\n- **Sort a category axis by its measure:** `encodings.x = { field: \"name\", sortBy: \"y\", sortOrder: \"descending\" }`.\n- **Pick a color scheme:** `encodings.color = { field: \"region\", scheme: \"tableau10\" }`.\n- **Control a color ramp when the user asks for it:** `encodings.color = { field: \"net\", scheme: [\"#c0392b\", \"#f2f2f2\", \"#2e75b6\"], mid: 0, bins: 5 }`; add `reverse: true` to flip it or `domain: [-50000, 50000]` to fix its extent. These win over the theme.\n- **Override an inferred type:** `encodings.x = { field: \"year\", type: \"ordinal\" }` (e.g. treat a year as discrete bands).\n- **Use readable field titles:** `field_display_names = { percentageOfCountries: \"Percentage of countries\" }`.\n  Keep encodings bound to the real column name; Flint uses the display name for axis titles and legend headers.\n- **Resize the chart:** Flint sizes from two numbers — `baseSize` (the *target*\n  it aims for, default 400×320) and `canvasSize` (a *hard ceiling* it may never\n  exceed). With dense data the chart stretches from base toward the ceiling.\n  - Want a comfortable size that may grow for dense data → set `chart_spec.baseSize = { width, height }`.\n  - Want a fixed slot it must fit inside → set `chart_spec.canvasSize = { width, height }` alone; the chart fills it and shrinks to fit, never overflowing. *What you ask for is what you get.*\n  - Both → aims for `baseSize`, grows toward `canvasSize`, never beyond.\n- **Force log \u002F zero baseline:** the `logScale_*` \u002F `includeZero_*` chart\n  properties above.\n\nGlobal layout tuning lives in the top-level `options` object (e.g.\n`addTooltips`, band padding, facet sizing). It is rarely needed for\nauthoring — omit it unless asked.\n\n## Worked examples\n\nIn each example `data` is a placeholder — the host binds real rows or a\nURL. You author only `chart_spec` and `semantic_types`.\n\n### Scatter plot\n\nUser: \"Plot car weight vs fuel economy, colored by origin.\"\n\n```json\n{\n  \"data\": { \"values\": [] },\n  \"semantic_types\": {\n    \"weight\": \"Quantity\",\n    \"mpg\": \"Quantity\",\n    \"origin\": \"Country\"\n  },\n  \"chart_spec\": {\n    \"chartType\": \"Scatter Plot\",\n    \"encodings\": {\n      \"x\": { \"field\": \"weight\" },\n      \"y\": { \"field\": \"mpg\" },\n      \"color\": { \"field\": \"origin\" }\n    },\n    \"baseSize\": { \"width\": 400, \"height\": 300 }\n  }\n}\n```\n\n### Revenue bar chart with facets, sorted by value\n\nUser: \"Show revenue by product line, biggest first, one panel per region.\"\n\n```json\n{\n  \"data\": { \"values\": [] },\n  \"semantic_types\": {\n    \"product_line\": \"Category\",\n    \"revenue\": \"Amount\",\n    \"region\": \"Region\"\n  },\n  \"chart_spec\": {\n    \"chartType\": \"Bar Chart\",\n    \"encodings\": {\n      \"x\": { \"field\": \"product_line\", \"sortBy\": \"y\", \"sortOrder\": \"descending\" },\n      \"y\": { \"field\": \"revenue\" },\n      \"column\": { \"field\": \"region\" }\n    }\n  }\n}\n```\n\n### Time series, multiple series (wide → long via array)\n\nUser: \"Line chart of monthly sales and profit.\"\n\n```json\n{\n  \"data\": { \"values\": [] },\n  \"semantic_types\": {\n    \"month\": \"YearMonth\",\n    \"sales\": \"Amount\",\n    \"profit\": \"Profit\"\n  },\n  \"chart_spec\": {\n    \"chartType\": \"Line Chart\",\n    \"encodings\": {\n      \"x\": { \"field\": \"month\" },\n      \"y\": [\"sales\", \"profit\"]\n    },\n    \"chartProperties\": { \"interpolate\": \"monotone\", \"showPoints\": true }\n  }\n}\n```\n\n### Donut chart (Pie + innerRadius), value on `size`\n\nUser: \"Show market share by vendor as a donut.\"\n\nPie\u002Fdonut maps the slice value to `size` (rendered as angle) and the\ncategory to `color`. Data is already long (one row per vendor).\n\n```json\n{\n  \"data\": { \"values\": [] },\n  \"semantic_types\": {\n    \"vendor\": \"Category\",\n    \"share\": \"Percentage\"\n  },\n  \"chart_spec\": {\n    \"chartType\": \"Pie Chart\",\n    \"encodings\": {\n      \"size\": { \"field\": \"share\" },\n      \"color\": { \"field\": \"vendor\" }\n    },\n    \"chartProperties\": { \"innerRadius\": 60 }\n  }\n}\n```\n\n### Bullet chart (KPI vs target)\n\nUser: \"Show each rep's sales against their quota.\"\n\n```json\n{\n  \"data\": { \"values\": [] },\n  \"semantic_types\": {\n    \"rep\": \"Name\",\n    \"sales\": \"Amount\",\n    \"quota\": \"Amount\"\n  },\n  \"chart_spec\": {\n    \"chartType\": \"Bullet Chart\",\n    \"encodings\": {\n      \"y\": { \"field\": \"rep\" },\n      \"x\": { \"field\": \"sales\" },\n      \"goal\": { \"field\": \"quota\" }\n    }\n  }\n}\n```\n\n## What you should NOT do\n\n- **Don't re-emit the data.** Reference columns by name; let the host bind\n  `data` (url, variable, or small literal). Never paste large datasets.\n- **Don't write backend specs directly** — write the `ChartAssemblyInput`,\n  then call the assembler. That's the whole point.\n- **Don't invent transforms.** The only built-in reshape is the array form\n  on `x`\u002F`y`. If the data shape is wrong for the chart, say so and ask the\n  host to reshape it.\n- **Don't invent field names.** Reference only columns that exist, spelled\n  exactly. If the data is the wrong shape for the chart, reshape it upstream\n  rather than guessing column names that aren't there.\n- **Don't set `type`\u002F`aggregate`\u002F`sortOrder`** unless intent conflicts\n  with the default.\n- **Don't pass colors, font sizes, axis tick counts** — the compiler\n  derives these. Users fine-tune the *output* spec. The exception is a colour\n  mapping the user asked for (a brand ramp, fixed bins, a pivot): use the\n  `color` encoding's `scheme`, `reverse`, `mid`, `domain`, `bins` and `colors`.\n- **Don't invent semantic type names.** If none fit, use the family\n  default (`Quantity`, `Category`, `Date`).\n- **Don't call the library to discover channels\u002Ftypes** — this document is\n  the authoring reference.\n\n## Validation checklist\n\nBefore returning, verify:\n\n1. `chartType` is an exact registered name supported by the target backend.\n2. Every `field` referenced in `encodings` is a real column name.\n3. Every encoded field has an entry in `semantic_types` (specific type).\n4. Required channels for the chart type are present (e.g. Bullet→`goal`,\n   Candlestick→`open\u002Fhigh\u002Flow\u002Fclose`, Pie→`size`+`color`).\n5. Any `chartProperties` keys are valid for that chart type and in range.\n6. You did **not** inline large data or hand-tune derived styling.\n7. The data carries no embedded total\u002Fsubtotal level (e.g. an `all` \u002F `total`\n   row) mixed with its components on a stacked, grouped, or colored channel.\n",{"name":5,"description":7},"main","a794557384adcc3293a77043f6b9aff1c33d152c","agent-skills\u002Fflint-chart-author",268,"https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fflint-chart","https:\u002F\u002Fmicrosoft.github.io\u002Fflint-chart\u002F","https:\u002F\u002Fgithub.com\u002Fmicrosoft","https:\u002F\u002Fopensource.microsoft.com","Open source projects and samples from Microsoft","2026-11-10 13:52:42",{"source":8,"skillName":5,"commands":56,"prompt":66,"zipAvailable":67},[57,60,63],{"tool":58,"command":59},"npx","npx skills add microsoft\u002Fflint-chart --skill flint-chart-author",{"tool":61,"command":62},"bunx","bunx skills add microsoft\u002Fflint-chart --skill flint-chart-author",{"tool":64,"command":65},"pnpm","pnpm dlx skills add microsoft\u002Fflint-chart --skill flint-chart-author","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fflint-chart\u002Fblob\u002Fmain\u002Fagent-skills\u002Fflint-chart-author\u002FSKILL.md",true,[],[70,87,99,111],{"skillNo":71,"name":72,"nameZh":6,"description":73,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":74,"categories":75,"keywords":82,"publishedAt":40},"01M69AXSRQVDB9W7S83SRE0ZSP","flint-theme-author","Use when: creating, translating, refining, reviewing, or validating a custom Flint ThemeSpec from brand guidelines, websites, slide decks, publication references, design tokens, or an existing visual identity. Produce a reusable ThemeSpec JSON object for Flint Theme Lab without inventing fields or changing chart semantics.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fflint-chart\u002Fblob\u002Fmain\u002Fagent-skills\u002Fflint-theme-author\u002FSKILL.md",[76,79],{"categoryNo":77,"categoryName":78,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":80,"categoryName":81,"taxonomyType":32,"taxonomyTypeDesc":33},"01JBSEEDCAT000000000000032","设计师",[83,84,85,86],"flint theme author","design","brand guidelines","identity",{"skillNo":88,"name":89,"nameZh":6,"description":90,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":91,"categories":92,"keywords":95,"publishedAt":40},"01M69AXPYV1F85RNSJM8FC9SXN","flint-interaction-author","Use when: a Flint chart should respond to the reader (highlight, zoom, brush, legend toggle, reorder, annotate, explore, link charts); an application or an agent changes a mounted chart from outside or reads what it shows; or a gesture no preset gives must be built. Produce one valid interaction_spec, one ChartUpdate, or code against flint-chart\u002Finteractive, with no invented names.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fflint-chart\u002Fblob\u002Fmain\u002Fagent-skills\u002Fflint-interaction-author\u002FSKILL.md",[93,94],{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":27,"categoryName":28,"taxonomyType":21,"taxonomyTypeDesc":22},[96,37,97,98],"flint interaction author","valid","preset",{"skillNo":100,"name":101,"nameZh":6,"description":102,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":103,"categories":104,"keywords":107,"publishedAt":40},"01M69AXHW6ENKKHP24X54B9KT4","flint-board-author","Use when: several Flint charts should be read together in one view: a dashboard, KPI tiles over a trend, a side-by-side comparison, an overview with its detail; or a board on screen should change (add, retitle, resize, reconnect a chart). Produce one board spec whose headline, chart choice, layout and connected fields are decided, check and refine it with validate_board, then put it on screen once with show.","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fflint-chart\u002Fblob\u002Fmain\u002Fagent-skills\u002Fflint-board-author\u002FSKILL.md",[105,106],{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":30,"categoryName":31,"taxonomyType":32,"taxonomyTypeDesc":33},[108,37,109,110,39],"flint board author","dashboard","change",{"skillNo":112,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":113,"categories":114,"keywords":119,"publishedAt":40},"01M69AXEZX3YWNKXJ5Y5P76B0W","https:\u002F\u002Fgithub.com\u002Fmicrosoft\u002Fflint-chart\u002Fblob\u002Fmain\u002F.github\u002Fextensions\u002Fflint-chart\u002Fskills\u002Fflint-chart-author\u002FSKILL.md",[115,116,117,118],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":27,"categoryName":28,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":30,"categoryName":31,"taxonomyType":32,"taxonomyTypeDesc":33},[35,39,36,38,37]]