# InsightCharts
> InsightCharts is an open-source React library of charts, figures, page layouts and infographic posters built on Apache ECharts 6. Components are organised as atoms, molecules and templates. The Infographics set is the templates that draw one designed poster, built from the poster primitives. Every component can be written as JSX or as a JSON spec. Each one carries machine-readable metadata that says when to use it.
This file is the complete agent documentation: 11 guides, then 100 component pages. Index: https://insightcharts.dev/llms.txt
## Quickstart
### Install
```bash
npm install insightcharts echarts
```
`react` (18 or later), `react-dom` and `echarts` (6 or later) are peer dependencies. There is no stylesheet to import: the small amount of CSS the figure chrome needs is injected once at runtime.
To copy component source into your project instead (so you can edit it), use the shadcn registry. The engine still comes from the npm package.
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json
npx shadcn@latest add @insightcharts/bar-chart
```
### A first chart
```tsx
import { BarChart } from "insightcharts";
const data = [
{ country: "Norway", share: 98 },
{ country: "Brazil", share: 89 },
{ country: "Canada", share: 68 },
];
export function Renewables() {
return (
);
}
```
Every chart follows the same shape:
1. `data`: rows (or CSV text, a URL, a function; see the data guide).
2. Field names as strings: `x`, `y`, `series`. No accessors, no functions.
3. Text: `title` (one plain statement of what the data shows, with its number and period, or a plain label), `subtitle` (measure, unit, population, period), `source`, `note`. The rules for writing them are in "Choosing a form".
4. Emphasis: `highlight` names the entity the title is about, and the other series are drawn in grey. `annotations` mark a date, a threshold or a single value.
The chart draws its own title, legend, source line, loading skeleton, error message and empty state ("No data to show."). It sizes to its container; `height` defaults to 360, or pass `aspect` (width divided by height).
### The same chart as JSON
```tsx
import { Insight } from "insightcharts";
```
Use JSX when you are writing a page. Use a spec when the chart is chosen at runtime (by an agent, a CMS, an API). See the JSON spec guide.
### Server and client components
- `insightcharts`, `insightcharts/core`, `insightcharts/story` and `insightcharts/themes` are client modules (the built files start with `"use client"`). In Next.js you can import a chart into a server component and pass it serialisable props.
- `insightcharts/data` is server-safe. Load on the server and pass rows down:
```tsx
import { loadData, transformRows } from "insightcharts/data";
import { LineChart } from "insightcharts";
export default async function Page() {
const rows = await loadData("https://example.com/energy.csv");
return ;
}
```
- Function props (`onSelect`, `onEvents`, `onReady`, a function `option`, a function `format`, a function `transform`, a function `data`) cannot cross the server/client boundary. Put the chart in a file that starts with `"use client"` when you need them.
### Optional provider
Charts work with no provider. `InsightProvider` sets defaults for everything beneath it, and providers nest.
```tsx
import { InsightProvider } from "insightcharts";
{children}
```
Its props: `theme`, `mode` (`"auto"`, `"system"`, `"light"`, `"dark"`), `renderer` (`"auto"`, `"svg"`, `"canvas"`), `locale`, `animate`, `exportable`, `lazy`.
Defaults: `lazy` is true, so a chart mounts when it scrolls within 200px of the viewport. `renderer="auto"` uses SVG up to about 1,500 marks and canvas beyond. Animation is skipped when the reader prefers reduced motion.
### Custom ECharts options
- `option`: an ECharts option deep-merged over the generated one, or a function `(option, ctx) => option`.
- `onReady(chart)` and a ref (`getInstance()`, `getOption()`, `export()`) give you the ECharts instance.
- `Chart` and `Figure` from `insightcharts/core` render a raw ECharts option inside the same figure chrome when no component fits. Core registers no series types itself, so register the ones you draw: `echarts.use([ScatterChart])` with `echarts` from `insightcharts/core` and `ScatterChart` from `echarts/charts`.
### Components
**Atoms (61)**
- [arc-diagram](https://insightcharts.dev/llms/arc-diagram.md): Places entities in order on a line and joins each related pair with a semicircular arc whose thickness is the value of the connection; dots are sized by each entity's total.
- [area-chart](https://insightcharts.dev/llms/area-chart.md): Shows a quantity over time as a filled area from zero: one series, stacked series, shares of 100%, or a streamgraph.
- [arrow-chart](https://insightcharts.dev/llms/arrow-chart.md): Draws an arrow per category from a start value to an end value, showing the direction and size of each shift.
- [bar-chart](https://insightcharts.dev/llms/bar-chart.md): Compares a value across categories with bars from a zero baseline.
- [beeswarm-chart](https://insightcharts.dev/llms/beeswarm-chart.md): Shows a distribution as one dot per row along a value axis, with dots stacked sideways where they would collide.
- [big-number](https://insightcharts.dev/llms/big-number.md): A featured statistic with its label, change and the context that gives it meaning.
- [block-bar](https://insightcharts.dev/llms/block-bar.md): Compares 3 to 12 values as thick rounded bars from a zero baseline, with each value printed inside the end of its bar and the category beneath.
- [box-plot](https://insightcharts.dev/llms/box-plot.md): Summarises a numeric field per group as median, quartile box, whiskers and outlier dots, computed from raw rows.
- [bubble-network](https://insightcharts.dev/llms/bubble-network.md): Draws a small set of circles sized by value and joined by thin lines, with one circle highlighted and tagged.
- [bubble-scale](https://insightcharts.dev/llms/bubble-scale.md): Places circles sized by one measure along a single value scale set by another, spread sideways so none overlap, with boxed scale marks instead of an axis.
- [bullet-chart](https://insightcharts.dev/llms/bullet-chart.md): Compares one measure per row with its target, optionally against qualitative bands such as poor / fair / good.
- [bump-chart](https://insightcharts.dev/llms/bump-chart.md): Shows how the ranking of entities changes over time, with rank 1 at the top and each line named at its end.
- [calendar-heatmap](https://insightcharts.dev/llms/calendar-heatmap.md): Colours every day of one or more years by a value, laid out as a grid of weeks.
- [callout](https://insightcharts.dev/llms/callout.md): A boxed statement of fact with a short bold lead and a sentence of explanation. The tone is marked by icon and tint.
- [choropleth-map](https://insightcharts.dev/llms/choropleth-map.md): Colours the regions of a map by a normalised value, from GeoJSON you supply.
- [circle-pack](https://insightcharts.dev/llms/circle-pack.md): Shows parts as tightly packed circles sized by value, optionally clustered into groups, with the value and name printed inside each circle.
- [curve-rank](https://insightcharts.dev/llms/curve-rank.md): Places each entity on two parallel scales, one per measure, and joins its two positions with a smooth S-curve labelled with the name and both values.
- [data-table](https://insightcharts.dev/llms/data-table.md): A restrained editorial table from rows and a column config: aligned tabular numbers, an optional in-cell bar, row highlight and click-to-sort headers.
- [delta](https://insightcharts.dev/llms/delta.md): An inline change indicator: direction arrow, signed value and an optional baseline label.
- [diverging-bar-chart](https://insightcharts.dev/llms/diverging-bar-chart.md): Draws bars either side of a centre line: positive against negative values, or stacked agree / disagree responses around a neutral midpoint.
- [donut-chart](https://insightcharts.dev/llms/donut-chart.md): Shows how 2 to 5 parts make up one whole, with each slice labelled by name and share.
- [dumbbell-chart](https://insightcharts.dev/llms/dumbbell-chart.md): Shows two values per category as dots joined by a line, so the length of the line shows the difference.
- [fan-flow](https://insightcharts.dev/llms/fan-flow.md): One solid block that splits into curved ribbons, each as thick as its value, ending at an aligned column of names, values and shares.
- [funnel-chart](https://insightcharts.dev/llms/funnel-chart.md): Shows how many remain at each step of a sequence, with conversion and drop-off between steps.
- [glyph-table](https://insightcharts.dev/llms/glyph-table.md): A table with one row per entity in which every number is drawn as a small form chosen per column (counted dots, squares or icons, a ring, a sized circle, a bar) with the value printed beside it.
- [headline](https://insightcharts.dev/llms/headline.md): The opening block of a piece or section: kicker, title, one-sentence deck and a byline/date line.
- [heatmap](https://insightcharts.dev/llms/heatmap.md): Colours a category-by-category (or time-by-category) grid by a numeric value, using a sequential or diverging scale.
- [histogram](https://insightcharts.dev/llms/histogram.md): Bins a numeric field and shows how many rows fall in each bin, as touching bars on a continuous axis.
- [icon-array-grid](https://insightcharts.dev/llms/icon-array-grid.md): Compares many counts as a grid of labelled tiles, each holding an array of N icons with its name and number.
- [icon-stat](https://insightcharts.dev/llms/icon-stat.md): A "1 in 4"-style statement: a row or grid of simple glyphs with some filled, and the sentence they illustrate.
- [legend-key](https://insightcharts.dev/llms/legend-key.md): A standalone colour key: categorical swatches, or a sequential/diverging ramp with end labels.
- [line-chart](https://insightcharts.dev/llms/line-chart.md): Shows how one or more values change over time or another ordered dimension.
- [lollipop-chart](https://insightcharts.dev/llms/lollipop-chart.md): Ranks categories with a dot on a thin stem from zero, or as a stemless dot plot on a free scale.
- [nested-shares](https://insightcharts.dev/llms/nested-shares.md): Shows several shares of the same whole, such as one share at five dates, as outlines nested from one corner of a square, each enclosing an area equal to its share.
- [orbit-chart](https://insightcharts.dev/llms/orbit-chart.md): Places items as dots on concentric rings round a centre, one ring per category, level or period, with a key that explains rings, angle and dot area.
- [pictogram-chart](https://insightcharts.dev/llms/pictogram-chart.md): Compares quantities with rows of repeated icons, each icon standing for a fixed number of units.
- [pill-rank](https://insightcharts.dev/llms/pill-rank.md): Shows a ranked list as horizontally centred rounded bars, with each name in capitals on its pill, the value at the left edge and the rank at the right.
- [progress-meter](https://insightcharts.dev/llms/progress-meter.md): A labelled horizontal meter of one value against a maximum or target, with the target marked and the value printed.
- [proportional-area](https://insightcharts.dev/llms/proportional-area.md): Compares two to eight values as squares or circles whose areas are proportional to the values, each printed with its name and number.
- [prose](https://insightcharts.dev/llms/prose.md): A readable block of narration at a 45-75 character measure, for the text between charts.
- [pull-quote](https://insightcharts.dev/llms/pull-quote.md): A large quotation with its attribution.
- [radial-bar-chart](https://insightcharts.dev/llms/radial-bar-chart.md): Draws bars around a circle for data that repeats in a cycle, such as hours of the day or months of the year.
- [radial-spokes](https://insightcharts.dev/llms/radial-spokes.md): Draws one spoke per row radiating from a centre disc, each as long as its value, with the name and value set on an outer ring.
- [ribbon-chart](https://insightcharts.dev/llms/ribbon-chart.md): One filled band per entity across periods: its thickness is its value in each period and its vertical position is its rank, so bands cross as the order changes.
- [sankey-chart](https://insightcharts.dev/llms/sankey-chart.md): Shows how a quantity flows from sources through stages to destinations, with link width proportional to the flow.
- [scale-ladder](https://insightcharts.dev/llms/scale-ladder.md): A tall vertical band graded along one scale (linear or log, running up or down) with each item placed at its true position and labelled with an icon, its name and its value on alternating sides.
- [scatter-chart](https://insightcharts.dev/llms/scatter-chart.md): Plots two numeric measures against each other, one dot per row, with optional bubble size, group colour, selective labels and a trend line.
- [segmented-bar](https://insightcharts.dev/llms/segmented-bar.md): Splits one whole into proportional segments along a single horizontal bar.
- [shape-fill](https://insightcharts.dev/llms/shape-fill.md): Shows one or more percentages as icon silhouettes filled from the bottom to that level, with the number printed large beside each.
- [slope-chart](https://insightcharts.dev/llms/slope-chart.md): Connects each entity's value at two (or a few) points with a straight line, labelled with name and value at both ends.
- [small-multiples](https://insightcharts.dev/llms/small-multiples.md): Repeats one small line, area or bar chart per value of a facet field on a shared scale, each panel titled.
- [source-note](https://insightcharts.dev/llms/source-note.md): A sourcing footer for a whole piece: sources with optional links, the period covered, method, caveats and credit.
- [sparkline](https://insightcharts.dev/llms/sparkline.md): A word-sized line or bar series without axes, with the latest point marked.
- [stat-circles](https://insightcharts.dev/llms/stat-circles.md): Shows a few related magnitudes as plain circles sized by area, each with its number set large and a short label.
- [stat-tile](https://insightcharts.dev/llms/stat-tile.md): A compact, bordered KPI tile: label, value, change against a baseline and an optional trend graphic.
- [step-marker](https://insightcharts.dev/llms/step-marker.md): A numbered chapter or step heading ("01 — Electricity sources in 2015") for sequencing the sections of a story.
- [strip-plot](https://insightcharts.dev/llms/strip-plot.md): Shows every observation as a tick or dot along a value axis, one row per category.
- [timeline-chart](https://insightcharts.dev/llms/timeline-chart.md): Places events and spans on a time axis in labelled lanes, with each item named directly.
- [treemap](https://insightcharts.dev/llms/treemap.md): Shows parts of a whole as nested rectangles sized by value, optionally grouped into categories.
- [unit-blocks](https://insightcharts.dev/llms/unit-blocks.md): Shows the size of each group as a solid block of small icons, one icon per fixed number of units.
- [waffle-chart](https://insightcharts.dev/llms/waffle-chart.md): Shows shares of a whole as counted cells in a grid, such as 37 in every 100.
**Molecules (19)**
- [annotated-timeline](https://insightcharts.dev/llms/annotated-timeline.md): A vertical sequence of dated events along a rail, each with a short explanation and, optionally, a small chart or figure.
- [before-after](https://insightcharts.dev/llms/before-after.md): Two states of one measure side by side, as figures or small charts, with the change between them stated in the middle.
- [chart-callout](https://insightcharts.dev/llms/chart-callout.md): A chart with a callout beside it that states the main fact. In narrow containers the callout goes underneath.
- [chart-tabs](https://insightcharts.dev/llms/chart-tabs.md): One chart frame with a segmented control that switches between several views of the same data, morphing between views that share a chart type.
- [comparison-table](https://insightcharts.dev/llms/comparison-table.md): A table whose rows carry inline sparklines, deltas and in-cell bars beside their exact values, from rows and a column config.
- [filter-chips](https://insightcharts.dev/llms/filter-chips.md): The active crossfilter selections of the surrounding `InsightData`, shown as removable chips with a reset control.
- [hero-figure](https://insightcharts.dev/llms/hero-figure.md): The opening exhibit of a piece: kicker, headline and deck above one large chart, with a source line underneath.
- [kpi-grid](https://insightcharts.dev/llms/kpi-grid.md): A responsive grid of trend cards drawn from one dataset: each metric shows its latest value, its change and a sparkline.
- [linked-charts](https://insightcharts.dev/llms/linked-charts.md): Two to four charts over one shared dataset in a responsive grid: clicking a mark in one filters the others, with the active filters shown as removable chips.
- [metric-breakdown](https://insightcharts.dev/llms/metric-breakdown.md): A headline number with its composition underneath: one segmented bar and a key giving each part's value and share.
- [poster](https://insightcharts.dev/llms/poster.md): The sheet an infographic is composed on. It paints a full-bleed ground from a poster theme, forces one theme and colour mode on everything inside, and lays out a masthead, a grid of panels and a footer. It can save the whole sheet as one PNG.
- [proportion-statement](https://insightcharts.dev/llms/proportion-statement.md): A share stated as a countable sentence ("37 in every 100…"): a waffle grid or a row of icons beside the statement in large type, with the number emphasised.
- [quote-figure](https://insightcharts.dev/llms/quote-figure.md): A pull-quote paired with the statistic or small chart that backs it up, side by side and stacking in narrow containers.
- [ranked-list](https://insightcharts.dev/llms/ranked-list.md): A ranking as a list: position, name, value and a thin in-row bar, with an optional change indicator and highlighted rows.
- [scrolly-chart](https://insightcharts.dev/llms/scrolly-chart.md): A scroll-driven story from JSON: one base chart spec and a list of steps, each with narration and a patch of chart props applied while that step is active.
- [section](https://insightcharts.dev/llms/section.md): A story section: a numbered step marker or a headline, introductory prose, then one or more exhibits in a responsive one- to three-column grid.
- [small-multiple-grid](https://insightcharts.dev/llms/small-multiple-grid.md): Repeats any chart spec once per value of a facet field, each panel with its own filtered rows and a title, in a grid that follows the container width.
- [stat-row](https://insightcharts.dev/llms/stat-row.md): Two to five key figures in one row separated by hairlines, collapsing to two columns and then one as the container narrows.
- [trend-card](https://insightcharts.dev/llms/trend-card.md): A KPI card computed from a series: the latest value, its change against a baseline in the same data, and a sparkline of the series.
**Templates (20)**
- [atlas-poster](https://insightcharts.dev/llms/atlas-poster.md): A landscape spread of up to eleven small illustrations of one subject, each a different graphic form, in three bands of unequal panels: a statement in large type, spokes round an icon and a network of circles; a smooth trend with its peak labelled, nested squares and a slope chart; a pill ranking, a treemap, a tile map and sized circles; and a closing row of icon statements.
- [comparison-brief](https://insightcharts.dev/llms/comparison-brief.md): A compared with B on one page. It has a headline that states the result, the headline figures mirrored either side of their labels, one hero exhibit that shows both sides, paired exhibits on a shared scale, a verdict callout and a source note.
- [count-poster](https://insightcharts.dev/llms/count-poster.md): A poster that counts at scale. Blocks of thousands of unit icons fill most of the sheet, a second band counts reference groups with the same icon at the same size in a muted colour, and a bottom row holds three small graphics: squares sized by area with a ratio bracket, thick bars with the numbers inside, and shares as nested outlines or filled silhouettes.
- [data-explorer](https://insightcharts.dev/llms/data-explorer.md): A set of linked charts over one shared dataset that filter each other. It has a headline, a sentence of instructions with filter chips, a row of figures that recompute from the filtered rows, two to four charts and an optional detail table. It opens on selections the author chooses.
- [executive-brief](https://insightcharts.dev/llms/executive-brief.md): A one-page summary. It has a headline that states the main finding with its number, a deck, a row of three or four key figures, one hero chart with a takeaway callout, two smaller supporting exhibits, a short list of actions and a source note.
- [explainer](https://insightcharts.dev/llms/explainer.md): A vertical infographic in one narrow column. It opens with a big number or a proportion statement (`hook`), then the hero exhibit, then numbered sections with one exhibit each, a conclusion with an optional link, and sources.
- [flow-poster](https://insightcharts.dev/llms/flow-poster.md): A portrait poster of where something comes from and where it goes, in three stacked bands painted in one colour: the producers as large labelled tiles, the leading producer's output fanning out as ribbons to an aligned column of destinations, and the destinations as bubbles on a scale of how much each depends on that producer.
- [matrix-poster](https://insightcharts.dev/llms/matrix-poster.md): One wide landscape graphic that compares 4 to 12 entities on five to seven measures: two measures as S-curves between parallel scales on the left, joined to a table on the right in which every other number is drawn as dots, squares, icons, a ring, a circle or a bar. Both halves share one row order and one colour per entity.
- [network-poster](https://insightcharts.dev/llms/network-poster.md): A poster of who is connected to whom: a full-width arc diagram with one arc per connection, as thick as its value (`arc-diagram`), a strip of two to four large figures, and a bottom row where the same entities return as circles joined by lines (`bubble-network`) and as a ranking of centred pills (`pill-rank`).
- [profile-poster](https://insightcharts.dev/llms/profile-poster.md): A poster that describes one subject from four sides: the subject as one large silhouette filled to a share (`shape-fill`), two to four large figures, its place among other things on one scale (`scale-ladder`), and a table that sets it beside its peers with every number drawn as a small form (`glyph-table`).
- [radial-poster](https://insightcharts.dev/llms/radial-poster.md): A poster with one subject in the middle of the sheet: a wheel of spokes round an icon (`radial-spokes`), two to four large figures standing either side of it, and a bottom row with the same subject's surroundings on concentric rings (`orbit-chart`) and a ranking as centred pills (`pill-rank`).
- [ranking-poster](https://insightcharts.dev/llms/ranking-poster.md): A poster of one subject by the numbers, in four rows: two figures in large type or as filled silhouettes, two treemaps side by side, a full-width ranking of centred pills with one row highlighted, and packed circles with icons beside a column of sized circles and a closing figure.
- [report-page](https://insightcharts.dev/llms/report-page.md): A section of an analytical report laid out as a document. It has a title block, a summary, numbered findings, then method, limitations and sources. Each finding has one exhibit, an observation and a separately labelled interpretation with a confidence rating and a caveat.
- [scale-poster](https://insightcharts.dev/llms/scale-poster.md): A tall poster that is one illustrated scale: a full-height `scale-ladder` with icons, leader-labelled items and named zones, and a narrow side column of large figures and one small graphic, each set level with the part of the scale it describes.
- [scorecard](https://insightcharts.dev/llms/scorecard.md): A recurring report. It has a masthead with the report name and period, a headline that states the main change, a KPI grid with changes and sparklines, a ranked list beside one or two trend charts, an optional comparison table, short notes and a source note.
- [social-card](https://insightcharts.dev/llms/social-card.md): A graphic that states one fact, at a fixed aspect ratio (1:1, 4:5, 16:9 or 9:16). It has a kicker, a headline, one exhibit, a source and a credit, painted on the theme surface. A button downloads the whole card as a PNG.
- [story-deck](https://insightcharts.dev/llms/story-deck.md): A sequence of full-frame cards (title, stat, chart, quote, closing) that the reader swipes or steps through. Each card states one fact. It has an optional fixed aspect ratio, a keyboard-accessible pager and a stacked layout for print.
- [timeline-poster](https://insightcharts.dev/llms/timeline-poster.md): A poster of change over a run of periods: a full-width ribbon of bands whose thickness is each entity's value and whose order is its rank (`ribbon-chart`), a strip of two to four dated figures under it, and a bottom row with one measure per period as thick bars (`block-bar`) and one share at several dates as nested outlines (`nested-shares`).
- [timeline-story](https://insightcharts.dev/llms/timeline-story.md): A story told in date order. It has a headline, one overview exhibit of the whole period (a timeline chart or an annotated line), dated chapters along a rail, a closing paragraph and a source note. Each chapter has a short text and an optional small exhibit.
- [visual-essay](https://insightcharts.dev/llms/visual-essay.md): A long article read by scrolling. It has a headline, then an ordered list of blocks in a reading column, then a source note. The blocks are prose, figures, charts with a callout, scrolly sequences, pull quotes, stat rows, small multiples and chapter breaks. Exhibits can be wider than the column.
---
## Choosing a form
First write down the one fact the chart should show, with its number. Then pick the form that shows that fact most directly.
### Decision table
Generated from each component's metadata. Use only names in this table.
| The data's job | Component | Level | Use it when | Pick something else when |
|---|---|---|---|---|
| State one number or one fact | [big-number](https://insightcharts.dev/llms/big-number.md) | atom | A single headline figure, or a row of three or four key numbers. Always give it context: a comparison, a denominator or a period. | Showing a number with no comparison; using a gauge or a one-slice donut for the same job. |
| State one number or one fact | [data-table](https://insightcharts.dev/llms/data-table.md) | atom | When readers need to look up exact values, or compare a handful of entities across several measures with different units. Sort it by the column the caption refers to and highlight the row it names. | Showing a trend or a single comparison (use `line-chart` or `bar-chart`); more than about 7 columns or 30 rows without `maxHeight`. |
| State one number or one fact | [delta](https://insightcharts.dev/llms/delta.md) | atom | Next to a figure, in a sentence, a table cell or a tile, to say how much it changed and against what. Set `upIsGood: false` for measures where a fall is the good news (churn, emissions, wait times). | Standing alone as the main figure (use `big-number` or `stat-tile`, which include a delta). Do not omit the baseline label unless the surrounding text names it. |
| State one number or one fact | [icon-stat](https://insightcharts.dev/llms/icon-stat.md) | atom | A single proportion that is easier to read as people or units than as a percentage: 1 in 4, 3 in 10, 37 of 100. Round to a small denominator first. | Comparing several proportions (use `bar-chart`), precise percentages (use `big-number`), or fractions that need more than 100 units. |
| State one number or one fact | [legend-key](https://insightcharts.dev/llms/legend-key.md) | atom | When several charts (small multiples, a map and a bar chart, a dashboard row) share one colour encoding, show the key once above them and turn their own legends off. | A single chart: its built-in legend or, better, direct labels already do this. Do not list more than eight categories. |
| State one number or one fact | [progress-meter](https://insightcharts.dev/llms/progress-meter.md) | atom | Progress toward a goal, a quota or a capacity: funds raised, budget spent, share of a target reached. Stack several with `size: "sm"` to compare progress across a few items. | Comparing more than a handful of items (use `bar-chart`), anything without a meaningful end point (use `big-number`), or a gauge dial. |
| State one number or one fact | [stat-tile](https://insightcharts.dev/llms/stat-tile.md) | atom | Dashboard grids and KPI rows of three to six figures, each needing its own change indicator or sparkline. Give every tile a delta or a context line. | The single number a story leads with (use `big-number` with `size: "hero"`). Do not put a full chart with axes in the trend slot. |
| Compare values across categories | [arrow-chart](https://insightcharts.dev/llms/arrow-chart.md) | atom | Shifts between two moments across up to about 25 categories when some rise and some fall: swings between elections, before / after a policy. | When both levels matter more than the direction (use dumbbell-chart); more than two points in time (use slope-chart or line-chart); a single value per category (use bar-chart). |
| Compare values across categories | [bar-chart](https://insightcharts.dev/llms/bar-chart.md) | atom | Ranking or comparing up to about 20 categories; grouped for a few series, stacked when the total and its composition both matter. | Change over many time points (use line-chart); two values per category (use dumbbell-chart); more than about 5 stacked segments. |
| Compare values across categories | [bullet-chart](https://insightcharts.dev/llms/bullet-chart.md) | atom | Progress against a target or budget for one to about 10 measures, including measures in different units (`scale: "independent"`); a compact scorecard instead of a row of gauges. | No target or thresholds to compare with (use bar-chart); a single share of a whole (use progress-meter); change over time (use line-chart). |
| Compare values across categories | [diverging-bar-chart](https://insightcharts.dev/llms/diverging-bar-chart.md) | atom | Values that can be above or below zero (net change, balance, deviation from average); survey scales where the split between the two sides matters; two opposed groups back to back. | Values that are all positive (use bar-chart); parts that do not have a natural two-sided order (use a stacked bar-chart); change between two dates (use arrow-chart or dumbbell-chart). |
| Compare values across categories | [dumbbell-chart](https://insightcharts.dev/llms/dumbbell-chart.md) | atom | Before / after or group A / group B across up to about 20 categories, when the size of the difference is what the reader needs. | A single value per category (use lollipop-chart or bar-chart); when the direction of change matters more than the two levels (use arrow-chart); more than two points in time (use slope-chart or line-chart). |
| Compare values across categories | [heatmap](https://insightcharts.dev/llms/heatmap.md) | atom | Finding patterns across two dimensions at once: hour by weekday, cohort by month, product by region. Good when there are too many series for lines or bars and the overall pattern matters more than exact values. | Precise comparison of a few values (use bar-chart); a single series over time (use line-chart); a daily value across a year (use calendar-heatmap). |
| Compare values across categories | [lollipop-chart](https://insightcharts.dev/llms/lollipop-chart.md) | atom | Rankings of 8 to 30 categories where bars would be too heavy; dot plot mode when the values sit far from zero and the differences between them matter, or to compare a few series per row. | Fewer than about 6 categories or part-to-whole stacks (use bar-chart); exactly two values per row where the difference between them matters (use dumbbell-chart); change over time (use line-chart). |
| Compare values across categories | [pictogram-chart](https://insightcharts.dev/llms/pictogram-chart.md) | atom | A few categories (2 to 6) of countable things, people, homes, vehicles, where making the quantity concrete matters more than reading precise values. | More than about 8 categories or values that need precise comparison (use bar-chart); a share of one whole (use waffle-chart); values that would need more than about 60 icons in a row (raise `unit`, or use bar-chart). |
| Show change over time | [area-chart](https://insightcharts.dev/llms/area-chart.md) | atom | A volume or cumulative quantity over time where the filled magnitude matters; stacked when the total and its composition both matter; percent when only the mix matters; stream for the changing mix of many series over a long period. | Comparing the trajectories of several series against each other (use line-chart: only the bottom band of a stack has a flat baseline); a few time points (use bar-chart with stack); more than about 6 bands. |
| Show change over time | [bump-chart](https://insightcharts.dev/llms/bump-chart.md) | atom | League tables over 3 to about 15 periods for up to about 12 entities, when the order and the changes in rank are what matter and the underlying values differ too much (or too little) to share an axis. | When the size of the values matters (use line-chart); only two periods (use slope-chart); a single ranking (use bar-chart or lollipop-chart). |
| Show change over time | [calendar-heatmap](https://insightcharts.dev/llms/calendar-heatmap.md) | atom | Daily data over months or years where weekly rhythm, seasonality, streaks and gaps matter: activity, sales, incidents, habits. | Reading the trend or exact values (use line-chart); fewer than about three months of days (use bar-chart); a non-daily grain such as hour by weekday (use heatmap). |
| Show change over time | [line-chart](https://insightcharts.dev/llms/line-chart.md) | atom | Trends over time with roughly 5 or more points; comparing the trajectories of a few series; one highlighted series against many context series. | Unordered categories (use bar-chart); only two time points (use slope-chart); more than about 6 equally important lines (use small-multiples). |
| Show change over time | [radial-bar-chart](https://insightcharts.dev/llms/radial-bar-chart.md) | atom | Only when the dimension is cyclical and the wrap-around matters: activity by hour of day, seasonality by month, wind by compass direction. The first category is placed at 12 o'clock. | Any dimension that does not wrap around: categories, rankings, years (use bar-chart); precise comparison of values, since bars further out look bigger (use bar-chart or line-chart); more than about 3 stacked series. |
| Show change over time | [slope-chart](https://insightcharts.dev/llms/slope-chart.md) | atom | Before / after for 3 to about 15 entities when the change in level and in ordering both matter; pair with `highlight` to emphasise one line. | Five or more time points (use line-chart); when only the ordering matters (use bump-chart); when only the size of each gap matters (use dumbbell-chart or arrow-chart). |
| Show change over time | [small-multiples](https://insightcharts.dev/llms/small-multiples.md) | atom | The same measure for 4 to about 24 entities when one chart would have too many overlapping lines; pair with `highlight` to pick out one panel, or `context` to show every other line faintly behind each. | Three or fewer series (use line-chart with direct labels); when exact comparison between two entities matters most (use line-chart with `highlight`); a single value per entity (use bar-chart). |
| Show change over time | [sparkline](https://insightcharts.dev/llms/sparkline.md) | atom | Showing the recent history of a number inside a stat tile, a table cell or a sentence, where the shape of the trend matters and exact values do not. | Anything the reader must read values from or compare across series (use line-chart); fewer than about 5 points (state the change in words or use big-number with a delta). |
| Show change over time | [timeline-chart](https://insightcharts.dev/llms/timeline-chart.md) | atom | When things happened and how long they lasted: project phases, terms of office, product launches, the sequence of events in a story. Mix spans (start and end) with point milestones (start only). | A measured value changing over time (use line-chart, with annotations for the events); more than about 40 items (filter, or split into several timelines); durations to be compared precisely with no interest in when (use bar-chart). |
| Show how values are spread | [beeswarm-chart](https://insightcharts.dev/llms/beeswarm-chart.md) | atom | Showing a distribution while keeping every individual visible: 30 to about 2,000 named things (countries, players, schools), optionally in a few rows by group, with one or two highlighted. | More than about 2,000 rows or when only the summary matters (use histogram or box-plot); many groups with few rows each (use strip-plot); two numeric measures (use scatter-chart). |
| Show how values are spread | [box-plot](https://insightcharts.dev/llms/box-plot.md) | atom | Comparing the centre and spread of a numeric value across several groups, especially with many rows per group and an audience comfortable with quartiles. | A general audience or fewer than about 20 rows per group (use strip-plot or beeswarm-chart, which show the observations themselves); one group whose shape matters, such as two peaks, which a box hides (use histogram). |
| Show how values are spread | [histogram](https://insightcharts.dev/llms/histogram.md) | atom | Showing the shape of a distribution for one numeric variable with roughly 50 rows or more: its centre, spread, skew, gaps and multiple peaks. | Comparing distributions across several groups (use box-plot or beeswarm-chart); under about 50 rows, where every observation can be shown (use strip-plot or beeswarm-chart); counts by category (use bar-chart). |
| Show how values are spread | [strip-plot](https://insightcharts.dev/llms/strip-plot.md) | atom | Comparing the spread and clustering of a numeric value across 2 to about 15 categories when there are few enough observations to show each one; many rows with few to a few hundred observations each. | A single group where the shape matters (use histogram or beeswarm-chart); when individual points must be identifiable or coloured (use beeswarm-chart); when only quartiles matter (use box-plot). |
| Show how a total divides | [donut-chart](https://insightcharts.dev/llms/donut-chart.md) | atom | A simple composition with one dominant part, or one part the title is about, where the total itself is worth printing in the centre. | More than 5 categories or parts of similar size that must be ranked (use bar-chart); a single figure (use big-number); composition compared across groups or time (use bar-chart with stack "percent"); a compact card (use segmented-bar). |
| Show how a total divides | [segmented-bar](https://insightcharts.dev/llms/segmented-bar.md) | atom | A compact part-to-whole for cards, table cells and stat rows: budget split, device mix, vote share. The legend carries names in block order. | Comparing compositions across several groups (use bar-chart with stack "percent"); more than about 6 parts or many slivers (use bar-chart); when the reader should count units (use waffle-chart). |
| Show how a total divides | [treemap](https://insightcharts.dev/llms/treemap.md) | atom | Many parts (10 to 100) of very different sizes, or a two-level hierarchy, where the reader needs the largest parts rather than exact comparison: budgets, market capitalisation, export baskets. | Fewer than about 6 parts or values that need precise comparison (use bar-chart); 2 to 5 parts (use donut-chart or segmented-bar); values that can be negative (use bar-chart); change over time (use line-chart). |
| Show how a total divides | [waffle-chart](https://insightcharts.dev/llms/waffle-chart.md) | atom | One share ("37 in every 100") or a handful of categories that add up to a whole, where the reader should be able to count the units. | Comparing shares across several groups (use bar-chart with stack "percent"); more than about 6 categories or shares under 1% (use bar-chart); a compact inline part-to-whole (use segmented-bar). |
| Show how two measures relate | [scatter-chart](https://insightcharts.dev/llms/scatter-chart.md) | atom | Showing whether and how two measures move together, spotting clusters and outliers, and placing named entities in a two-dimensional space (with quadrant lines via `annotations`). | A measure over time (use line-chart); one numeric measure by category (use beeswarm-chart or strip-plot); fewer than about 8 points (use bar-chart or a table); more than 4 colour groups (facet with small-multiples). |
| Show movement between stages or groups | [funnel-chart](https://insightcharts.dev/llms/funnel-chart.md) | atom | A strict sequence where each stage is a subset of the one before: sign-up flows, hiring pipelines, sales stages, applications to admissions. 3 to 7 stages. | Stages that are not sequential subsets, or categories with no order (use bar-chart); flows that split to several destinations (use sankey-chart); conversion tracked over time (use line-chart). |
| Show movement between stages or groups | [sankey-chart](https://insightcharts.dev/llms/sankey-chart.md) | atom | Where things come from and where they go: energy from fuel to end use, budget from revenue to spending, users from channel to outcome. Works best with 2 to 4 stages and up to about 20 nodes. | A single sequence of steps with drop-off (use funnel-chart); comparing a few totals (use bar-chart); flows that loop back (cycles cannot be drawn and are dropped); more than about 40 links. |
| Show values by place | [choropleth-map](https://insightcharts.dev/llms/choropleth-map.md) | atom | A rate, share or per-capita figure that varies by place, where the geographic pattern (a north-south divide, a cluster) is what the reader needs to see. Use a diverging scale for change or for values either side of a meaningful midpoint. | Raw counts, which mostly map population (normalise first, or use bar-chart); ranking regions precisely (use bar-chart); a handful of regions with no spatial pattern (use bar-chart); regions so different in size that small ones vanish (use a tile-grid GeoJSON). |
| Draw a poster graphic | [arc-diagram](https://insightcharts.dev/llms/arc-diagram.md) | atom | Connections among up to about 20 entities that have a meaningful order (stops on a line, chapters, age groups, teams by size): trips between stations, trade between neighbours, characters sharing scenes. | Flows with a direction through stages (use sankey-chart); one source splitting into parts (use fan-flow); a full matrix of every pair where exact values matter (use heatmap); more than about 20 entities or 60 connections (use heatmap). |
| Draw a poster graphic | [block-bar](https://insightcharts.dev/llms/block-bar.md) | atom | A short series or ranking that should read as a bold graphic with no axis: ticket sales in each of six years, two paired measures in five regions. Add `detail` for a second small figure above each bar and `marker` for an event between two bars. | More than 12 bars, more than two series or stacked parts (use bar-chart); negative values (use diverging-bar-chart); a long time series (use line-chart); values that differ by more than about 50 times, where the short bars vanish (use proportional-area). |
| Draw a poster graphic | [bubble-network](https://insightcharts.dev/llms/bubble-network.md) | atom | Showing which of up to about 12 named things are connected, and how big each is: trade blocs, route maps between hubs, partnerships, shared ownership. Give `x` and `y` to place the circles by hand, for example roughly geographically. | Flows with magnitudes between stages (use sankey-chart); more than about 12 circles or a dense web of links (use heatmap as a matrix, or data-table); comparison of the sizes alone (use bar-chart or circle-pack). |
| Draw a poster graphic | [bubble-scale](https://insightcharts.dev/llms/bubble-scale.md) | atom | A poster answer to "where does each one sit on this scale, and how big is it": 5 to about 40 named things with a rate or score (the scale) and a magnitude (the area), such as import dependency against share of demand. | Two measures that should be read against each other on two axes (use scatter-chart); one measure only (use beeswarm-chart or strip-plot); precise comparison of the sizes (use bar-chart); more than about 60 rows (use beeswarm-chart). |
| Draw a poster graphic | [circle-pack](https://insightcharts.dev/llms/circle-pack.md) | atom | A poster view of 5 to about 60 parts of very different sizes where the reader should see the largest ones and their rough proportions at once: budgets by programme, emissions by source, users by product. One level of grouping is supported. | Values that need precise comparison (use bar-chart, where bars share a baseline); parts that must visibly add up to a whole (use treemap or waffle-chart); more than about 80 parts (use treemap); negative values (use diverging-bar-chart). |
| Draw a poster graphic | [curve-rank](https://insightcharts.dev/llms/curve-rank.md) | atom | Two different measures of the same 4 to about 15 entities, when the point is how standing on one relates to standing on the other: price against sales, staff against output, cost against usage. Each side has its own scale, stated in its heading. | The same measure at two moments (use slope-chart); ranks across many periods (use bump-chart); more than about 15 entities or a statistical relationship (use scatter-chart); exact comparison on one measure (use bar-chart). |
| Draw a poster graphic | [fan-flow](https://insightcharts.dev/llms/fan-flow.md) | atom | One total divided among 4 to about 14 destinations (or collected from that many origins, with `direction: "in"`) when the picture of the split is the point: where a budget goes, what a day's water is used for, which sources feed one total. | Flows with more than one stage or several sources (use sankey-chart); precise comparison of the parts (use bar-chart); 2 to 4 parts (use segmented-bar or donut-chart); values that can be negative (use bar-chart). |
| Draw a poster graphic | [glyph-table](https://insightcharts.dev/llms/glyph-table.md) | atom | Comparing 3 to about 12 entities across 2 to 5 measures with different units, when the table should read as a graphic: profiles of products, cities, teams or species. Pair it with curve-rank by passing both the same `order`. | Looking up many exact values, sorting, or more than about 12 rows (use data-table); one measure across entities (use bar-chart or pictogram-chart); a trend over time (use line-chart or small-multiples). |
| Draw a poster graphic | [icon-array-grid](https://insightcharts.dev/llms/icon-array-grid.md) | atom | Counts of up to about 150 in 4 to 24 categories, especially rates per 100 or per 1,000: cars per 100 households in 12 districts. Set `total` so every tile shows the same full array with the uncounted icons as ghosts. | Counts in the thousands (use unit-blocks); two to six categories where one row of icons each is enough (use pictogram-chart); values that need precise comparison or ranking (use block-bar or bar-chart); one share of one whole (use waffle-chart). |
| Draw a poster graphic | [nested-shares](https://insightcharts.dev/llms/nested-shares.md) | atom | How much of one whole something was at 2 to 8 dates or in 2 to 8 categories, when the growth of a share is the point: bicycles made 4% of trips in 2005 and 27% in 2025. | Shares that are parts of each other and should add up (use waffle-chart or treemap); more than 8 items or precise reading of a trend (use line-chart); shares within a point or two of each other, where outlines coincide (use block-bar). |
| Draw a poster graphic | [orbit-chart](https://insightcharts.dev/llms/orbit-chart.md) | atom | A designed alternative to a table for 6 to about 40 named things grouped by distance, level or era from one centre: suppliers by tier, moons by planet-distance band, releases by decade, stakeholders by closeness. One item can be highlighted. | A numeric distance that must be read exactly (use strip-plot or scatter-chart); comparison of the dot values (use bar-chart); more than about 6 rings or 40 items (use data-table); hierarchy with sizes that add up (use treemap or circle-pack). |
| Draw a poster graphic | [pill-rank](https://insightcharts.dev/llms/pill-rank.md) | atom | A poster ranking of 4 to about 20 named things where the order and the overall silhouette matter more than fine differences: top cities, largest employers, league tables, with one row highlighted and optional reference rows set apart below. | Precise comparison of values (use bar-chart, where bars share a baseline); negative values (use diverging-bar-chart); change in rank over time (use bump-chart); more than about 25 rows (use data-table). |
| Draw a poster graphic | [proportional-area](https://insightcharts.dev/llms/proportional-area.md) | atom | A few quantities that differ a lot (2 times to 100 times), where the reader should feel the difference in size: lake areas, budgets, market sizes. Nest them to show one quantity inside another. | Values within about 30% of each other, where areas look the same (use block-bar or bar-chart); more than 8 values (use treemap or bar-chart); parts of one whole (use treemap or waffle-chart); negative values. |
| Draw a poster graphic | [radial-spokes](https://insightcharts.dev/llms/radial-spokes.md) | atom | A poster of 4 to 12 effects, causes or attributes of one subject, which sits in the centre as an icon or a short label: what a household's energy goes on, the side effects of one drug, the scores of one product. | Precise comparison or a ranking (use bar-chart or pill-rank); a dimension that is a cycle, such as hours or months (use radial-bar-chart); more than 12 rows (use bar-chart); several subjects compared on the same attributes (use small-multiples or heatmap). |
| Draw a poster graphic | [ribbon-chart](https://insightcharts.dev/llms/ribbon-chart.md) | atom | 4 to 8 entities over 4 to 10 periods when both the size of each and the changing order matter, and the graphic should read as one flowing form: market shares, harvests, medal tables, budget lines. | Totals that matter more than order, or a smooth centred stream of many periods (use area-chart, which has stacked and stream modes); rank alone (use bump-chart); more than 8 entities or 10 periods (use line-chart or small-multiples); values that can be negative (use line-chart). |
| Draw a poster graphic | [scale-ladder](https://insightcharts.dev/llms/scale-ladder.md) | atom | Answering how deep, how high, how hot, how old or how far each of 4 to about 20 things is on one shared scale, when the reader needs the positions themselves: depths, altitudes, temperatures, ages, distances. Use `zones` to name ranges along the scale. | Comparing sizes rather than positions (use bar-chart or lollipop-chart); events with start and end dates (use timeline-chart); a distribution of many unnamed values (use strip-plot or histogram). |
| Draw a poster graphic | [shape-fill](https://insightcharts.dev/llms/shape-fill.md) | atom | One to six shares of a full amount where the object itself says what is measured: how full a reservoir is, how charged a fleet is, what share of homes have a heat pump. | The fill height only approximates the share, because an irregular silhouette holds less area at its narrow end, so the number must always stay printed; for values to be compared precisely use block-bar or bar-chart, for a counted share use waffle-chart, for a compact inline meter use progress-meter. |
| Draw a poster graphic | [stat-circles](https://insightcharts.dev/llms/stat-circles.md) | atom | The poster way to show two to six related magnitudes whose rough proportion is the point: kilograms per person in three countries, the same quantity in three years, a total and its parts set side by side. | More than six values or values that need precise comparison (use bar-chart); a single number (use big-number); many circles of one whole (use circle-pack); values close together, where areas look the same (use bar-chart or dumbbell-chart). |
| Draw a poster graphic | [unit-blocks](https://insightcharts.dev/llms/unit-blocks.md) | atom | Large counts of people or things in 2 to 12 groups, where the reader should see the crowd itself: 20,350 marathon finishers by age group, one figure per 10 runners. | Precise comparison of close values (use block-bar or bar-chart); fewer than about 30 icons per group (use pictogram-chart or icon-array-grid); shares of one whole (use waffle-chart). |
| Draw a poster graphic | [poster](https://insightcharts.dev/llms/poster.md) | molecule | Use it as the outer frame of any infographic: one dominant graphic with a few supporting panels, on a painted ground. Give it a poster theme (`noir`, `magenta`, `dusk`, `paper`, `ocean`, `forest`, `blueprint`, `sunrise`) and put a chart or figure spec in each panel. Put the one fact in `title` and keep panel headings short. It reflows to one column under 600px. | Long reading text or more than about six panels (use `report-page` or `visual-essay`). A single fact at a fixed social size (use `social-card`). Charts that need axes, gridlines and dense labels to be read, because a poster ground suits large flat marks with their values printed on them. |
| Lay out a whole poster | [atlas-poster](https://insightcharts.dev/llms/atlas-poster.md) | template | Use it when one subject has many small facts of different kinds (shares, places, a trend, a change, a ranking, a map) and none of them should dominate: a crop, an industry, a service, a country's internet. Fill at least eight regions. Give every panel a short heading that states its one number. Every region is optional, and each band shares its width among the panels that are present. | One dominant fact or graphic (use `count-poster`, `flow-poster` or `ranking-poster`). The same measures across several entities (use `matrix-poster`). Fewer than six panels (use `poster` and place them yourself). A phone-first piece: the spread becomes one long column under 600px. |
| Lay out a whole poster | [count-poster](https://insightcharts.dev/llms/count-poster.md) | template | Use it when the subject is a large count of people or things in 2 to 6 groups and the reader should see the crowd: pupils by stage, trees planted by species, vehicles built by model. Give `hero` the groups and `reference` one to three quantities to compare them with. Both bands use the same `unit`, `icon` and icon size, so their areas compare. State the total in the title. | Counts under a few hundred (use `pictogram-chart` in a `poster`). Shares of one whole as the main point (use `waffle-chart` or `treemap`). A ranking of many named things (use `ranking-poster`). A flow from sources to destinations (use `flow-poster`). |
| Lay out a whole poster | [flow-poster](https://insightcharts.dev/llms/flow-poster.md) | template | Use it when one producer leads and the reader should follow its output: ports and the buyers of one port's catch, regions and the users of one region's electricity, terminals and the routes out of the largest. Give `producers` 4 to 10 rows, `flow` 4 to 12 destinations of the leading producer, and `scale` the same destinations with a share (position) and a size. Keep the destination names identical in `flow` and `scale` so `linked` can join them. | Flows through more than one stage or between many sources and many destinations (use `sankey-chart` in a `poster`). A count of people or things (use `count-poster`). Many measures of the same entities (use `matrix-poster`). |
| Lay out a whole poster | [matrix-poster](https://insightcharts.dev/llms/matrix-poster.md) | template | Use it for profiles of a handful of comparable things with measures in different units: cities, species, products, stations, teams. Pass one row per entity with every measure in it. Put the two measures whose relationship matters in `curve` and the rest in `columns`, choosing a counted glyph (`dots`, `blocks`, `icons`) for small whole numbers, `ring` for shares, `bar` or `circle` for magnitudes. Leave `highlight` out to give every entity its own colour, derived from the theme when its palette is smaller than the number of entities; set it to draw one or two entities in colour and the rest in grey. | One measure across entities (use `pill-rank` or `bar-chart`). More than 12 entities (use `data-table` or `heatmap`). A subject with a total and its parts (use `ranking-poster` or `flow-poster`). A phone-first piece: the table stacks into cards under 600px and the poster becomes long. |
| Lay out a whole poster | [network-poster](https://insightcharts.dev/llms/network-poster.md) | template | Use it when the connections between 5 to about 20 named things are the subject: ferry crossings between harbours, joint papers between institutes, freight between rail yards. Give `hero` one row per connection and the `order` the entities sit in, `map` the entities with a size and optional hand-placed `x` and `y`, and `rank` one measure of each entity. With `linked`, a click on an entity in any of the three highlights it in the other two. | A flow from sources to destinations in one direction (use `flow-poster` or `sankey-chart`). More than about 20 entities or 40 connections, because the arcs cover each other. Entities with no meaningful order and few links (use `bubble-network` in a `poster`). Parts of one subject (use `radial-poster`). |
| Lay out a whole poster | [profile-poster](https://insightcharts.dev/llms/profile-poster.md) | template | Use it for a fact sheet about one named thing: a vehicle, a reservoir, a building, a machine, a species. Give `portrait` one row with the share that defines the subject and the `icon` that stands for it, `figures` the numbers a reader should take away, `scale` the subject among 4 to 10 reference points, and `table` the subject beside 2 to 6 peers. Highlight the subject in `scale` and `table`. On a sheet at least 960px wide the silhouette, the figures and the scale stand in three columns. | Several subjects of equal weight (use `glyph-table` in a `poster`, or `ranking-poster`). A subject whose parts are the point (use `radial-poster`). A subject with no defining share: leave `portrait` to a full spec such as `stat-circles`, or use `scale-poster` when the scale is the whole subject. Change over time (use `timeline-poster`). |
| Lay out a whole poster | [radial-poster](https://insightcharts.dev/llms/radial-poster.md) | template | Use it when the poster is about the 4 to 12 parts, causes, effects or scores of one thing: what a household's water goes on, the species one tree supports, where one home's energy goes. Give `hero` the spokes and an `icon` for the centre, state the total in the title, and use `figures` for the two to four numbers a reader should take away. | Parts of a whole that must be compared precisely (use `bar-chart` in a `poster`). Positions on one scale (use `scale-poster`). Change over time (use `timeline-poster`). Connections between many things (use `network-poster`). Large counts shown as crowds (use `count-poster`). |
| Lay out a whole poster | [ranking-poster](https://insightcharts.dev/llms/ranking-poster.md) | template | Use it for a profile of one event, place or organisation that has a headline total, two ways to split it, a ranking of 6 to 12 named things and a few supporting magnitudes: a festival, a race, a nature reserve, a library service. Put the total in the title and repeat it as the first figure. Highlight the ranking row the heading is about. | A subject whose point is a count of people or things (use `count-poster`). Several entities compared on the same measures (use `matrix-poster`). Eight or more small graphics on one subject (use `atlas-poster`). A ranking alone (use `pill-rank` in a `poster`). |
| Lay out a whole poster | [scale-poster](https://insightcharts.dev/llms/scale-poster.md) | template | Use it when the positions of 6 to about 16 named things on one scale are the whole subject: depths, altitudes, temperatures, distances, ages. Give the scale's rows and zones in `scale`, up to five `figures` with the scale position each belongs to, and optionally a `side` graphic of two to four magnitudes. Beside the scale the figures line up with their positions; under 760px they move below it. | Comparing sizes rather than positions (use `ranking-poster` or `count-poster`); change over time (use `timeline-poster`); more than about 16 items, because the labels run together; several unrelated graphics (use `poster` and lay the panels out yourself). |
| Lay out a whole poster | [timeline-poster](https://insightcharts.dev/llms/timeline-poster.md) | template | Use it when the subject is how 4 to 8 named things grew, shrank and changed places over 4 to 10 periods: electricity by source, library loans by format, commuters by mode. Give `hero` the periods and entities, `milestones` the dated numbers a reader should take away, `bars` one measure of the whole at each period and `shares` one share at a few dates. State the latest value in the title. | A single series over time (use `line-chart` or `area-chart` in a `poster`). Events with start and end dates (use `timeline-chart` or the `timeline-story` template). More than 8 entities or 10 periods, because the bands get too thin to label. Positions on one scale (use `scale-poster`). Parts of one subject at one date (use `radial-poster`). |
| State something in words | [callout](https://insightcharts.dev/llms/callout.md) | atom | Beside or under a chart, to state its main fact in words, with the number. Also for a caveat the reader must not miss (`negative`). | Long narration (use `prose`), a featured number (use `big-number` or `stat-tile`), or more than one callout per chart. |
| State something in words | [headline](https://insightcharts.dev/llms/headline.md) | atom | At the top of a data story (`page`), at the start of each chapter (`section`), or as the heading of a tile (`card`). Write the title as one plain fact with its number. Write the deck as a second plain sentence. | Titling a single chart: charts carry their own `title` and `subtitle`. For numbered chapters use `step-marker`. |
| State something in words | [prose](https://insightcharts.dev/llms/prose.md) | atom | To say what a chart showed and introduce the next one. Use two or three short paragraphs at a time. | A single statement of fact (use `callout`), a quotation (use `pull-quote`), or sourcing and method notes (use `source-note`). |
| State something in words | [pull-quote](https://insightcharts.dev/llms/pull-quote.md) | atom | To quote a person on a finding the charts have already shown, or to break up a long run of charts. One per section at most. | Restating a number (use `big-number`) or the main fact of a chart (use `callout`). Never quote without attribution. |
| State something in words | [source-note](https://insightcharts.dev/llms/source-note.md) | atom | Once, at the end of a story or dashboard, to say where every number came from and how it was produced. | Sourcing a single chart: use that chart's own `source` and `note` props so the citation travels with its export. |
| State something in words | [step-marker](https://insightcharts.dev/llms/step-marker.md) | atom | When a piece has three to seven ordered chapters or steps. Open each one with a marker so the reader can see the position in the sequence. | Unordered sections (use `headline` with `size: "section"`), or numbering a list of items inside a paragraph. |
| Arrange several pieces | [before-after](https://insightcharts.dev/llms/before-after.md) | molecule | A single measure at two moments or under two conditions: before and after a policy, control and treatment, last year and this. The change is computed from the two values unless you state it. | More than two states (use `line-chart` or `slope-chart`); many entities each with a before and after (use `dumbbell-chart` or `arrow-chart`); two unrelated figures (use `stat-row`). |
| Arrange several pieces | [chart-callout](https://insightcharts.dev/llms/chart-callout.md) | molecule | Whenever a chart needs its meaning stated in words next to it: the standard chart-plus-interpretation unit of a report. The chart's title states the main fact with its number. The callout adds a second fact, a cause or a caveat in plain words. | Repeating the chart title in the callout; long narration (put `prose` before the chart instead); a chart that is already a small tile (the callout would outweigh it). |
| Arrange several pieces | [chart-tabs](https://insightcharts.dev/llms/chart-tabs.md) | molecule | The same entities under two to five measures or scalings where each view changes the reading: total / per capita / change, absolute / share, linear / log. Give the frame a plain label as its title. Each view's `subtitle` names its measure. | Hiding the most important view behind a tab (show it first, or show both with `small-multiple-grid`); unrelated charts (give each its own figure); more than five views. |
| Arrange several pieces | [comparison-table](https://insightcharts.dev/llms/comparison-table.md) | molecule | Comparing five to twenty entities on a level, a trend and a change at once: countries, products, teams. Readers look up their row. The sparkline and delta show how the value changed. | Plain lookups with no series (use `data-table`); one measure ranked (use `ranked-list` or `bar-chart`); trends the reader must compare precisely (use `small-multiples`). Each sparkline has its own scale, so it shows the direction of change and cannot be used to compare levels between rows. |
| Arrange several pieces | [filter-chips](https://insightcharts.dev/llms/filter-chips.md) | molecule | Inside any `` whose charts use `select`: put it above the charts so readers can see what is filtered, drop one value, or clear everything. `linked-charts` already includes it. | Outside `` it renders nothing unless you pass `selections` yourself. It is not a filter builder: selections are made by clicking marks in the charts. |
| Arrange several pieces | [kpi-grid](https://insightcharts.dev/llms/kpi-grid.md) | molecule | The top of a dashboard or a monthly report: three to eight metrics tracked over the same periods. Pass one table with a column per metric. | Figures without a series behind them (use `stat-row`); comparing the metrics with each other on one scale (use `line-chart` or `small-multiples`); more than eight cards. |
| Arrange several pieces | [linked-charts](https://insightcharts.dev/llms/linked-charts.md) | molecule | Exploration of one table along several dimensions: by region, by category and over time. Pass row-level data once; each chart aggregates it with its own `transform`, sets `select: true` and omits `data`. | A report that states one fixed fact (use `chart-callout` or `scrolly-chart`); charts over unrelated datasets (they cannot filter each other); more than four charts. |
| Arrange several pieces | [metric-breakdown](https://insightcharts.dev/llms/metric-breakdown.md) | molecule | A total that the reader will immediately want split: a budget by department, revenue by product line, emissions by sector. Two to six parts that add up to the number above them. | Parts that do not sum to the headline; more than about six parts (use `bar-chart`); composition compared across groups or over time (use `bar-chart` with `stack: "percent"` or `area-chart`). |
| Arrange several pieces | [ranked-list](https://insightcharts.dev/llms/ranked-list.md) | molecule | A top-N list where the names matter as much as the values: league tables, best and worst performers, leaderboards with a movement column. It keeps long names readable and works in a narrow column. | Precise visual comparison of many values or more than about 15 rows (use `bar-chart`); several measures per entity (use `data-table` or `comparison-table`); rank changing over time (use `bump-chart`). |
| Arrange several pieces | [small-multiple-grid](https://insightcharts.dev/llms/small-multiple-grid.md) | molecule | Faceting a chart form the `small-multiples` atom does not draw: stacked bars, scatter plots, donuts, waffles, dumbbells, histograms. Also when each panel should be a full chart with its own tooltip and axis. | Line, area or bar facets that must share one value scale: use the `small-multiples` atom, which draws every panel in one chart on a common axis. Here each panel scales to its own rows unless the spec fixes the axis (for example `option: { xAxis: { max: 70 } }`), so say so in the subtitle or fix it. More than about twelve panels. |
| Arrange several pieces | [stat-row](https://insightcharts.dev/llms/stat-row.md) | molecule | The opening summary of a report or dashboard: the two to five most important numbers. Give every figure a delta or a context line, and order them by importance. | More than five figures (use `kpi-grid`), a single lead number (use `big-number` with `size: "hero"`), or figures that need their history shown (use `kpi-grid`, or `variant: "tiles"` with a `trend`). |
| Arrange several pieces | [trend-card](https://insightcharts.dev/llms/trend-card.md) | molecule | A metric whose recent history matters as much as its level: users, revenue, response time. Pass the series and field names; the figure, the change and the sparkline all come from the same rows, so they cannot disagree. | A number with no history (use `big-number` or `stat-tile`); several metrics from one dataset (use `kpi-grid`); a series the reader must read values from (use `line-chart`). |
| Arrange several pieces | [comparison-brief](https://insightcharts.dev/llms/comparison-brief.md) | template | Use it for two things a reader has to choose between or compare, such as two countries, two products or a before and an after. Write the result with its number in the headline. Put the four or five most important measures in `figures`. Use a two-series line, dumbbell, slope or `before-after` as the hero. In each pair, use the same chart form and the same scale for both sides. The `subject` side takes the accent colour everywhere and the other side stays grey. | Three or more things (use a `comparison-table` or `small-multiple-grid` inside a `report-page`). One thing over time with nothing to compare it with (use `timeline-story`). Paired charts on different scales, because readers compare bar lengths across the pair. |
| Arrange several pieces | [executive-brief](https://insightcharts.dev/llms/executive-brief.md) | template | Use it when a decision-maker will spend about a minute on the page. State the main finding with its number and period in the headline. The headline and the hero chart should be enough for a reader who stops there. The figures, supporting exhibits and actions add detail. Use one accent colour for the subject (`highlight`) and leave everything else grey. Mark the cause on the hero as an annotation. The proportions suit a printed page. | More than one hero or more than three supporting exhibits (use `report-page`). A recurring set of numbers with no single finding (use `scorecard`). A story told in several steps (use `visual-essay`). |
| Arrange several pieces | [report-page](https://insightcharts.dev/llms/report-page.md) | template | Use it for readers who will check the reasoning, as in evaluations, research notes, audits and experiment write-ups. State the main finding with its number in the title. Put all the main findings and the recommendation in the summary. Write each finding title as one fact with its number, and give it an exhibit that shows that fact. Put what the data shows in `observation` and what you infer from it in `interpretation`. End with method, limitations and sources. | A decision-maker who wants only the result (use `executive-brief`). A general audience (use `explainer` or `visual-essay`). Findings without an exhibit. Put those in the summary. |
| Arrange several pieces | [scorecard](https://insightcharts.dev/llms/scorecard.md) | template | Use it for weekly or monthly numbers. Keep the layout the same in every edition so readers know where each number is. Change the headline each edition to state the main change with its number. The headline and the KPI row should be enough for a reader who stops there. The ranking, trends and notes add detail. Highlight the one row or series the headline is about and leave the rest grey. | A one-off finding (use `executive-brief`). Readers who need to filter the data themselves (use `data-explorer`). More than six KPIs. |
| Arrange several pieces | [social-card](https://insightcharts.dev/llms/social-card.md) | template | Use it for one finding that will be shared in a feed, a slide or a chat thread with no text around it. State the fact with its number in the headline. Use one simple exhibit, such as one highlighted bar, one line or one number. Put the source and your name on the card, because it will be shared without the page. Use 1:1 or 4:5 for feeds, 16:9 for slides and link previews, 9:16 for stories. | More than one fact or more than one exhibit (use `story-deck` for a sequence, `executive-brief` for a page). Charts that need a legend, small labels or hovering to read, because a shared image is not interactive. An exhibit with its own `title`, because the card's headline is the title. |
| Show a sequence of steps | [annotated-timeline](https://insightcharts.dev/llms/annotated-timeline.md) | molecule | Telling how something unfolded when the events matter more than a continuous measure: a policy history, an incident, a product's milestones. Four to eight events; give a figure only to the ones where a number changed. | Events that should be read against a time scale, with gaps and overlaps to scale (use `timeline-chart`); a continuous series with a few annotated moments (use `line-chart` with `annotations`); ordered steps that are not dated (use `section` with `step`). |
| Show a sequence of steps | [hero-figure](https://insightcharts.dev/llms/hero-figure.md) | molecule | At the very top of a data story or a one-page report. The headline states the main fact with its number. The chart shows that fact. | Giving the chart its own `title` (the headline is the title; use the chart's `subtitle` for the measure). A second hero on the same page: later sections use `section` or `chart-callout`. |
| Show a sequence of steps | [proportion-statement](https://insightcharts.dev/llms/proportion-statement.md) | molecule | One proportion that is easier to read as a count of people or units than as a percentage. Round to a small whole first: 1 in 4 with icons, 37 in every 100 with a waffle. | Several shares side by side (use `bar-chart`, or `waffle-chart` with categories); a precise percentage where the decimals matter (use `big-number`); totals above 100. |
| Show a sequence of steps | [quote-figure](https://insightcharts.dev/llms/quote-figure.md) | molecule | A quotation shown next to the number it refers to: an interview finding with its survey figure, an executive's statement with the metric. One per section. | A quote with no evidence (use `pull-quote`); a figure that needs axes to read (use `chart-callout` and put the quote in prose); unattributed quotations. |
| Show a sequence of steps | [scrolly-chart](https://insightcharts.dev/llms/scrolly-chart.md) | molecule | Walking a reader through one chart in stages: show everything, highlight the subject, change the measure, add the annotation. Make ONE change per step (the highlight, the data or the encoding) so the reader can see what changed. | Steps that change several things at once, or that swap to an unrelated chart (start a new `section` instead). More than about seven steps. A fact that one view can show (use `chart-callout`). In documents that will be printed or embedded in a short frame, set `scrollMode: "steps"`. |
| Show a sequence of steps | [section](https://insightcharts.dev/llms/section.md) | molecule | The building block of a longer piece. Each section states one fact with its number in the title, explains it in a paragraph and shows it in one to three exhibits. Use `step` when the sections are read in order. | The opening of a piece (use `hero-figure`); a single chart with one callout (use `chart-callout` directly); more than three exhibits in one section (split it). |
| Show a sequence of steps | [data-explorer](https://insightcharts.dev/llms/data-explorer.md) | template | Use it when readers will want to filter the data themselves after seeing the main finding. State the finding with its number in the headline. Set `initialSelections` so the piece opens on the view the headline describes. Use the figures and the `finding` callout to give the numbers for that view. Keep to one dataset and two to four small, plain charts. Readers click the charts to filter, so keep them all the same size. | A fixed message that needs no exploration (use `executive-brief` or `visual-essay`). Charts over unrelated datasets, which cannot filter each other. More than four charts or five figures. |
| Show a sequence of steps | [explainer](https://insightcharts.dev/llms/explainer.md) | template | Use it to explain one subject to a general audience in a single narrow column. The column also works as a tall image or on a phone screen. State the main finding with its number in the headline and repeat the main number in `hook`. Make the hero exhibit the largest thing on the page. Give each numbered section one heading, one exhibit and one sentence with its main number. Write each section so it can be read without the others. Keep one accent colour on the subject (`highlight`) throughout. | Findings that need caveats and separate interpretation (use `report-page`). A chart that has to change step by step (use `visual-essay` with a scrolly block). More than five sections, because readers stop before the end. |
| Show a sequence of steps | [story-deck](https://insightcharts.dev/llms/story-deck.md) | template | Use it for a short sequence read on a phone, such as a “year in review”. Each card should be readable in a couple of seconds. State the main fact with its number on the title card. Put one fact on each later card, as a number or as one chart with one highlighted mark under a headline that states the fact. Use five to ten cards. End with a closing card and the sources. Charts in cards should have few data points and direct labels, with no legend. | Findings that need two exhibits in view at once (use `executive-brief`). A single shareable image (use `social-card`). Dense charts or long paragraphs, because a card has little room. One chart that changes step by step (use `scrolly-chart` in `steps` mode). |
| Show a sequence of steps | [timeline-story](https://insightcharts.dev/llms/timeline-story.md) | template | Use it when the reader needs the events in date order to understand a change. State the change over the whole period in the headline, with the numbers and dates. Mark the main events on the overview exhibit as annotations. Give each chapter one event. Add a small figure to a chapter only when it has a number to show. Mark the two or three most important events with `emphasis`. | Events with no measure that runs through the period (use a plain `annotated-timeline`). A comparison of two states with nothing in between (use `comparison-brief`). More than eight chapters. Merge minor events into the text of the chapter before or after. |
| Show a sequence of steps | [visual-essay](https://insightcharts.dev/llms/visual-essay.md) | template | Use it for a finding that takes several charts and some text to explain. State the main finding with its number in the headline. Put the main figure within the first screen. Alternate text and charts, with two or three short paragraphs before each exhibit. In a scrolly block change one thing per step, such as the highlight, the year or an annotation. Use one accent colour for the subject in every chart. | A summary for someone who will not scroll (use `executive-brief`). Reference material read out of order (use `report-page`). More than one or two scrolly blocks. Use one only where the chart changes between steps. |
When two forms fit, use the simpler one. When nothing fits, use a sentence with a `big-number`.
### Poster primitives
The rows above with the job "Draw a poster graphic" are the poster primitives. They are for panels of a `poster` and for the regions of the infographic templates. Each prints names and values on or beside large flat marks and has no axes by default. Use the ordinary chart for a report page and the primitive for a poster.
| The data's job | Primitive | Ordinary chart for a report page |
|---|---|---|
| Count people or things in 2 to 12 groups, in the thousands | `unit-blocks` | `pictogram-chart` |
| Compare counts of up to about 150 in 4 to 24 categories | `icon-array-grid` | `pictogram-chart` |
| Compare 2 to 8 quantities that differ by 2 to 100 times | `proportional-area` | `bar-chart` |
| State 2 to 6 related magnitudes with the number set large | `stat-circles` | `stat-row` |
| Show 5 to about 60 parts of very different sizes | `circle-pack` | `treemap` |
| Show one share at 2 to 8 dates, or 2 to 8 shares of one whole | `nested-shares` | `waffle-chart` |
| Show 1 to 6 percentages as a filled silhouette | `shape-fill` | `icon-stat` |
| Compare a short series or ranking of 3 to 12 values | `block-bar` | `bar-chart` |
| Rank 4 to about 20 named things | `pill-rank` | `bar-chart`, `ranked-list` |
| Relate two measures of the same 4 to about 15 entities | `curve-rank` | `slope-chart`, `scatter-chart` |
| Show size and changing rank of 4 to 8 entities over 4 to 10 periods | `ribbon-chart` | `bump-chart`, `area-chart` |
| Place 4 to about 20 things on one scale (depth, height, age) | `scale-ladder` | `strip-plot` |
| Place 5 to about 40 things on one scale with a magnitude as area | `bubble-scale` | `beeswarm-chart` |
| Show 4 to 12 attributes of one subject | `radial-spokes` | `bar-chart` |
| Group 6 to about 40 named things by level or period round a centre | `orbit-chart` | `comparison-table` |
| Split one total among 4 to about 14 destinations | `fan-flow` | `sankey-chart` |
| Show connections among up to about 20 ordered entities | `arc-diagram` | `sankey-chart` |
| Show which of up to about 12 things are connected, with sizes | `bubble-network` | `sankey-chart` |
| Compare 3 to about 12 entities on 2 to 5 measures with different units | `glyph-table` | `comparison-table` |
A whole poster is an infographic template (the job "Lay out a whole poster") or the `poster` molecule. See the infographics guide.
### Writing titles and text
These rules apply to every `title`, `subtitle`, deck, callout, annotation label and story step. They are hard requirements.
Write one plain statement of fact, the way a person would say it out loud, with the real numbers and the period in it.
| Write this | Not this |
|---|---|
| Monthly churn fell from 4.2% to 2.1% in 2024 | Churn halved after the March onboarding revamp, and the gain has held for six months |
| Renewables supplied 98% of Norway's electricity in 2023 | Norway runs almost entirely on renewable electricity |
| Acme had 266 active teams in December 2024, 2.4 times Globex | Acme pulled away from the pack in late 2023 |
| Solar's share rose from 2.7% to 5.5% between 2019 and 2023 | Solar is closing the gap with wind |
| Households wasted 631 million tonnes of food in 2022 | It is not a rich-country habit |
- One clause. Do not add a second half joined with a comma, "and", "but", "while", "yet", a semicolon, a colon or a dash that adds a twist, a consequence or a follow-up. If there are two facts, write two sentences or put the second in the subtitle.
- Put the number in. Write "fell from 4.2% to 2.1%", not "halved". Write "98%", not "almost entirely". Write "3 of 12", not "a handful".
- Take every number from the data. Never invent a figure, a cause or an event that the data does not show.
- No metaphor, idiom or figure of speech. Do not write "pulled away from the pack", "closing the gap", "tells the story", "the long tail" or "did the work".
- No teasing. Do not point at something the reader has to work out, such as "one hospital explains most of it" or "here is where it goes".
- No dramatic verbs (soared, plunged, surged, skyrocketed, slashed). No staged contrast ("not X, but Y").
- No rhetorical questions, no groups of three for rhythm, no sign-off lines.
- A plain label is always acceptable when there is no single fact to state: "Monthly churn, 2024".
- Subtitles name the measure, unit, population and period. Sources name the source.
### Design rules
The components enforce some of these. You are responsible for the others.
1. **State one fact in the title.** `title` is one plain statement with its number and period, or a plain label. Put the measure, unit, population and period in `subtitle`.
2. **Highlight one series.** Set `highlight` to the one or two entities the title is about. The other series are drawn in grey. Do not colour every series when the title is about one of them.
3. **Label marks directly.** Charts label line ends and bar tips where they can and show a legend only for two or more series. Do not add a legend for something that is already labelled.
4. **Use few annotations.** `annotations` mark a date, a threshold or a single value: a `line` at a target or a date, a `band` over a period, a `point` label on one value. Use two or three per chart at most.
5. **Start bars and areas at zero.** Bars and filled areas start at zero, and the components do this for you. Lines and dots may use a value axis that does not start at zero. If you rebase or use a log scale, say so in the subtitle.
6. **No dual axes, no gauges, no pie with more than 5 slices.** Two measures get two charts (or an index). A single figure is a `big-number`. Many parts become a ranked bar chart with the smallest folded into "Other" (the `top` transform does this).
7. **Change one thing per scroll step.** In a `Scrolly` story each step changes one thing: the data, the highlight or the encoding.
8. **Do not rely on tooltips.** The chart must be readable without hovering. Touch screens and exported images have no hover.
9. **Always give a source.** Pass `source`. Use `note` for method caveats, and say when data is illustrative.
10. **Give a big number context.** Add a comparison, a denominator or a period with `context`, `delta` and `deltaLabel` on `big-number`. Write "31.7%, up from 19% in 2010", not "31.7%" alone.
### How colour is assigned
You do not pick colours per series. The theme assigns them:
- Identity (which entity): the categorical palette, in stable entity order. More than 8 entities should be folded or split into small multiples.
- Emphasis: `highlight`. The first highlighted entity takes the accent colour, others take the next categorical colours, and the remaining series take the context grey.
- Magnitude and polarity: sequential and diverging ramps from the theme.
- Good or bad: `positive` and `negative`, always with a second cue such as an arrow or sign.
To change colours, change the theme (see the theming guide), not individual charts.
### Checklist
- Is the title one clause that states one fact with its number and period, or a plain label?
- Can every number in the text be found in or computed from the data?
- Is exactly one thing highlighted?
- Can it be read with no hover, at 380px wide, and in dark mode?
- Is there a `subtitle` naming the measure and a `source`?
- Would a sentence or a `big-number` show the same fact?
---
## Common chart props
Every chart built with `defineChart` accepts these on top of its own props. HTML components (big-number, callout, ...) do not. All are JSON-serialisable except the callbacks (`onSelect`, `onEvents`, `onReady`) and the function forms of `data`, `transform`, `format` and `option`. Generated from `CommonChartProps` in `packages/insightcharts/src/core/define.tsx`.
| Prop | Type | Description |
|---|---|---|
| `data` | `DataInput` | Rows, CSV text, a URL, `{ url, path }`, an async function, or an Arrow-style table. Omit inside ``. |
| `transform` | `Transform \| Transform[]` | A pipeline of transforms applied before drawing. |
| `title` | `string` | The title. One plain statement of what the data shows, with the number and the period ("Monthly churn fell from 4.2% to 2.1% in 2024"), or a plain label ("Monthly churn, 2024"). One clause, no figures of speech. |
| `subtitle` | `string` | Measure, unit, population, period. |
| `source` | `string` | Data source, shown as "Source: ...". |
| `note` | `string` | Method notes or caveats. |
| `height` | `number \| string` | CSS height of the plot. Default 360. |
| `aspect` | `number` | Width / height. Overrides `height`. |
| `highlight` | `string \| string[]` | The entity (or entities) the title is about. Every other series is drawn in grey. |
| `annotations` | `Annotation[]` | Reference lines, shaded bands and callouts, in data coordinates. |
| `format` | `FormatSpec` | How to print values: `"compact"`, `"percent"`, `"currency:USD"`, `"{value} kg"`, Intl options, or a function. |
| `legend` | `boolean` | Show the legend. Default: shown when there are two or more series. |
| `select` | `boolean \| string` | Make marks clickable to filter. Inside `` the selection filters every other chart. Pass a field name, or `true` to use the chart's category field. |
| `onSelect` | `(values: string[], field: string) => void` | Called with the selected values whenever the selection changes. |
| `option` | `EChartsOption \| ((option: EChartsOption, ctx: BuildContext) => EChartsOption)` | Deep-merged over the generated ECharts option, or a function that receives and returns it. Use it for ECharts settings no prop covers. |
| `theme` | `ThemeInput` | A full theme, `{ light, dark }` partial tokens, or flat tokens applied to both modes. Merged over the surrounding `InsightProvider` theme. |
| `mode` | `ModeInput` | `"auto"` (default) follows the page, `"system"` follows the OS, or force `"light"` / `"dark"`. |
| `renderer` | `RendererInput` | `"auto"` (default) uses SVG for light charts and canvas for heavy ones. |
| `animate` | `boolean` | Set false to disable animation for this chart. Reduced-motion preferences are always respected. |
| `lazy` | `boolean` | Mount the chart only when it scrolls within 200px of the viewport. Default true (from `InsightProvider`). |
| `group` | `string` | Link tooltips with other charts sharing this group id. |
| `exportable` | `boolean \| ExportFormat[]` | Show an export menu: `true` or a list of formats from png, jpeg, svg, pdf, csv, json. |
| `table` | `boolean` | Show a collapsible "View data" table under the chart. |
| `alt` | `string` | Accessible description: the chart type and measures, then the main pattern and the notable values. |
| `id` | `string` | Stable id for this chart's selection in the shared store. |
| `onEvents` | `Record` | ECharts event handlers: `{ click: (params, chart) => {} }`. |
| `onReady` | `(chart: EChartsInstance) => void` | Called with the ECharts instance once it is initialised. |
| `className` | `string` | Class on the figure element. |
| `style` | `CSSProperties` | Inline style on the figure element. |
---
## Data
Charts work on an array of plain row objects. The `data` prop accepts many shapes and normalises them to rows; `transform` then reshapes those rows. Both are JSON-serialisable, so a whole pipeline can live in a spec.
### Accepted `data` shapes
| Shape | Example | Notes |
|---|---|---|
| Row objects | `[{ year: 2020, value: 1 }]` | The native form. |
| Array of arrays | `[["year", "value"], [2020, 1]]` | The first array is the header. |
| Array of primitives | `[3, 5, 8]` | Becomes `{ index, value }` rows. |
| Columnar object | `{ year: [2020, 2021], value: [1, 2] }` | Every value must be an array or typed array. |
| Table object | Apache Arrow table, DuckDB result, Arquero table | Anything with `toArray()`. Rows with `toJSON()` are converted. |
| API envelope | `{ data: [...] }` | An array under `data`, `rows`, `results`, `items` or `records` is found automatically. |
| CSV text | `"year,value\n2020,1"` | Any string that is not a URL. Delimiter is detected from `,` tab `;` and the pipe character. |
| URL string | `"/data/energy.csv"` | A single-line string with no comma that starts with `http://`, `https://`, `/`, `./`, `../` or `data:`. |
| Remote source | `{ url, format, path, init, refresh }` | `format`: `csv`, `tsv`, `json`, `ndjson` (otherwise inferred from the extension, then the content type, then the content). `path`: dot path to the rows inside a JSON response, e.g. `"data.results"`. `init`: passed to `fetch`. `refresh`: re-fetch interval in ms. |
| Inline CSV | `{ csv: "...", delimiter: ";" }` | |
| Inline JSON | `{ json: anything, path: "a.b" }` | |
| Function or promise | `() => fetchRows()` or a `Promise` | Resolves to any shape above. Not JSON; client components only. |
Omit `data` on a chart inside `` and it reads the shared rows.
If no row array can be found, the chart shows an error telling you to pass `path`.
#### CSV type inference
CSV cells are converted: numbers (including `1,234` thousands separators), `true`/`false`, and `42%` (becomes `0.42`). Empty cells, `NA`, `N/A`, `null` and `NaN` become `null`. Values with a leading zero (`007`, zip codes) stay strings. Dates stay strings; ISO date strings (`2024-03-01`, `2024-03`) are recognised by charts as temporal. Use the `parse` transform to force a type.
#### Loading behaviour
- In-memory data resolves synchronously: no loading flash.
- URLs, functions and promises load asynchronously; the chart shows a skeleton, then the chart, or an error message with the HTTP status.
- URL loads are de-duplicated and cached for the page's lifetime, unless the source has `init` or `refresh`.
- Inline literals written in JSX are compared by content, so `data={[...]}` does not reload on every render. Arrays over 500 rows are compared by identity: keep a stable reference (a module constant or `useMemo`).
On the server, or anywhere outside React:
```ts
import { loadData, transformRows, parseCsv } from "insightcharts/data";
const rows = transformRows(await loadData({ url: "https://example.com/api", path: "data.results" }), [
{ type: "filter", field: "year", op: ">=", value: 2015 },
]);
```
### Wide and long data
Charts with `x`, `y` and `series` accept both layouts:
```tsx
// Wide: one column per series
// Long: one row per series per x
```
In wide data the series keys are the field names (`"solar"`); in long data they are the values of the `series` field. Those keys are what `highlight` and `names` refer to.
### Transforms
`transform` takes one transform or an array applied in order. Each is a JSON object with a `type`; a function `(rows) => rows` is also accepted in JSX.
| Transform | Fields | What it does |
|---|---|---|
| `"filter"` | `field`, `op` (default `==`), `value` | Keep rows that match. |
| `"aggregate"` | `groupBy` (field or fields, optional), `measures: { name: { op, field } }` | One row per group. Omit `field` for `count`. |
| `"sort"` | `by`, `order` (`"asc"` default, or `"desc"`) | |
| `"limit"` | `n`, `offset` | Slice. |
| `"top"` | `n`, `by`, `other`, `label` | Keep the top `n` rows by `by`. To fold the rest into one row, pass both `other` (the text, e.g. `"Other"`) and `label` (the field that text goes in); the folded row's `by` value is the sum. |
| `"fold"` | `fields`, `key` (default `"key"`), `value` (default `"value"`) | Wide to long. |
| `"pivot"` | `index`, `key`, `value`, `op` (default `sum`) | Long to wide: one row per `index`, one column per distinct `key`. |
| `"bin"` | `field`, `step` or `bins`, `as` (default `_bin`) | Adds the bin start as `as` and the bin end as `_end`. |
| `"derive"` | `as`, `op`, `fields`, `groupBy`, `base` | Adds a computed column; see below. |
| `"rename"` | `fields: { old: "new" }` | |
| `"select"` | `fields` | Keep only these columns. |
| `"parse"` | `fields: { name: "number" \| "date" \| "string" \| "boolean" }` | Coerce types. `number` strips `,`, `$`, `%` and spaces. |
Filter ops: `==`, `!=`, `>`, `>=`, `<`, `<=`, `in` (value is an array), `not-in`, `between` (value is `[low, high]`, inclusive), `contains` (case-insensitive substring), `exists` (non-null and non-empty; pass `value: false` to invert).
Aggregate ops: `sum`, `mean`, `median`, `min`, `max`, `count`, `distinct`, `first`, `last`.
Derive ops:
- Arithmetic on two fields: `add`, `subtract`, `multiply`, `divide` (`fields: ["a", "b"]`).
- Running, on one field in row order, restarting per `groupBy` group: `percent-of-total`, `cumulative`, `rank` (1 is the largest), `change` (difference from the previous row), `percent-change`, `index` (rebased so the first row equals `base`, default 100).
#### Examples
Top five plus "Other":
```json
[
{ "type": "aggregate", "groupBy": "category", "measures": { "revenue": { "op": "sum", "field": "amount" } } },
{ "type": "top", "n": 5, "by": "revenue", "other": "Other", "label": "category" }
]
```
Index several series to 100 at the start so growth is comparable (say "Indexed, 2019 = 100" in the subtitle):
```json
{ "type": "derive", "as": "indexed", "op": "index", "fields": ["value"], "groupBy": "country" }
```
Wide to long:
```json
{ "type": "fold", "fields": ["solar", "wind"], "key": "source", "value": "share" }
```
### Order of operations
Standalone chart: load `data`, then the chart's `transform`.
Inside ``: load the shared `data`, apply the store's `transform`, filter by the other charts' selections, then apply the chart's own `transform`.
---
## Theming and dark mode
### The default theme
With no configuration, charts inherit the page font and read shadcn/ui's `--background`, `--foreground` and `--muted-foreground` variables when they exist, with neutral fallbacks when they do not. The series colours are InsightCharts' own palette, whose order is chosen so neighbouring colours stay distinguishable under colour-vision deficiency. Keep the order if you re-skin.
### Three ways to theme
#### 1. CSS variables
Every colour token of the default theme reads an `--ic-*` variable first. Set them in your stylesheet, globally or on any ancestor:
```css
:root {
--ic-accent: #7c3aed;
--ic-series-1: #7c3aed;
--ic-font-display: "Fraunces", serif;
}
.dark {
--ic-accent: #a78bfa;
--ic-series-1: #a78bfa;
}
```
`npx shadcn@latest add @insightcharts/theme` writes the full set with their default values into your global CSS so you can edit them in place.
| CSS variable | Token | Light default | Dark default |
|---|---|---|---|
| `--ic-surface` | `surface` | `var(--background, #fcfcfb)` | `var(--background, #1a1a19)` |
| `--ic-ink` | `ink` | `var(--foreground, #0b0b0b)` | `var(--foreground, #f0efec)` |
| `--ic-ink-secondary` | `inkSecondary` | `#52514e` | `#c3c2b7` |
| `--ic-muted` | `muted` | `var(--muted-foreground, #898781)` | `var(--muted-foreground, #898781)` |
| `--ic-grid` | `grid` | `#e1e0d9` | `#2c2c2a` |
| `--ic-axis` | `axis` | `#c3c2b7` | `#383835` |
| `--ic-accent` | `accent` | `#2a78d6` | `#3987e5` |
| `--ic-context` | `context` | `#c3c2b7` | `#52514e` |
| `--ic-positive` | `positive` | `#0ca30c` | `#0ca30c` |
| `--ic-negative` | `negative` | `#d03b3b` | `#e66767` |
| `--ic-series-1` | `categorical[0]` | `#2a78d6` | `#3987e5` |
| `--ic-series-2` | `categorical[1]` | `#eb6834` | `#d95926` |
| `--ic-series-3` | `categorical[2]` | `#1baf7a` | `#199e70` |
| `--ic-series-4` | `categorical[3]` | `#eda100` | `#c98500` |
| `--ic-series-5` | `categorical[4]` | `#e87ba4` | `#d55181` |
| `--ic-series-6` | `categorical[5]` | `#008300` | `#0ca30c` |
| `--ic-series-7` | `categorical[6]` | `#6250d6` | `#9085e9` |
| `--ic-series-8` | `categorical[7]` | `#e34948` | `#e66767` |
| `--ic-font` | `fontFamily` | `inherit` | `inherit` |
| `--ic-font-display` | `displayFamily` | `var(--ic-font, inherit)` | `var(--ic-font, inherit)` |
The `sequential` and `diverging` ramps, `fontSize`, `radius` and `lineWidth` have no CSS variable; set them with the `theme` prop.
#### 2. The `theme` prop
On any chart, on `InsightProvider`, or on HTML components that declare it. It accepts:
- flat tokens, applied to both modes: `theme={{ accent: "#7c3aed", radius: 2 }}`
- per-mode tokens: `theme={{ light: { accent: "#7c3aed" }, dark: { accent: "#a78bfa" } }}`
- a full theme object from `createTheme(input, base?)`
A chart's `theme` is merged over the surrounding provider's. Token values may be literals or CSS variable references (`"var(--brand, #2a78d6)"`); they are resolved against the chart's own DOM node, so scoped overrides work.
Tokens: `surface`, `ink`, `inkSecondary`, `muted`, `grid`, `axis`, `accent`, `context`, `positive`, `negative`, `categorical[]`, `sequential[]`, `diverging[]`, `fontFamily`, `displayFamily`, `fontSize`, `radius`, `lineWidth`.
#### 3. The shadcn preset
```tsx
import { InsightProvider } from "insightcharts";
import { shadcnTheme } from "insightcharts/themes";
{children}
```
`shadcnTheme` takes series colours from `--chart-1` to `--chart-5`, the accent from `--chart-1` (falling back to `--primary`) and gridlines from `--border`. shadcn's stock chart colours are not ordered for colour-vision-deficiency separation, so keep direct labels or legends on. `insightcharts/themes` also exports `defaultTheme`, `createTheme`, six presets and the `themes` record that holds all of them by name.
### Presets
`editorialTheme`, `ledgerTheme`, `monoTheme`, `earthTheme`, `scienceTheme` and `posterTheme` each define a light and a dark token set, with categorical palettes checked for colour-vision-deficiency separation in both modes. Unlike `insight` and `shadcn`, which follow the host page, a preset paints for its own surface: put it in a container with the matching background, which `themeSurfaces[name]` gives you as `{ light, dark }`.
```tsx
import { InsightProvider } from "insightcharts";
import { editorialTheme, themeSurfaces } from "insightcharts/themes";
{children}
```
### Dark mode
`mode` is a prop on every chart and on `InsightProvider`:
- `"auto"` (default): match what the page looks like. In order: the nearest ancestor with a `.dark` / `.light` class or a `data-theme`, `data-mode`, `data-color-mode` or `data-color-scheme` attribute; otherwise the luminance of the nearest painted background; otherwise the page's `color-scheme` and the OS preference.
- `"system"`: follow the OS preference.
- `"light"` / `"dark"`: force it.
Charts re-resolve their colours when the root's class or theme attributes change, so toggles such as next-themes work without a remount. Each component root carries `data-ic-mode="light|dark"`.
### Restyling the chrome
The title, subtitle, legend and source line are HTML elements, each with a `data-slot` attribute: `ic-figure`, `ic-header`, `ic-title`, `ic-subtitle`, `ic-legend`, `ic-plot`, `ic-source`, `ic-table`, `ic-actions`, `ic-menu`, `ic-skeleton`, `ic-state`. HTML components add their own (for example `ic-big-number-value`). Target them from CSS:
```css
[data-slot="ic-title"] { font-size: 1.5rem; }
```
`className` and `style` on a chart apply to its `figure` element.
---
## Crossfiltering with InsightData
`InsightData` loads data once and shares it. Charts inside it that do not pass their own `data` read the shared rows, and any chart with `select` filters all the others when the reader clicks a mark.
```tsx
"use client";
import { BarChart, InsightData, LineChart } from "insightcharts";
export function Sales({ rows }: { rows: Record[] }) {
return (
);
}
```
### How it behaves
- `select` makes marks clickable. `select={true}` uses the chart's own category field (for `bar-chart` that is `x`; for `line-chart` it is `series`). `select="field"` names the field explicitly.
- A click toggles that value in the chart's selection; several values can be selected. A click on empty plot area clears it.
- A selection filters every other chart, never the chart that made it. The selecting chart keeps its full domain and fades the unselected marks.
- Each chart's own `transform` runs after the shared filter, so charts can aggregate the filtered rows differently.
- A chart that passes its own `data` inside `InsightData` is not filtered, but its selection still filters the others.
- Without an `InsightData` ancestor, `select` still works locally: the chart fades unselected marks and calls `onSelect`.
### Props
`InsightData`:
| Prop | Type | Description |
|---|---|---|
| `data` | `DataInput` | Shared data, any accepted shape. |
| `transform` | `Transform \| Transform[]` | Applied once to the shared rows, before filtering. |
| `selections` | `Record` | Controlled selections, keyed by chart id. |
| `onSelectionsChange` | `(selections) => void` | Called on every change. |
On charts: `select`, `onSelect(values, field)`, and `id` (a stable key for the chart's selection; set it when you control or persist selections, otherwise a generated id is used).
A `Selection` is `{ field, values?: string[], range?: [min, max] }`. Clicks produce `values`; `range` can be set programmatically and filters with an inclusive between.
### Reading or driving the store
```tsx
import { useInsightData } from "insightcharts";
function ClearFilters() {
const store = useInsightData(); // null outside
if (!store || Object.keys(store.selections).length === 0) return null;
return ;
}
```
The store exposes `all` (unfiltered rows), `rows`, `status`, `error`, `selections`, `select(owner, selection | null)`, `clear()` and `rowsFor(owner?)` (rows filtered by every selection except `owner`'s).
### Linked tooltips
Separately from filtering, charts that share a `group` id link their tooltips and axis pointers: `group="sales"` on each.
### Design notes
- Tell the reader the charts are linked, with a subtitle such as "Click a bar to filter".
- The charts must show their main fact before the reader clicks anything.
---
## Scroll and swipe stories with Scrolly
`Scrolly` pins a graphic and changes its state as the reader moves through a list of steps. Because the same chart receives new props at each step, ECharts animates between the states.
```tsx
"use client";
import { LineChart, Scrolly } from "insightcharts";
const data = [
{ year: "2019", solar: 2.7, wind: 5.3 },
{ year: "2021", solar: 3.7, wind: 6.5 },
{ year: "2023", solar: 5.5, wind: 7.8 },
];
const steps = [
{ id: "both", content: "Solar and wind share of global electricity generation, 2019 to 2023.", highlight: [] as string[] },
{ id: "wind", content: "Wind's share rose from 5.3% in 2019 to 7.8% in 2023.", highlight: ["wind"] },
{ id: "solar", content: "Solar's share rose from 2.7% in 2019 to 5.5% in 2023.", highlight: ["solar"] },
];
export function EnergyStory() {
return (
{({ step }) => (
)}
);
}
```
### Steps and state
Each step is `{ id?, content, ...yourKeys }`. `content` is the text for the step (a sentence or two). Any other keys are yours: the state the graphic should be in. `id` is also set as the step element's `id`, so it works as a link anchor.
`children` is a function that receives `{ index, step, progress, count }` and returns the graphic. `progress` runs 0 to 1 through the active step in scroll mode and is 0 in the other modes.
`children` is a function, so `Scrolly` must be used from a client component.
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `steps` | `StoryStep[]` | | The steps. |
| `children` | `(state) => ReactNode` | | Renders the graphic for the active step. |
| `mode` | `"auto" \| "scroll" \| "swipe" \| "steps"` | `"auto"` | See below. |
| `side` | `"left" \| "right"` | `"left"` | Which side the text column sits on in scroll mode. |
| `stickyTop` | `number \| string` | `0` | Offset from the top of the viewport at which the graphic pins; set it to the height of a fixed header. |
| `stepHeight` | `string` | `"80vh"` | Scroll distance per step. |
| `index` | `number` | | Controlled active step. |
| `onStepChange` | `(index, step) => void` | | Called when the active step changes. |
| `theme` | `ThemeInput` | | Theme for the step cards. |
| `colorMode` | `"auto" \| "system" \| "light" \| "dark"` | provider's | Colour mode for the step cards. Named `colorMode` because `mode` is the interaction mode. |
| `className`, `style` | | | On the root element. |
### Modes
- `"scroll"`: the graphic is sticky and the steps scroll past; the step crossing the middle of the viewport is active. When the container is narrower than 768px the steps overlay the graphic as cards instead of sitting beside it.
- `"swipe"`: the graphic sits on top and the steps are cards that swipe horizontally, with Previous and Next buttons.
- `"steps"`: one card at a time with Previous and Next buttons. No scroll hijacking; suitable for embeds.
- `"auto"`: swipe when the container is under 640px wide on a coarse-pointer (touch) device, scroll otherwise.
In swipe and steps modes the left and right arrow keys move between steps. In scroll and swipe modes all step text is in the document.
### Writing steps
- Change one thing per step: the data, the highlight or the encoding.
- Keep the same chart component in the same position across steps and change its props. Swapping to a different component, or changing its `key`, remounts it and loses the transition.
- Use three to seven steps. Each step is one or two plain sentences.
- Each step states one fact about what the chart shows at that step, with the number from the data: "Solar's share rose from 2.7% in 2019 to 5.5% in 2023." Do not start a step with "But" or "And" to link it to the previous one.
- The first step shows all of the data with no highlight.
- Give the chart a `title` that is true at every step (a plain label works), or set the title per step from the step's keys.
- Step text follows the same writing rules as titles. See "Choosing a form".
---
## Infographics
An infographic is a template with `category: "infographic"`. It draws one designed sheet, called a poster: a painted background, a large title, little other text, and one large graphic or several graphics laid out together. Every infographic is composed on the `poster` molecule from atoms with `category: "poster"`, called poster primitives.
### When to choose a poster
| The piece is | Use |
|---|---|
| One image that is read in a few seconds and shared as a picture | An infographic template |
| One image with a layout no infographic template has | The `poster` molecule with your own panels |
| One fact at a fixed social size (1:1, 4:5, 16:9, 9:16) | `social-card` |
| A page with paragraphs, a method note, recommendations or charts with axes | A report-style template such as `executive-brief`, `report-page` or `visual-essay` |
| A dashboard the reader filters | `data-explorer` |
A poster suits large counts, rankings, shares, flows and scales that read from the size of the marks. It does not suit dense time series or anything that needs axes and gridlines to be read.
### The infographic templates
Pick the template from the decision table in "Choosing a form" (rows with the job "Lay out a whole poster"). Each template's page lists its regions and has a complete example.
### Writing an infographic spec
```json
{
"type": "count-poster",
"theme": "noir",
"kicker": "Education",
"title": "Norland's schools taught 1.84 million pupils in 2025",
"subtitle": "Pupils enrolled in state schools by stage, autumn term 2025.",
"unit": 1000,
"unitLabel": "1,000 people",
"hero": {
"data": [
{ "stage": "Primary", "pupils": 820000 },
{ "stage": "Lower secondary", "pupils": 480000 },
{ "stage": "Upper secondary", "pupils": 390000 },
{ "stage": "Vocational", "pupils": 170000 }
],
"name": "stage",
"value": "pupils",
"title": "820,000 pupils were in primary school"
},
"sources": "Norland Ministry of Education, school census 2025",
"note": "Norland is an invented country. All figures are illustrative."
}
```
Render it with ``.
- `title` is required. It is the poster's one fact, as a plain sentence with its number. `kicker` is a short topic label and `subtitle` names the measure, population and period.
- `theme` is a poster theme name. Each template has its own default.
- Each template has named region props (`hero`, `reference`, `area`, `bars` and `shares` on `count-poster`; `scale`, `figures` and `side` on `scale-poster`). Read the template's page for its regions.
- A region takes a compact config: `data` rows, the field names, and any other prop of the primitive that draws the region. A `title`, `subtitle`, `source` or `note` in the config becomes the heading and source line of that panel.
- A region also takes a full spec with its own `type`. That replaces the primitive the template would have drawn.
- `sources`, `note` and `credit` are the footer. Always set `sources`. Say in `note` when data is illustrative.
- `linked: true` makes marks clickable. A click highlights the same name in every other graphic that has it and filters graphics that read the shared `data` prop.
- `download: true` adds a "Download PNG" control under the poster.
- `maxWidth` limits the width. `mode` is `"light"` by default and a poster does not follow the page's colour mode.
Keep names identical across regions (the same spelling of each entity) so `linked` can join them.
### Composing a poster with the poster frame
Use `poster` when no template has the layout.
```json
{
"type": "poster",
"theme": "forest",
"kicker": "Transport",
"title": "Tarn Valley buses carried 9.6 million passengers in 2025",
"subtitle": "Passengers on the valley's seven bus routes, January to December 2025.",
"panels": [
{ "span": 5, "valign": "center", "content": { "type": "big-number", "size": "hero", "value": "9.6 million", "label": "Passengers in 2025", "context": "8.9 million in 2024" } },
{
"span": 7,
"title": "Route 4 carried 2.3 million passengers",
"subtitle": "Passengers by route, thousands, 2025",
"content": { "type": "pill-rank", "data": [{ "route": "Route 4", "k": 2300 }, { "route": "Route 1", "k": 1900 }, { "route": "Route 7", "k": 1500 }], "name": "route", "value": "k", "format": "{value}k", "highlight": "Route 4" }
},
{
"title": "Passenger numbers were 6.1 million in 2021",
"content": { "type": "block-bar", "data": [{ "year": "2021", "m": 6.1 }, { "year": "2023", "m": 8.2 }, { "year": "2025", "m": 9.6 }], "name": "year", "value": "m", "format": "{value}m" }
}
],
"sources": "Tarn Valley Transport Authority",
"note": "Tarn Valley is an invented place. All figures are illustrative."
}
```
- `panels` is required. Each panel is `{ content, title?, subtitle?, span?, rowSpan?, source?, note?, align?, valign?, bleed?, id? }`.
- `content` is a component spec. Put the panel's heading in the panel's `title`, not in the chart's `title`. Every panel heading is then set in the same type.
- `columns` is 12 by default. `span` is in those units and defaults to the full width. `span: 5` beside `span: 7` makes one row. `columns: 2` or `columns: 3` gives a simple grid.
- `aspect` fixes the shape (`"4:5"`, `"16:9"`, `0.75`). The poster still grows when its content needs more height.
- `rules` is `"dotted"` (default), `"solid"` or `"none"`.
- The frame passes its theme and colour mode to everything inside. Do not set `theme` or `mode` on a chart in a panel.
- Use at most about six panels. Give charts in panels a `height` when they do not size themselves (`bar-chart`, `donut-chart`, `treemap`). Poster primitives size themselves.
### Poster themes
Eight themes, passed by name as `theme`.
| Name | Ground | Marks |
|---|---|---|
| `noir` | Near-black `#0d0d0f` | Yellow, sky blue, coral, off-white, green. White type. |
| `magenta` | Magenta `#c2006b` | Pale pink, near-black, cyan, yellow. White type. |
| `dusk` | Violet gradient, `#24104f` to `#5e2067` | Orange, pale yellow, pink, lilac, mint. White type. |
| `paper` | Off-white `#f3ecdf` | Red, ink black, warm grey, blue. Black type. `mode: "dark"` gives the same design on charcoal `#1d1b18`. |
| `ocean` | Navy to teal gradient, `#041c3a` to `#084c5c` | Aqua, coral, sand, blue. White type. |
| `forest` | Dark green `#0f3d2a` | Lime, orange, cream, light blue. Cream type. |
| `blueprint` | Cobalt `#1447c8` | Yellow, white, salmon, pale blue. White type. Monospace display face. |
| `sunrise` | Cream to peach gradient, `#fff1e0` to `#ffdfc6` | Plum, tangerine, teal, blue. Plum type. |
- Seven themes have one look: the same tokens in light and dark. Only `paper` has a different dark variant.
- A poster theme has 4 or 5 categorical colours. Fold further entities into "Other" or shade by value.
- `posterThemes` and `posterBackgrounds` are exported from `insightcharts/themes`. `posterBackgrounds[name].light` is the CSS background, which is a gradient for `dusk`, `ocean` and `sunrise`.
- Do not hard-code colours in a spec. Pick a theme.
### Poster primitives
The data job to primitive table is in "Choosing a form" under "Poster primitives". A primitive is a chart built with `defineChart` and accepts the common chart props (`data`, `transform`, `title`, `highlight`, `format`, `select`, ...). It prints names and values on or beside its marks, has no axes by default and sets its own height. It works in any theme and outside a poster.
Any other atom also fits a panel. `big-number` (with `size: "hero"`), `pictogram-chart`, `treemap`, `waffle-chart`, `segmented-bar`, `donut-chart`, `bar-chart` and `icon-stat` are used in the `poster` examples.
### Icons
An icon prop (`icon`, `icons`, `iconField` values) takes a built-in name or a custom single path written as `"path://M0 0..."` on a 24 by 24 grid. Use only these names:
- People: `person`, `woman`, `child`, `baby`, `group`
- Home and buildings: `house`, `building`, `factory`, `hospital`, `school`, `shop`
- Transport: `car`, `bus`, `train`, `bike`, `plane`, `ship`, `truck`, `rocket`
- Nature and energy: `tree`, `leaf`, `sun`, `cloud`, `rain`, `drop`, `snow`, `wind-turbine`, `solar-panel`, `flame`, `bolt`, `battery`, `barrel`, `plug`, `mountain`, `wave`, `globe`
- Food and farming: `wheat`, `bread`, `apple`, `carrot`, `fish`, `cow`, `chicken`, `milk-bottle`, `cup`, `bottle`, `fork-knife`
- Money and shopping: `coin`, `banknote`, `cart`, `bag`, `bin`
- Objects: `phone`, `laptop`, `book`, `lightbulb`, `heart`, `cross`, `pill`, `syringe`, `clock`, `calendar`, `flag`, `star`, `pin`, `lock`, `satellite`
- Shapes: `circle`, `square`
An unknown name falls back to another icon without an error. Check every icon name against this list. `icons`, `iconNames`, `iconGroups`, `resolveIcon` and `iconSymbol` are exported from `insightcharts` and `insightcharts/core`. The `Icon` component draws one icon as inline SVG and takes `name`, `size`, `color`, `title` and `fallback`.
### PNG download
- `download: true` on `poster` or on any infographic adds a "Download PNG" control under the sheet. The image is the whole sheet without the control.
- On `poster`, `exportWidth` sets the image width (default 2400), `filename` the file name (default: a slug of the title), `downloadLabel` the label and `onExport` receives the `Blob`. The infographic templates take `download` only.
- With `download` set, every chart in the poster mounts at once instead of lazily.
- From code: `rasterize(element, pixelWidth)` and `canvasToBlob(canvas)` from `insightcharts/core` paint any element to a PNG, and `downloadBlob(blob, name)` saves it. The poster sheet is the element with `data-slot="ic-poster"`.
- The painter draws backgrounds including linear and radial gradients, borders, radii, clipping, text, inline SVG and canvases. It does not draw outer or blurred shadows, CSS transforms, conic or repeating gradients or `url()` backgrounds.
### Phone layout
A poster lays out from its own width, not the viewport.
| Poster width | Layout |
|---|---|
| 760px and wider | The grid as written. |
| 600 to 759px | Two columns. On a 12-unit grid a panel with `span` up to 6 takes one column and a wider panel takes both. |
| Under 600px | One column, every panel full width, in the order of `panels`. |
Under 600px the padding is 20px, the gap is 28px and a fixed `aspect` is dropped. Order `panels` so the poster reads correctly as one column. Poster primitives lay themselves out again for the narrow width.
### Checklist
- The `title` is one clause with its number and period.
- Every panel heading is one fact about that panel or a plain label.
- `sources` is set.
- Every icon name is in the list above.
- Entity names are spelt the same in every region.
- There are at most about six panels.
- No chart in a panel sets its own `theme` or `mode`.
---
## Export
### The export menu
```tsx
```
`exportable` adds an "Export" menu at the top right of the figure (visible on hover or keyboard focus). `true` offers PNG, SVG, PDF and CSV. Pass a list to choose from all six formats: `exportable={["png", "jpeg", "svg", "pdf", "csv", "json"]}`. Set it once for every chart with ``.
### Formats
| Format | Contents |
|---|---|
| `png`, `jpeg` | The chart re-rendered off-screen at `pixelRatio` (default 2), with the title, subtitle and source line drawn in, on the theme's surface colour. |
| `svg` | A standalone SVG with the same chrome as text elements. |
| `pdf` | A single page containing the image. |
| `csv`, `json` | The rows the chart drew, after loading, crossfilter and `transform`. |
Image exports use the chart's current size, theme and colour mode, and never have a transparent background unless you pass one.
### Programmatic export
Every chart forwards a ref with `export(options)`, `getInstance()` and `getOption()`.
```tsx
"use client";
import { useRef } from "react";
import { LineChart } from "insightcharts";
import type { ChartHandle } from "insightcharts";
export function Downloadable({ data }: { data: Record[] }) {
const chart = useRef(null);
return (
<>
>
);
}
```
Options:
| Option | Default | Description |
|---|---|---|
| `format` | `"png"` | `png`, `jpeg`, `svg`, `pdf`, `csv` or `json`. |
| `filename` | slug of the title | Without extension. |
| `pixelRatio` | `2` | Raster scale. |
| `chrome` | `true` | Include the title, subtitle and source line in image exports. |
| `background` | theme surface | Background colour. |
| `download` | `true` | Set false to get the `Blob` back without triggering a download. |
`export()` always resolves to the `Blob`. Export runs in the browser; there is no server-side image rendering in the package.
### The data table
`table` adds a collapsible "View data" table under the chart with the same rows. Screen-reader users and readers who need exact values can use it.
Lower-level functions (`exportChart`, `toSvgString`, `toCanvas`, `downloadBlob`) are exported from `insightcharts/core` for custom charts built on `Chart`.
---
## The JSON spec
A spec is a component described as plain JSON: `type` is the component's kebab-case name, every other key is a prop.
```json
{
"type": "line-chart",
"data": { "url": "/data/energy.csv" },
"transform": [{ "type": "filter", "field": "year", "op": ">=", "value": 2015 }],
"x": "year",
"y": ["solar", "wind"],
"highlight": "solar",
"annotations": [{ "type": "line", "axis": "x", "value": "2020", "label": "Pandemic" }],
"title": "Solar and wind share of electricity since 2015",
"subtitle": "Share of global electricity generation, %",
"source": "Ember"
}
```
```tsx
import { Insight } from "insightcharts";
```
### When to use a spec
- The chart is chosen at runtime: by an AI agent, a CMS field, an API response.
- You need to store, diff or validate charts as data.
When you are writing a page by hand, JSX with named imports is the better choice: it is type-checked and only bundles what you import (the package ships one module per component). Rendering `Insight` registers, and therefore bundles, every component.
### Rules
- `type` must be a component name from the manifest. An unknown type renders a visible message listing the available types; it does not throw.
- Props are the same as in JSX. Field names are strings in both.
- Specs nest. Wherever a molecule or template takes content (a slot), it accepts text or another spec.
- Everything is expressible except functions: no callbacks (`onSelect`, `onEvents`, `onReady`), and no function forms of `data`, `transform`, `format` or `option`. Use the string and object forms: URLs and `{ url, path }` for data, transform objects, format strings such as `"percent"` or `"{value} kg"`, and an `option` object for ECharts overrides.
### Validate before rendering
The schema is generated from the component metadata:
- `https://insightcharts.dev/schema/insight-spec.json`
It is a `oneOf` over component types. Each type lists the component's own props (plus the common chart props for charts, or `className`, `style`, `theme` and `mode` for HTML components) and rejects unknown keys, which catches misspelt and invented props. Use any JSON Schema (draft-07) validator. The component manifest, with every component's description, props and example, is at `https://insightcharts.dev/schema/manifest.json` and ships in the package as `insightcharts/manifest.json`.
### Formats and annotations in JSON
`format`: `"auto"`, `"compact"` (12.9K), `"percent"` (0.42 becomes 42%), `"percent-raw"` (42 becomes 42%), `"integer"`, `"signed"`, `"year"`, `"currency:USD"`, `"decimal:2"`, a template containing `{value}` such as `"{value} kg"`, or an `Intl.NumberFormat` options object.
`annotations` (cartesian charts), positioned in data coordinates:
```json
[
{ "type": "line", "axis": "y", "value": 50, "label": "Target", "dashed": true },
{ "type": "band", "axis": "x", "from": "2020", "to": "2021", "label": "Lockdowns" },
{ "type": "point", "x": "2023", "y": 5.5, "label": "5.5% in 2023", "position": "top" }
]
```
`line` also takes `emphasis` (draw in ink rather than muted). `point` takes `dot: false` to hide the marker; its `label` supports `\n` line breaks.
### Registering your own components
`Insight` and nested slots look components up by `meta.name`. To make a spec resolve to your own component, or to a registry copy you have customised, register it:
```tsx
import { registerComponents } from "insightcharts";
import { BarChart } from "@/components/insightcharts/atoms/bar-chart";
registerComponents({ BarChart }); // the last registration for a name wins
```
`listComponents()` returns the metadata of everything registered, and `getComponent(type)` returns one component.
---
## Recipes
Each recipe uses `bar-chart`, `line-chart` and `big-number`, which are the reference components. Check the component catalogue for more specific forms before settling on these.
Where a recipe shows its rows, the title states a fact from those rows. Where the rows are not shown (`data={rows}`), the title is a plain label. When you have the data, replace the label with one fact and its number, following the writing rules in "Choosing a form".
### Ranked bars with one highlighted
```tsx
const rows = [
{ country: "Norway", share: 98 },
{ country: "Brazil", share: 89 },
{ country: "Canada", share: 68 },
{ country: "Germany", share: 52 },
];
```
A single-series bar chart sorts descending and labels each bar, so there is no value axis to read. `highlight` takes the category label.
### Top five plus Other
```tsx
```
### Highlight one line among several
```tsx
```
Long data: one row per country per year. Every country other than Denmark is drawn in the context grey.
### Annotate a date, a period and a value
```tsx
```
Annotation `value`, `from`, `to` and `x` must match the x values in the data.
### Compare growth with an index
```tsx
```
The rebasing must be stated in the subtitle. Rows must already be in time order within each group.
### A big number with context
```tsx
```
Numeric `delta` values are fractions rendered as signed percentages (`0.021` is +2.1%). Pass `upIsGood={false}` when an increase is bad.
### Stat row above a chart
Until a molecule covers your case, compose atoms with ordinary layout. Use container-friendly CSS (`minmax(0, 1fr)`, `minWidth: 0`) so charts can shrink.
```tsx
```
### Load from an API on the server
```tsx
import { loadData } from "insightcharts/data";
import { BarChart } from "insightcharts";
export default async function Page() {
const rows = await loadData({ url: "https://api.example.com/v1/emissions", path: "data.results" });
return ;
}
```
In a client-only app, pass the same `{ url, path }` object straight to `data`. The chart shows a skeleton while it loads.
### A JSON spec
```json
{
"type": "bar-chart",
"data": [
{ "country": "Norway", "share": 98 },
{ "country": "Brazil", "share": 89 },
{ "country": "Canada", "share": 68 }
],
"x": "country",
"y": "share",
"format": "{value}%",
"highlight": "Norway",
"title": "Renewables supplied 98% of Norway's electricity in 2023",
"subtitle": "Share of electricity generation from renewables, 2023",
"source": "Ember"
}
```
Render with `` and validate against `https://insightcharts.dev/schema/insight-spec.json`. `data` can also be a URL with a `transform`:
```json
{
"type": "bar-chart",
"data": { "url": "/data/renewables.csv" },
"transform": [{ "type": "filter", "field": "year", "op": "==", "value": 2023 }],
"x": "country",
"y": "share",
"title": "Renewable share of electricity by country, 2023"
}
```
### Override one ECharts setting
```tsx
```
`option` is deep-merged over what the component generates. Use a prop when one exists, and `option` only when none does.
---
## Arc diagram (`arc-diagram`)
> Places entities in order on a line and joins each related pair with a semicircular arc whose thickness is the value of the connection; dots are sized by each entity's total.
- Component: `ArcDiagram` (import from `insightcharts`)
- Spec type: `"arc-diagram"`
- Level: atom. Category: poster.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
Connections among up to about 20 entities that have a meaningful order (stops on a line, chapters, age groups, teams by size): trips between stations, trade between neighbours, characters sharing scenes.
### Avoid
Flows with a direction through stages (use sankey-chart); one source splitting into parts (use fan-flow); a full matrix of every pair where exact values matter (use heatmap); more than about 20 entities or 60 connections (use heatmap).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/arc-diagram
```
The file lands at `components/insightcharts/atoms/arc-diagram.tsx`. `@insightcharts/arc-diagram-demo` adds a ready-made example next to it.
### Usage
```tsx
import { ArcDiagram } from "insightcharts";
const data = [
{ a: "Harbour", b: "Market", trips: 4200 },
{ a: "Harbour", b: "Quay", trips: 900 },
{ a: "Quay", b: "Market", trips: 1300 },
{ a: "Market", b: "Town Hall", trips: 2600 },
{ a: "Market", b: "University", trips: 3100 },
{ a: "Town Hall", b: "University", trips: 1500 },
{ a: "Town Hall", b: "Hospital", trips: 1100 },
{ a: "University", b: "Park", trips: 800 },
{ a: "University", b: "Airport", trips: 1900 },
{ a: "Park", b: "Hospital", trips: 600 },
{ a: "Hospital", b: "Stadium", trips: 700 },
{ a: "Stadium", b: "Airport", trips: 1200 },
{ a: "Harbour", b: "Airport", trips: 2300 },
{ a: "Market", b: "Stadium", trips: 1700 },
{ a: "Quay", b: "University", trips: 500 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "arc-diagram",
"data": [
{ "a": "Harbour", "b": "Market", "trips": 4200 },
{ "a": "Harbour", "b": "Quay", "trips": 900 },
{ "a": "Quay", "b": "Market", "trips": 1300 },
{ "a": "Market", "b": "Town Hall", "trips": 2600 },
{ "a": "Market", "b": "University", "trips": 3100 },
{ "a": "Town Hall", "b": "University", "trips": 1500 },
{ "a": "Town Hall", "b": "Hospital", "trips": 1100 },
{ "a": "University", "b": "Park", "trips": 800 },
{ "a": "University", "b": "Airport", "trips": 1900 },
{ "a": "Park", "b": "Hospital", "trips": 600 },
{ "a": "Hospital", "b": "Stadium", "trips": 700 },
{ "a": "Stadium", "b": "Airport", "trips": 1200 },
{ "a": "Harbour", "b": "Airport", "trips": 2300 },
{ "a": "Market", "b": "Stadium", "trips": 1700 },
{ "a": "Quay", "b": "University", "trips": 500 }
],
"from": "a",
"to": "b",
"value": "trips",
"order": ["Harbour", "Quay", "Market", "Town Hall", "University", "Park", "Hospital", "Stadium", "Airport"],
"colorBy": "none",
"format": "integer",
"title": "Harbour and Market stations exchanged 4,200 trips a day",
"subtitle": "Daily trips between pairs of stations on one tram line, both directions; dots show each station's total",
"note": "Illustrative figures for an invented line."
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `from` (required) | `string` | | Field naming one end of each connection. With `select: true` a click selects the entity under this field name, so inside `` other charts are filtered on `from`; pass `select: ""` to link on the field that holds the entity in the shared rows. |
| `to` (required) | `string` | | Field naming the other end. |
| `value` | `string` | | Value field: arc thickness. Omit to count rows. Rows joining the same pair are summed, in either direction. |
| `order` | `string[]` | | Entity names in the order they sit on the line. Entities not listed follow in data order. |
| `orientation` | `"auto" \| "horizontal" \| "vertical"` | `"auto"` | Line along the bottom with arcs above, or down the left with arcs to the right. Auto turns vertical on narrow screens. |
| `colorBy` | `"series" \| "none"` | | `series`: each arc in the colour of its first entity; past the size of the theme's palette the extra colours are derived from the theme's own. `none`: every arc in the accent. Default: series while the entities fit the palette (at most 8), none beyond. |
| `arcLabels` | `boolean` | `true` | Print values at the top of the arcs, largest first, wherever they do not collide. The values that do not fit are printed while their entity is hovered or selected, and every value is in the tooltip. |
| `names` | `Record` | | Display names for entity keys. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: [network-poster](https://insightcharts.dev/llms/network-poster.md)
### More examples
#### One entity highlighted
```tsx
import { ArcDiagram } from "insightcharts";
const data = [
{ a: "Harbour", b: "Market", trips: 4200 },
{ a: "Harbour", b: "Quay", trips: 900 },
{ a: "Quay", b: "Market", trips: 1300 },
{ a: "Market", b: "Town Hall", trips: 2600 },
{ a: "Market", b: "University", trips: 3100 },
{ a: "Town Hall", b: "University", trips: 1500 },
{ a: "Town Hall", b: "Hospital", trips: 1100 },
{ a: "University", b: "Park", trips: 800 },
// ... 7 more rows
];
```
#### Coloured by entity, counting rows
```tsx
import { ArcDiagram } from "insightcharts";
const data = [
{ a: "Ana", b: "Ben" },
{ a: "Ana", b: "Ben" },
{ a: "Ana", b: "Ben" },
{ a: "Ana", b: "Cleo" },
{ a: "Ben", b: "Cleo" },
{ a: "Ben", b: "Cleo" },
{ a: "Cleo", b: "Dev" },
{ a: "Cleo", b: "Dev" },
// ... 9 more rows
];
```
#### Ten entities, one colour each
```tsx
import { ArcDiagram } from "insightcharts";
const data = [
{ a: "Pier", b: "Docks", trips: 1800 },
{ a: "Pier", b: "Forum", trips: 2400 },
{ a: "Docks", b: "Canal", trips: 900 },
{ a: "Canal", b: "Forum", trips: 1500 },
{ a: "Forum", b: "Abbey", trips: 3300 },
{ a: "Forum", b: "Castle", trips: 1300 },
{ a: "Abbey", b: "Castle", trips: 1100 },
{ a: "Abbey", b: "Mills", trips: 700 },
// ... 7 more rows
];
```
#### Stress: 18 entities with long names
```tsx
import { ArcDiagram } from "insightcharts";
const data = [
{ a: "Customer support", b: "Marketing", messages: 33 },
{ a: "Customer support", b: "Data platform", messages: 22 },
{ a: "Customer support", b: "Finance", messages: 14 },
{ a: "Customer support", b: "Research", messages: 8 },
{ a: "Billing", b: "Product design", messages: 222 },
{ a: "Billing", b: "Security", messages: 189 },
{ a: "Billing", b: "People team", messages: 158 },
{ a: "Billing", b: "Partnerships", messages: 130 },
// ... 24 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/arc-diagram.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/arc-diagram.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Area chart (`area-chart`)
> Shows a quantity over time as a filled area from zero: one series, stacked series, shares of 100%, or a streamgraph.
- Component: `AreaChart` (import from `insightcharts`)
- Spec type: `"area-chart"`
- Level: atom. Category: trend.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
A volume or cumulative quantity over time where the filled magnitude matters; stacked when the total and its composition both matter; percent when only the mix matters; stream for the changing mix of many series over a long period.
### Avoid
Comparing the trajectories of several series against each other (use line-chart: only the bottom band of a stack has a flat baseline); a few time points (use bar-chart with stack); more than about 6 bands.
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/area-chart
```
The file lands at `components/insightcharts/atoms/area-chart.tsx`. `@insightcharts/area-chart-demo` adds a ready-made example next to it.
### Usage
```tsx
import { AreaChart } from "insightcharts";
const data = [
{ year: "2014", gw: 180 },
{ year: "2015", gw: 229 },
{ year: "2016", gw: 306 },
{ year: "2017", gw: 405 },
{ year: "2018", gw: 512 },
{ year: "2019", gw: 633 },
{ year: "2020", gw: 774 },
{ year: "2021", gw: 945 },
{ year: "2022", gw: 1177 },
{ year: "2023", gw: 1610 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "area-chart",
"data": [
{ "year": "2014", "gw": 180 },
{ "year": "2015", "gw": 229 },
{ "year": "2016", "gw": 306 },
{ "year": "2017", "gw": 405 },
{ "year": "2018", "gw": 512 },
{ "year": "2019", "gw": 633 },
{ "year": "2020", "gw": 774 },
{ "year": "2021", "gw": 945 },
{ "year": "2022", "gw": 1177 },
{ "year": "2023", "gw": 1610 }
],
"x": "year",
"y": "gw",
"names": { "gw": "Installed solar capacity" },
"format": "{value} GW",
"title": "World solar capacity rose from 180 GW in 2014 to 1,610 GW in 2023",
"subtitle": "Cumulative installed solar PV capacity, gigawatts",
"source": "IRENA, Renewable Capacity Statistics (rounded)",
"note": "Figures are approximate, for illustration"
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `x` (required) | `string` | | Time or ordered category field. ISO dates are placed on a true time axis. |
| `y` (required) | `string \| string[]` | | Value field, or several (one area each). |
| `series` | `string` | | Field that splits rows into areas (long data). |
| `stack` | `boolean \| "percent"` | `false` | Stack the areas; `percent` normalises every x position to 100%. |
| `stream` | `boolean` | `false` | Streamgraph: stack around a centred baseline. The value axis is dropped because only band thickness is readable. |
| `curve` | `"linear" \| "smooth" \| "step"` | `"linear"` | Edge shape. A streamgraph defaults to smooth. |
| `labels` | `boolean` | | Name each band inside itself where it fits; the rest go to the legend. Default: on when stacked. |
| `names` | `Record` | | Display names for series keys. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: [atlas-poster](https://insightcharts.dev/llms/atlas-poster.md)
### More examples
#### Stacked, labelled in the bands
```tsx
import { AreaChart } from "insightcharts";
const data = [
{ year: "2000", Coal: 5990, Gas: 2770, Nuclear: 2540, Hydro: 2620, "Wind and solar": 32 },
{ year: "2004", Coal: 6940, Gas: 3450, Nuclear: 2700, Hydro: 2810, "Wind and solar": 88 },
{ year: "2008", Coal: 8260, Gas: 4300, Nuclear: 2660, Hydro: 3250, "Wind and solar": 233 },
{ year: "2012", Coal: 9170, Gas: 5100, Nuclear: 2400, Hydro: 3650, "Wind and solar": 630 },
{ year: "2016", Coal: 9450, Gas: 5800, Nuclear: 2540, Hydro: 4020, "Wind and solar": 1290 },
{ year: "2020", Coal: 9440, Gas: 6370, Nuclear: 2640, Hydro: 4350, "Wind and solar": 2440 },
{ year: "2024", Coal: 10590, Gas: 6790, Nuclear: 2770, Hydro: 4410, "Wind and solar": 4620 },
];
```
#### 100% stacked with highlight
```tsx
import { AreaChart } from "insightcharts";
const data = [
{ year: "2000", Coal: 5990, Gas: 2770, Nuclear: 2540, Hydro: 2620, "Wind and solar": 32 },
{ year: "2004", Coal: 6940, Gas: 3450, Nuclear: 2700, Hydro: 2810, "Wind and solar": 88 },
{ year: "2008", Coal: 8260, Gas: 4300, Nuclear: 2660, Hydro: 3250, "Wind and solar": 233 },
{ year: "2012", Coal: 9170, Gas: 5100, Nuclear: 2400, Hydro: 3650, "Wind and solar": 630 },
{ year: "2016", Coal: 9450, Gas: 5800, Nuclear: 2540, Hydro: 4020, "Wind and solar": 1290 },
{ year: "2020", Coal: 9440, Gas: 6370, Nuclear: 2640, Hydro: 4350, "Wind and solar": 2440 },
{ year: "2024", Coal: 10590, Gas: 6790, Nuclear: 2770, Hydro: 4410, "Wind and solar": 4620 },
];
```
#### Streamgraph, long format
```tsx
import { AreaChart } from "insightcharts";
const data = [
{ month: "2021-01-01", genre: "Drama", hours: 58 },
{ month: "2021-02-01", genre: "Drama", hours: 58 },
{ month: "2021-03-01", genre: "Drama", hours: 58 },
{ month: "2021-04-01", genre: "Drama", hours: 55 },
{ month: "2021-05-01", genre: "Drama", hours: 50 },
{ month: "2021-06-01", genre: "Drama", hours: 45 },
{ month: "2021-07-01", genre: "Drama", hours: 38 },
{ month: "2021-08-01", genre: "Drama", hours: 31 },
// ... 280 more rows
];
```
#### Overlapping washes
```tsx
import { AreaChart } from "insightcharts";
const data = [
{ "2023": 41, "2024": 52, month: "Jan" },
{ "2023": 44, "2024": 55, month: "Feb" },
{ "2023": 58, "2024": 71, month: "Mar" },
{ "2023": 73, "2024": 80, month: "Apr" },
{ "2023": 96, "2024": 104, month: "May" },
{ "2023": 118, "2024": 131, month: "Jun" },
{ "2023": 134, "2024": 152, month: "Jul" },
{ "2023": 129, "2024": 147, month: "Aug" },
// ... 4 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/area-chart.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/area-chart.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Arrow chart (`arrow-chart`)
> Draws an arrow per category from a start value to an end value, showing the direction and size of each shift.
- Component: `ArrowChart` (import from `insightcharts`)
- Spec type: `"arrow-chart"`
- Level: atom. Category: comparison.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
Shifts between two moments across up to about 25 categories when some rise and some fall: swings between elections, before / after a policy.
### Avoid
When both levels matter more than the direction (use dumbbell-chart); more than two points in time (use slope-chart or line-chart); a single value per category (use bar-chart).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/arrow-chart
```
The file lands at `components/insightcharts/atoms/arrow-chart.tsx`. `@insightcharts/arrow-chart-demo` adds a ready-made example next to it.
### Usage
```tsx
import { ArrowChart } from "insightcharts";
const data = [
{ group: "Under 30", y2019: 62, y2024: 71 },
{ group: "30 to 44", y2019: 58, y2024: 63 },
{ group: "45 to 64", y2019: 49, y2024: 47 },
{ group: "65 and over", y2019: 41, y2024: 34 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "arrow-chart",
"data": [
{ "group": "Under 30", "y2019": 62, "y2024": 71 },
{ "group": "30 to 44", "y2019": 58, "y2024": 63 },
{ "group": "45 to 64", "y2019": 49, "y2024": 47 },
{ "group": "65 and over", "y2019": 41, "y2024": 34 }
],
"category": "group",
"from": "y2019",
"to": "y2024",
"names": { "y2019": "2019", "y2024": "2024" },
"format": "{value}%",
"changeLabel": "Change",
"height": 220,
"title": "Support among under-30s rose from 62% in 2019 to 71% in 2024",
"subtitle": "Share in favour of the congestion charge, by age group, 2019 to 2024",
"source": "City residents survey",
"note": "Illustrative figures."
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `category` (required) | `string` | | Category field: one arrow each. |
| `from` (required) | `string` | | Field with the start value, or (long data) the `series` value that marks it. |
| `to` (required) | `string` | | Field with the end value, or (long data) the `series` value that marks it. |
| `series` | `string` | | Long data: the field whose values are `from` and `to`. |
| `value` | `string` | `"value"` | Long data: the measure field. |
| `good` | `"up" \| "down" \| "none"` | `"up"` | Direction drawn in the positive colour. `none` uses the accent for every arrow. |
| `sort` | `"change" \| "from" \| "to" \| "none"` | `"change"` | Row order. |
| `order` | `"desc" \| "asc"` | `"desc"` | Sort direction. |
| `labels` | `boolean` | | Start and end values at the tail and head. Default: on for up to 14 rows; otherwise a value axis is drawn. |
| `changeLabel` | `boolean \| string` | `false` | Print the signed change in a right-hand column; a string sets the column header. |
| `zero` | `boolean` | `false` | Start the value axis at zero. |
| `names` | `Record` | | Display names for the `from` and `to` keys. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: no molecule or template yet
### More examples
#### Lower is better, one row highlighted
```tsx
import { ArrowChart } from "insightcharts";
const data = [
{ country: "Greece", before: 27.5, after: 10.1 },
{ country: "Spain", before: 26.1, after: 11.4 },
{ country: "Portugal", before: 16.2, after: 6.4 },
{ country: "Italy", before: 12.1, after: 6.5 },
{ country: "France", before: 10.3, after: 7.4 },
{ country: "Germany", before: 5.2, after: 3.4 },
{ country: "Austria", before: 5.3, after: 5.2 },
{ country: "Luxembourg", before: 5.9, after: 6.1 },
];
```
#### Long format
```tsx
import { ArrowChart } from "insightcharts";
const data = [
{ party: "Greens", election: "2019", value: 20.5 },
{ party: "Greens", election: "2024", value: 11.9 },
{ party: "Conservatives", election: "2019", value: 28.9 },
{ party: "Conservatives", election: "2024", value: 30 },
{ party: "Social Democrats", election: "2019", value: 15.8 },
{ party: "Social Democrats", election: "2024", value: 13.9 },
{ party: "Far right", election: "2019", value: 11 },
{ party: "Far right", election: "2024", value: 15.9 },
// ... 2 more rows
];
```
#### Stress: 24 rows on a value axis
```tsx
import { ArrowChart } from "insightcharts";
const data = [
{ region: "Northumberland and Durham coalfield towns", before: 31, after: 48 },
{ region: "Greater Manchester", before: 44, after: 52 },
{ region: "West Midlands conurbation", before: 41, after: 47 },
{ region: "Merseyside", before: 47, after: 50 },
{ region: "South Yorkshire", before: 36, after: 49 },
{ region: "West Yorkshire", before: 42, after: 48 },
{ region: "Tyne and Wear", before: 45, after: 51 },
{ region: "Inner London", before: 58, after: 49 },
// ... 16 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/arrow-chart.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/arrow-chart.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Bar chart (`bar-chart`)
> Compares a value across categories with bars from a zero baseline.
- Component: `BarChart` (import from `insightcharts`)
- Spec type: `"bar-chart"`
- Level: atom. Category: comparison.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
Ranking or comparing up to about 20 categories; grouped for a few series, stacked when the total and its composition both matter.
### Avoid
Change over many time points (use line-chart); two values per category (use dumbbell-chart); more than about 5 stacked segments.
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/bar-chart
```
The file lands at `components/insightcharts/atoms/bar-chart.tsx`. `@insightcharts/bar-chart-demo` adds a ready-made example next to it.
### Usage
```tsx
import { BarChart } from "insightcharts";
const data = [
{ country: "Norway", share: 98 },
{ country: "Brazil", share: 89 },
{ country: "Canada", share: 68 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "bar-chart",
"data": [
{ "country": "Norway", "share": 98 },
{ "country": "Brazil", "share": 89 },
{ "country": "Canada", "share": 68 }
],
"x": "country",
"y": "share",
"format": "{value}%",
"title": "Renewables supplied 98% of Norway's electricity in 2023"
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `x` (required) | `string` | | Category field. |
| `y` (required) | `string \| string[]` | | Value field, or several for grouped / stacked bars. |
| `series` | `string` | | Field that splits rows into series (long data). |
| `orientation` | `"auto" \| "horizontal" \| "vertical"` | `"auto"` | Horizontal for rankings and long labels, vertical for time. |
| `sort` | `"none" \| "asc" \| "desc"` | | Bar order. Default: descending for rankings, data order for time. |
| `stack` | `boolean \| "percent"` | `false` | Stack series; `percent` normalises to 100%. |
| `labels` | `boolean` | | Value labels at bar ends. Default: on for one series of up to 12 bars. |
| `names` | `Record` | | Display names for series keys. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: [before-after](https://insightcharts.dev/llms/before-after.md), [chart-callout](https://insightcharts.dev/llms/chart-callout.md), [chart-tabs](https://insightcharts.dev/llms/chart-tabs.md), [linked-charts](https://insightcharts.dev/llms/linked-charts.md), [poster](https://insightcharts.dev/llms/poster.md), [scrolly-chart](https://insightcharts.dev/llms/scrolly-chart.md), [section](https://insightcharts.dev/llms/section.md), [small-multiple-grid](https://insightcharts.dev/llms/small-multiple-grid.md), [comparison-brief](https://insightcharts.dev/llms/comparison-brief.md), [data-explorer](https://insightcharts.dev/llms/data-explorer.md), [executive-brief](https://insightcharts.dev/llms/executive-brief.md), [explainer](https://insightcharts.dev/llms/explainer.md), [report-page](https://insightcharts.dev/llms/report-page.md), [scorecard](https://insightcharts.dev/llms/scorecard.md), [social-card](https://insightcharts.dev/llms/social-card.md), [story-deck](https://insightcharts.dev/llms/story-deck.md), [timeline-story](https://insightcharts.dev/llms/timeline-story.md), [visual-essay](https://insightcharts.dev/llms/visual-essay.md)
### More examples
#### Two bars, horizontal
```tsx
import { BarChart } from "insightcharts";
const data = [{ group: "Renters under 35", share: 46 }, { group: "Owners under 35", share: 21 }];
```
#### Vertical over time with a target line
```tsx
import { BarChart } from "insightcharts";
const data = [
{ year: "2019", gw: 98 },
{ year: "2020", gw: 127 },
{ year: "2021", gw: 168 },
{ year: "2022", gw: 239 },
{ year: "2023", gw: 407 },
{ year: "2024", gw: 452 },
];
```
#### Grouped, horizontal, highlighted
```tsx
import { BarChart } from "insightcharts";
const data = [
{ region: "Sub-Saharan Africa", y2000: 26, y2023: 51 },
{ region: "South Asia", y2000: 58, y2023: 99 },
{ region: "Latin America", y2000: 92, y2023: 98 },
{ region: "East Asia and Pacific", y2000: 88, y2023: 99 },
];
```
#### Stacked to 100%
```tsx
import { BarChart } from "insightcharts";
const data = [
{ age: "18 to 29", Daily: 71, Weekly: 19, Rarely: 10 },
{ age: "30 to 49", Daily: 58, Weekly: 25, Rarely: 17 },
{ age: "50 to 64", Daily: 39, Weekly: 28, Rarely: 33 },
{ age: "65 and over", Daily: 22, Weekly: 24, Rarely: 54 },
];
```
#### Forced vertical with names too long for their bands
```tsx
import { BarChart } from "insightcharts";
const data = [
{ mode: "Private car", share: 46 },
{ mode: "Public transport", share: 24 },
{ mode: "Walking", share: 15 },
{ mode: "Cycling and scooters", share: 9 },
{ mode: "Taxi and ride-hailing", share: 4 },
{ mode: "Other", share: 2 },
];
```
#### Stress: 18 long category names
```tsx
import { BarChart } from "insightcharts";
const data = [
{ sector: "Professional, scientific and technical services", share: 64 },
{ sector: "Information and communication", share: 63 },
{ sector: "Financial and insurance activities", share: 62 },
{ sector: "Public administration and defence", share: 55 },
{ sector: "Education", share: 54 },
{ sector: "Real estate activities", share: 48 },
{ sector: "Administrative and support services", share: 47 },
{ sector: "Arts, entertainment and recreation", share: 46 },
// ... 10 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/bar-chart.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/bar-chart.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Beeswarm chart (`beeswarm-chart`)
> Shows a distribution as one dot per row along a value axis, with dots stacked sideways where they would collide.
- Component: `BeeswarmChart` (import from `insightcharts`)
- Spec type: `"beeswarm-chart"`
- Level: atom. Category: distribution.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
Showing a distribution while keeping every individual visible: 30 to about 2,000 named things (countries, players, schools), optionally in a few rows by group, with one or two highlighted.
### Avoid
More than about 2,000 rows or when only the summary matters (use histogram or box-plot); many groups with few rows each (use strip-plot); two numeric measures (use scatter-chart).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/beeswarm-chart
```
The file lands at `components/insightcharts/atoms/beeswarm-chart.tsx`. `@insightcharts/beeswarm-chart-demo` adds a ready-made example next to it.
### Usage
```tsx
import { BeeswarmChart } from "insightcharts";
const data = [
{ country: "Qatar", rate: 0.4 },
{ country: "Kuwait", rate: 0.6 },
{ country: "UAE", rate: 0.9 },
{ country: "Saudi Arabia", rate: 1.2 },
{ country: "Japan", rate: 2.5 },
{ country: "Italy", rate: 2.9 },
{ country: "Germany", rate: 3 },
{ country: "United States", rate: 3.6 },
{ country: "France", rate: 3.1 },
{ country: "Spain", rate: 3.3 },
{ country: "United Kingdom", rate: 3.4 },
{ country: "Canada", rate: 3.2 },
{ country: "Australia", rate: 3.1 },
{ country: "South Korea", rate: 2.4 },
{ country: "Netherlands", rate: 2.8 },
{ country: "Sweden", rate: 2.6 },
{ country: "Norway", rate: 2.5 },
{ country: "Switzerland", rate: 2.3 },
{ country: "Poland", rate: 4.2 },
{ country: "Portugal", rate: 3.1 },
{ country: "Greece", rate: 3.9 },
{ country: "Ireland", rate: 2.7 },
{ country: "Denmark", rate: 2.4 },
{ country: "Finland", rate: 2.9 },
{ country: "Austria", rate: 2.7 },
{ country: "Belgium", rate: 2.9 },
{ country: "Czechia", rate: 3.4 },
{ country: "Hungary", rate: 4.4 },
{ country: "Romania", rate: 5.6 },
{ country: "Bulgaria", rate: 5.9 },
{ country: "Mexico", rate: 12.4 },
{ country: "Brazil", rate: 14.2 },
{ country: "Argentina", rate: 9.8 },
{ country: "Chile", rate: 7.9 },
{ country: "Colombia", rate: 13.1 },
{ country: "Peru", rate: 11.6 },
{ country: "China", rate: 6.3 },
{ country: "India", rate: 26.6 },
{ country: "Indonesia", rate: 21 },
{ country: "Vietnam", rate: 19.8 },
{ country: "Thailand", rate: 8.1 },
{ country: "Malaysia", rate: 7.4 },
{ country: "Philippines", rate: 24.3 },
{ country: "Pakistan", rate: 55.8 },
{ country: "Bangladesh", rate: 28.9 },
{ country: "Turkey", rate: 9.1 },
{ country: "Iran", rate: 12 },
{ country: "Egypt", rate: 19.2 },
{ country: "South Africa", rate: 33.5 },
{ country: "Kenya", rate: 37.2 },
{ country: "Ghana", rate: 42.1 },
{ country: "Ethiopia", rate: 46.8 },
{ country: "Tanzania", rate: 44.3 },
{ country: "Uganda", rate: 41 },
{ country: "Nigeria", rate: 107.2 },
{ country: "Niger", rate: 115.2 },
{ country: "Chad", rate: 101.1 },
{ country: "Mali", rate: 94 },
{ country: "Somalia", rate: 106.1 },
{ country: "Sierra Leone", rate: 100.8 },
{ country: "DR Congo", rate: 76 },
{ country: "Angola", rate: 66.5 },
{ country: "Mozambique", rate: 67.2 },
{ country: "Cameroon", rate: 69.8 },
{ country: "Senegal", rate: 37.4 },
{ country: "Rwanda", rate: 38.3 },
{ country: "Zambia", rate: 56.2 },
{ country: "Sudan", rate: 52.9 },
{ country: "Afghanistan", rate: 55.5 },
{ country: "Yemen", rate: 39.9 },
{ country: "Haiti", rate: 56.3 },
{ country: "Bolivia", rate: 23.7 },
{ country: "Guatemala", rate: 22.1 },
{ country: "Nepal", rate: 26.2 },
{ country: "Myanmar", rate: 40.1 },
{ country: "Cambodia", rate: 23.6 },
{ country: "Morocco", rate: 16.7 },
{ country: "Algeria", rate: 21.5 },
{ country: "Iraq", rate: 23.4 },
{ country: "Ukraine", rate: 7.8 },
{ country: "Russia", rate: 4.8 },
{ country: "Kazakhstan", rate: 9.7 },
{ country: "Uzbekistan", rate: 13.6 },
{ country: "Sri Lanka", rate: 6.4 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "beeswarm-chart",
"data": [
{ "country": "Qatar", "rate": 0.4 },
{ "country": "Kuwait", "rate": 0.6 },
{ "country": "UAE", "rate": 0.9 },
{ "country": "Saudi Arabia", "rate": 1.2 },
{ "country": "Japan", "rate": 2.5 },
{ "country": "Italy", "rate": 2.9 },
{ "country": "Germany", "rate": 3 },
{ "country": "United States", "rate": 3.6 },
{ "country": "France", "rate": 3.1 },
{ "country": "Spain", "rate": 3.3 },
{ "country": "United Kingdom", "rate": 3.4 },
{ "country": "Canada", "rate": 3.2 },
{ "country": "Australia", "rate": 3.1 },
{ "country": "South Korea", "rate": 2.4 },
{ "country": "Netherlands", "rate": 2.8 },
{ "country": "Sweden", "rate": 2.6 },
{ "country": "Norway", "rate": 2.5 },
{ "country": "Switzerland", "rate": 2.3 },
{ "country": "Poland", "rate": 4.2 },
{ "country": "Portugal", "rate": 3.1 },
{ "country": "Greece", "rate": 3.9 },
{ "country": "Ireland", "rate": 2.7 },
{ "country": "Denmark", "rate": 2.4 },
{ "country": "Finland", "rate": 2.9 },
{ "country": "Austria", "rate": 2.7 },
{ "country": "Belgium", "rate": 2.9 },
{ "country": "Czechia", "rate": 3.4 },
{ "country": "Hungary", "rate": 4.4 },
{ "country": "Romania", "rate": 5.6 },
{ "country": "Bulgaria", "rate": 5.9 },
{ "country": "Mexico", "rate": 12.4 },
{ "country": "Brazil", "rate": 14.2 },
{ "country": "Argentina", "rate": 9.8 },
{ "country": "Chile", "rate": 7.9 },
{ "country": "Colombia", "rate": 13.1 },
{ "country": "Peru", "rate": 11.6 },
{ "country": "China", "rate": 6.3 },
{ "country": "India", "rate": 26.6 },
{ "country": "Indonesia", "rate": 21 },
{ "country": "Vietnam", "rate": 19.8 },
{ "country": "Thailand", "rate": 8.1 },
{ "country": "Malaysia", "rate": 7.4 },
{ "country": "Philippines", "rate": 24.3 },
{ "country": "Pakistan", "rate": 55.8 },
{ "country": "Bangladesh", "rate": 28.9 },
{ "country": "Turkey", "rate": 9.1 },
{ "country": "Iran", "rate": 12 },
{ "country": "Egypt", "rate": 19.2 },
{ "country": "South Africa", "rate": 33.5 },
{ "country": "Kenya", "rate": 37.2 },
{ "country": "Ghana", "rate": 42.1 },
{ "country": "Ethiopia", "rate": 46.8 },
{ "country": "Tanzania", "rate": 44.3 },
{ "country": "Uganda", "rate": 41 },
{ "country": "Nigeria", "rate": 107.2 },
{ "country": "Niger", "rate": 115.2 },
{ "country": "Chad", "rate": 101.1 },
{ "country": "Mali", "rate": 94 },
{ "country": "Somalia", "rate": 106.1 },
{ "country": "Sierra Leone", "rate": 100.8 },
{ "country": "DR Congo", "rate": 76 },
{ "country": "Angola", "rate": 66.5 },
{ "country": "Mozambique", "rate": 67.2 },
{ "country": "Cameroon", "rate": 69.8 },
{ "country": "Senegal", "rate": 37.4 },
{ "country": "Rwanda", "rate": 38.3 },
{ "country": "Zambia", "rate": 56.2 },
{ "country": "Sudan", "rate": 52.9 },
{ "country": "Afghanistan", "rate": 55.5 },
{ "country": "Yemen", "rate": 39.9 },
{ "country": "Haiti", "rate": 56.3 },
{ "country": "Bolivia", "rate": 23.7 },
{ "country": "Guatemala", "rate": 22.1 },
{ "country": "Nepal", "rate": 26.2 },
{ "country": "Myanmar", "rate": 40.1 },
{ "country": "Cambodia", "rate": 23.6 },
{ "country": "Morocco", "rate": 16.7 },
{ "country": "Algeria", "rate": 21.5 },
{ "country": "Iraq", "rate": 23.4 },
{ "country": "Ukraine", "rate": 7.8 },
{ "country": "Russia", "rate": 4.8 },
{ "country": "Kazakhstan", "rate": 9.7 },
{ "country": "Uzbekistan", "rate": 13.6 },
{ "country": "Sri Lanka", "rate": 6.4 }
],
"value": "rate",
"label": "country",
"highlight": ["Nigeria", "India", "United States"],
"title": "Nigeria had 107.2 under-five deaths per 1,000 live births in 2022",
"subtitle": "Deaths before age five per 1,000 live births, 84 countries, 2022",
"source": "UN IGME (rounded)",
"note": "Figures are approximate, for illustration"
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `value` (required) | `string` | | Numeric field that positions each dot along the axis. |
| `group` | `string` | | Field that splits the dots into rows, one swarm per group. |
| `series` | `string` | | Field that colours dots by category. Beyond 6 categories the rest turn grey. |
| `label` | `string` | | Field naming each dot, for tooltips, `highlight` and labels on highlighted dots. |
| `radius` | `number` | | Dot radius in px. Default 5 down to 3 by row count; shrinks if a swarm would overflow its row. |
| `median` | `boolean` | `false` | Mark each row's median with a vertical tick. |
| `names` | `Record` | | Display names for series keys. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: no molecule or template yet
### More examples
#### Rows by group, with medians
```tsx
import { BeeswarmChart } from "insightcharts";
const data = [
{ player: "Guard 1", position: "Guard", height: 194 },
{ player: "Guard 2", position: "Guard", height: 194 },
{ player: "Guard 3", position: "Guard", height: 191 },
{ player: "Guard 4", position: "Guard", height: 187 },
{ player: "Guard 5", position: "Guard", height: 189 },
{ player: "Guard 6", position: "Guard", height: 188 },
{ player: "Guard 7", position: "Guard", height: 192 },
{ player: "Guard 8", position: "Guard", height: 191 },
// ... 372 more rows
];
```
#### Colour by series, long format
```tsx
import { BeeswarmChart } from "insightcharts";
const data = [
{ person: "Engineering 1", team: "Engineering", level: "Junior", salary: 112 },
{ person: "Engineering 2", team: "Engineering", level: "Junior", salary: 109 },
{ person: "Engineering 3", team: "Engineering", level: "Junior", salary: 114 },
{ person: "Engineering 4", team: "Engineering", level: "Junior", salary: 127 },
{ person: "Engineering 5", team: "Engineering", level: "Senior", salary: 154 },
{ person: "Engineering 6", team: "Engineering", level: "Junior", salary: 127 },
{ person: "Engineering 7", team: "Engineering", level: "Lead", salary: 170 },
{ person: "Engineering 8", team: "Engineering", level: "Junior", salary: 124 },
// ... 202 more rows
];
```
#### Highlight a whole row
```tsx
import { BeeswarmChart } from "insightcharts";
const data = [
{ person: "Engineering 1", team: "Engineering", level: "Junior", salary: 112 },
{ person: "Engineering 2", team: "Engineering", level: "Junior", salary: 109 },
{ person: "Engineering 3", team: "Engineering", level: "Junior", salary: 114 },
{ person: "Engineering 4", team: "Engineering", level: "Junior", salary: 127 },
{ person: "Engineering 5", team: "Engineering", level: "Senior", salary: 154 },
{ person: "Engineering 6", team: "Engineering", level: "Junior", salary: 127 },
{ person: "Engineering 7", team: "Engineering", level: "Lead", salary: 170 },
{ person: "Engineering 8", team: "Engineering", level: "Junior", salary: 124 },
// ... 202 more rows
];
```
#### Stress: 2,000 dots
```tsx
import { BeeswarmChart } from "insightcharts";
const data = [
{ id: "Trip 1", minutes: 28.8 },
{ id: "Trip 2", minutes: 17.1 },
{ id: "Trip 3", minutes: 17.1 },
{ id: "Trip 4", minutes: 16.4 },
{ id: "Trip 5", minutes: 32.2 },
{ id: "Trip 6", minutes: 16.8 },
{ id: "Trip 7", minutes: 14.4 },
{ id: "Trip 8", minutes: 15.8 },
// ... 1992 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/beeswarm-chart.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/beeswarm-chart.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Big number (`big-number`)
> A featured statistic with its label, change and the context that gives it meaning.
- Component: `BigNumber` (import from `insightcharts`)
- Spec type: `"big-number"`
- Level: atom. Category: figure.
- An HTML component: it takes only the props listed below, plus `theme`, `mode`, `className` and `style` where its source declares them. It does not take the common chart props.
### When to use
A single headline figure, or a row of three or four key numbers. Always give it context: a comparison, a denominator or a period.
### Avoid
Showing a number with no comparison; using a gauge or a one-slice donut for the same job.
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/big-number
```
The file lands at `components/insightcharts/atoms/big-number.tsx`. `@insightcharts/big-number-demo` adds a ready-made example next to it.
### Usage
```tsx
import { BigNumber } from "insightcharts";
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "big-number",
"value": 0.317,
"format": "percent",
"label": "Share of global electricity from renewables",
"delta": 0.021,
"deltaLabel": "vs 2023",
"context": "Up from 19% in 2010"
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `value` (required) | `number \| string` | | The figure. Numbers are formatted with `format`. |
| `label` (required) | `string` | | What it measures, sentence case. |
| `context` | `string` | | The comparison, denominator or period that gives it meaning. |
| `delta` | `number \| string` | | Change against a baseline. Numbers render as signed percentages (0.12 is +12%). |
| `deltaLabel` | `string` | | What the delta is against, e.g. "vs last quarter". |
| `upIsGood` | `boolean` | `true` | Whether an increase is good news; decides the delta colour. |
| `format` | `FormatSpec` | `"auto"` | Number format. |
| `size` | `"hero" \| "stat"` | `"stat"` | `hero` for the one number a piece leads with. |
| `align` | `"left" \| "center"` | `"left"` | Text alignment. |
### Composition
- Composes: nothing (it is a primitive)
- Used by: [annotated-timeline](https://insightcharts.dev/llms/annotated-timeline.md), [before-after](https://insightcharts.dev/llms/before-after.md), [hero-figure](https://insightcharts.dev/llms/hero-figure.md), [metric-breakdown](https://insightcharts.dev/llms/metric-breakdown.md), [poster](https://insightcharts.dev/llms/poster.md), [quote-figure](https://insightcharts.dev/llms/quote-figure.md), [stat-row](https://insightcharts.dev/llms/stat-row.md), [explainer](https://insightcharts.dev/llms/explainer.md), [social-card](https://insightcharts.dev/llms/social-card.md), [story-deck](https://insightcharts.dev/llms/story-deck.md), [timeline-story](https://insightcharts.dev/llms/timeline-story.md)
### Links
- Registry item: https://insightcharts.dev/r/big-number.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/big-number.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Block bars (`block-bar`)
> Compares 3 to 12 values as thick rounded bars from a zero baseline, with each value printed inside the end of its bar and the category beneath.
- Component: `BlockBar` (import from `insightcharts`)
- Spec type: `"block-bar"`
- Level: atom. Category: poster.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
A short series or ranking that should read as a bold graphic with no axis: ticket sales in each of six years, two paired measures in five regions. Add `detail` for a second small figure above each bar and `marker` for an event between two bars.
### Avoid
More than 12 bars, more than two series or stacked parts (use bar-chart); negative values (use diverging-bar-chart); a long time series (use line-chart); values that differ by more than about 50 times, where the short bars vanish (use proportional-area).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/block-bar
```
The file lands at `components/insightcharts/atoms/block-bar.tsx`. `@insightcharts/block-bar-demo` adds a ready-made example next to it.
### Usage
```tsx
import { BlockBar } from "insightcharts";
const data = [
{ year: "2020", tickets: 18400, seats: "41% of seats" },
{ year: "2021", tickets: 22900, seats: "49% of seats" },
{ year: "2022", tickets: 31200, seats: "58% of seats" },
{ year: "2023", tickets: 36800, seats: "64% of seats" },
{ year: "2024", tickets: 41500, seats: "69% of seats" },
{ year: "2025", tickets: 48200, seats: "74% of seats" },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "block-bar",
"data": [
{ "year": "2020", "tickets": 18400, "seats": "41% of seats" },
{ "year": "2021", "tickets": 22900, "seats": "49% of seats" },
{ "year": "2022", "tickets": 31200, "seats": "58% of seats" },
{ "year": "2023", "tickets": 36800, "seats": "64% of seats" },
{ "year": "2024", "tickets": 41500, "seats": "69% of seats" },
{ "year": "2025", "tickets": 48200, "seats": "74% of seats" }
],
"name": "year",
"value": "tickets",
"detail": "seats",
"format": "integer",
"marker": { "after": "2021", "label": "Second screen opened" },
"title": "The Portholm Film Festival sold 48,200 tickets in 2025",
"subtitle": "Tickets sold per year and share of available seats filled, 2020 to 2025",
"source": "Illustrative data",
"note": "Invented figures for a fictional festival"
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `name` (required) | `string` | | Category field: one bar (or one pair of bars) per value. |
| `value` (required) | `string \| string[]` | | Value field, or two fields for paired bars. |
| `series` | `string` | | Field that splits long-format rows into paired series. |
| `detail` | `string` | | Field holding a short secondary label printed above each bar, e.g. "3.5% of GDP". |
| `marker` | `{ after: string; label?: string }` | | A dotted line after the named category, marking an event, with an optional label. |
| `orientation` | `"auto" \| "vertical" \| "horizontal"` | `"auto"` | `auto` draws columns and switches to horizontal bars on narrow widths. |
| `names` | `Record` | | Display names for series keys. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: [count-poster](https://insightcharts.dev/llms/count-poster.md), [timeline-poster](https://insightcharts.dev/llms/timeline-poster.md)
### More examples
#### Paired series
```tsx
import { BlockBar } from "insightcharts";
const data = [
{ line: "Coast line", weekday: 41200, weekend: 26800 },
{ line: "Valley line", weekday: 33500, weekend: 12900 },
{ line: "Airport link", weekday: 27100, weekend: 29400 },
{ line: "Harbour loop", weekday: 15800, weekend: 9200 },
];
```
#### Highlight, click to select
```tsx
import { BlockBar } from "insightcharts";
const data = [
{ country: "Norway", share: 89 },
{ country: "Sweden", share: 58 },
{ country: "Netherlands", share: 35 },
{ country: "Germany", share: 19 },
{ country: "Spain", share: 9 },
];
```
#### Stress: twelve bars, long labels, uneven values
```tsx
import { BlockBar } from "insightcharts";
const data = [
{ port: "Greater Northern Container Terminal", teu: 9400, d: "31% of total" },
{ port: "Southport", teu: 6100, d: "20%" },
{ port: "Marlow Quay", teu: 4300, d: "14%" },
{ port: "Eastern Approaches Deepwater Port", teu: 3900, d: "13%" },
{ port: "Whitby Sands", teu: 2200, d: "7%" },
{ port: "Fenwick", teu: 1500, d: "5%" },
{ port: "Old Harbour", teu: 1100, d: "4%" },
{ port: "Tidewater", teu: 800, d: "3%" },
// ... 4 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/block-bar.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/block-bar.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Box plot (`box-plot`)
> Summarises a numeric field per group as median, quartile box, whiskers and outlier dots, computed from raw rows.
- Component: `BoxPlot` (import from `insightcharts`)
- Spec type: `"box-plot"`
- Level: atom. Category: distribution.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
Comparing the centre and spread of a numeric value across several groups, especially with many rows per group and an audience comfortable with quartiles.
### Avoid
A general audience or fewer than about 20 rows per group (use strip-plot or beeswarm-chart, which show the observations themselves); one group whose shape matters, such as two peaks, which a box hides (use histogram).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/box-plot
```
The file lands at `components/insightcharts/atoms/box-plot.tsx`. `@insightcharts/box-plot-demo` adds a ready-made example next to it.
### Usage
```tsx
import { BoxPlot } from "insightcharts";
const data = [
{ department: "Emergency", minutes: 12 },
{ department: "Emergency", minutes: 18 },
{ department: "Emergency", minutes: 22 },
{ department: "Emergency", minutes: 25 },
{ department: "Emergency", minutes: 27 },
{ department: "Emergency", minutes: 31 },
{ department: "Emergency", minutes: 34 },
{ department: "Emergency", minutes: 38 },
{ department: "Emergency", minutes: 41 },
{ department: "Emergency", minutes: 44 },
{ department: "Emergency", minutes: 47 },
{ department: "Emergency", minutes: 52 },
{ department: "Emergency", minutes: 58 },
{ department: "Emergency", minutes: 63 },
{ department: "Emergency", minutes: 71 },
{ department: "Emergency", minutes: 84 },
{ department: "Emergency", minutes: 96 },
{ department: "Emergency", minutes: 131 },
{ department: "Emergency", minutes: 188 },
{ department: "Orthopaedics", minutes: 9 },
{ department: "Orthopaedics", minutes: 11 },
{ department: "Orthopaedics", minutes: 14 },
{ department: "Orthopaedics", minutes: 15 },
{ department: "Orthopaedics", minutes: 17 },
{ department: "Orthopaedics", minutes: 19 },
{ department: "Orthopaedics", minutes: 20 },
{ department: "Orthopaedics", minutes: 22 },
{ department: "Orthopaedics", minutes: 24 },
{ department: "Orthopaedics", minutes: 25 },
{ department: "Orthopaedics", minutes: 27 },
{ department: "Orthopaedics", minutes: 29 },
{ department: "Orthopaedics", minutes: 31 },
{ department: "Orthopaedics", minutes: 34 },
{ department: "Orthopaedics", minutes: 38 },
{ department: "Orthopaedics", minutes: 43 },
{ department: "Orthopaedics", minutes: 55 },
{ department: "Cardiology", minutes: 6 },
{ department: "Cardiology", minutes: 8 },
{ department: "Cardiology", minutes: 9 },
{ department: "Cardiology", minutes: 11 },
{ department: "Cardiology", minutes: 12 },
{ department: "Cardiology", minutes: 13 },
{ department: "Cardiology", minutes: 15 },
{ department: "Cardiology", minutes: 16 },
{ department: "Cardiology", minutes: 17 },
{ department: "Cardiology", minutes: 18 },
{ department: "Cardiology", minutes: 20 },
{ department: "Cardiology", minutes: 21 },
{ department: "Cardiology", minutes: 23 },
{ department: "Cardiology", minutes: 26 },
{ department: "Cardiology", minutes: 28 },
{ department: "Cardiology", minutes: 33 },
{ department: "Cardiology", minutes: 61 },
{ department: "Paediatrics", minutes: 4 },
{ department: "Paediatrics", minutes: 5 },
{ department: "Paediatrics", minutes: 7 },
{ department: "Paediatrics", minutes: 8 },
{ department: "Paediatrics", minutes: 9 },
{ department: "Paediatrics", minutes: 10 },
{ department: "Paediatrics", minutes: 11 },
{ department: "Paediatrics", minutes: 12 },
{ department: "Paediatrics", minutes: 12 },
{ department: "Paediatrics", minutes: 13 },
{ department: "Paediatrics", minutes: 14 },
{ department: "Paediatrics", minutes: 16 },
{ department: "Paediatrics", minutes: 17 },
{ department: "Paediatrics", minutes: 19 },
{ department: "Paediatrics", minutes: 22 },
{ department: "Paediatrics", minutes: 24 },
{ department: "Dermatology", minutes: 3 },
{ department: "Dermatology", minutes: 4 },
{ department: "Dermatology", minutes: 5 },
{ department: "Dermatology", minutes: 6 },
{ department: "Dermatology", minutes: 6 },
{ department: "Dermatology", minutes: 7 },
{ department: "Dermatology", minutes: 8 },
{ department: "Dermatology", minutes: 9 },
{ department: "Dermatology", minutes: 9 },
{ department: "Dermatology", minutes: 10 },
{ department: "Dermatology", minutes: 11 },
{ department: "Dermatology", minutes: 12 },
{ department: "Dermatology", minutes: 13 },
{ department: "Dermatology", minutes: 15 },
{ department: "Dermatology", minutes: 18 },
{ department: "Dermatology", minutes: 35 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "box-plot",
"data": [
{ "department": "Emergency", "minutes": 12 },
{ "department": "Emergency", "minutes": 18 },
{ "department": "Emergency", "minutes": 22 },
{ "department": "Emergency", "minutes": 25 },
{ "department": "Emergency", "minutes": 27 },
{ "department": "Emergency", "minutes": 31 },
{ "department": "Emergency", "minutes": 34 },
{ "department": "Emergency", "minutes": 38 },
{ "department": "Emergency", "minutes": 41 },
{ "department": "Emergency", "minutes": 44 },
{ "department": "Emergency", "minutes": 47 },
{ "department": "Emergency", "minutes": 52 },
{ "department": "Emergency", "minutes": 58 },
{ "department": "Emergency", "minutes": 63 },
{ "department": "Emergency", "minutes": 71 },
{ "department": "Emergency", "minutes": 84 },
{ "department": "Emergency", "minutes": 96 },
{ "department": "Emergency", "minutes": 131 },
{ "department": "Emergency", "minutes": 188 },
{ "department": "Orthopaedics", "minutes": 9 },
{ "department": "Orthopaedics", "minutes": 11 },
{ "department": "Orthopaedics", "minutes": 14 },
{ "department": "Orthopaedics", "minutes": 15 },
{ "department": "Orthopaedics", "minutes": 17 },
{ "department": "Orthopaedics", "minutes": 19 },
{ "department": "Orthopaedics", "minutes": 20 },
{ "department": "Orthopaedics", "minutes": 22 },
{ "department": "Orthopaedics", "minutes": 24 },
{ "department": "Orthopaedics", "minutes": 25 },
{ "department": "Orthopaedics", "minutes": 27 },
{ "department": "Orthopaedics", "minutes": 29 },
{ "department": "Orthopaedics", "minutes": 31 },
{ "department": "Orthopaedics", "minutes": 34 },
{ "department": "Orthopaedics", "minutes": 38 },
{ "department": "Orthopaedics", "minutes": 43 },
{ "department": "Orthopaedics", "minutes": 55 },
{ "department": "Cardiology", "minutes": 6 },
{ "department": "Cardiology", "minutes": 8 },
{ "department": "Cardiology", "minutes": 9 },
{ "department": "Cardiology", "minutes": 11 },
{ "department": "Cardiology", "minutes": 12 },
{ "department": "Cardiology", "minutes": 13 },
{ "department": "Cardiology", "minutes": 15 },
{ "department": "Cardiology", "minutes": 16 },
{ "department": "Cardiology", "minutes": 17 },
{ "department": "Cardiology", "minutes": 18 },
{ "department": "Cardiology", "minutes": 20 },
{ "department": "Cardiology", "minutes": 21 },
{ "department": "Cardiology", "minutes": 23 },
{ "department": "Cardiology", "minutes": 26 },
{ "department": "Cardiology", "minutes": 28 },
{ "department": "Cardiology", "minutes": 33 },
{ "department": "Cardiology", "minutes": 61 },
{ "department": "Paediatrics", "minutes": 4 },
{ "department": "Paediatrics", "minutes": 5 },
{ "department": "Paediatrics", "minutes": 7 },
{ "department": "Paediatrics", "minutes": 8 },
{ "department": "Paediatrics", "minutes": 9 },
{ "department": "Paediatrics", "minutes": 10 },
{ "department": "Paediatrics", "minutes": 11 },
{ "department": "Paediatrics", "minutes": 12 },
{ "department": "Paediatrics", "minutes": 12 },
{ "department": "Paediatrics", "minutes": 13 },
{ "department": "Paediatrics", "minutes": 14 },
{ "department": "Paediatrics", "minutes": 16 },
{ "department": "Paediatrics", "minutes": 17 },
{ "department": "Paediatrics", "minutes": 19 },
{ "department": "Paediatrics", "minutes": 22 },
{ "department": "Paediatrics", "minutes": 24 },
{ "department": "Dermatology", "minutes": 3 },
{ "department": "Dermatology", "minutes": 4 },
{ "department": "Dermatology", "minutes": 5 },
{ "department": "Dermatology", "minutes": 6 },
{ "department": "Dermatology", "minutes": 6 },
{ "department": "Dermatology", "minutes": 7 },
{ "department": "Dermatology", "minutes": 8 },
{ "department": "Dermatology", "minutes": 9 },
{ "department": "Dermatology", "minutes": 9 },
{ "department": "Dermatology", "minutes": 10 },
{ "department": "Dermatology", "minutes": 11 },
{ "department": "Dermatology", "minutes": 12 },
{ "department": "Dermatology", "minutes": 13 },
{ "department": "Dermatology", "minutes": 15 },
{ "department": "Dermatology", "minutes": 18 },
{ "department": "Dermatology", "minutes": 35 }
],
"value": "minutes",
"group": "department",
"highlight": "Emergency",
"format": "{value} min",
"title": "The median emergency wait was 44 minutes",
"subtitle": "Minutes from arrival to being seen, sampled visits by department",
"note": "Illustrative data. Box: middle half of visits; line: median; whiskers: within 1.5 × IQR; dots: outliers."
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `value` (required) | `string` | | Numeric field to summarise. Pass raw rows; quartiles are computed in the chart. |
| `group` | `string` | | Category field: one box per distinct value. Omit for a single box. |
| `label` | `string` | | Field naming each row, shown when hovering an outlier. |
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Horizontal leaves room for long names; vertical suits ordered categories. |
| `whiskers` | `"tukey" \| "minmax"` | `"tukey"` | `tukey`: furthest point within 1.5 × IQR, the rest drawn as outliers. `minmax`: the extremes. |
| `sort` | `"none" \| "asc" \| "desc"` | `"none"` | Order boxes by median. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: no molecule or template yet
### More examples
#### Sorted, long labels, many outliers
```tsx
import { BoxPlot } from "insightcharts";
const data = [
{ parcel: "Northwind-1000", carrier: "Northwind Express Parcel Service", days: 2.4 },
{ parcel: "Northwind-1001", carrier: "Northwind Express Parcel Service", days: 1.8 },
{ parcel: "Northwind-1002", carrier: "Northwind Express Parcel Service", days: 2.2 },
{ parcel: "Northwind-1003", carrier: "Northwind Express Parcel Service", days: 3.1 },
{ parcel: "Northwind-1004", carrier: "Northwind Express Parcel Service", days: 4 },
{ parcel: "Northwind-1005", carrier: "Northwind Express Parcel Service", days: 2.4 },
{ parcel: "Northwind-1006", carrier: "Northwind Express Parcel Service", days: 2.4 },
{ parcel: "Northwind-1007", carrier: "Northwind Express Parcel Service", days: 1.1 },
// ... 1072 more rows
];
```
#### Vertical, ordered categories
```tsx
import { BoxPlot } from "insightcharts";
const data = [
{ month: "Jan", temp: 4.8 },
{ month: "Jan", temp: 7.4 },
{ month: "Jan", temp: 0.4 },
{ month: "Jan", temp: 3.5 },
{ month: "Jan", temp: -0.7 },
{ month: "Jan", temp: 2 },
{ month: "Jan", temp: -1.4 },
{ month: "Jan", temp: 7.2 },
// ... 352 more rows
];
```
#### Single box, min-max whiskers
```tsx
import { BoxPlot } from "insightcharts";
const data = [
{ parcel: "Swift-1000", carrier: "Swift", days: 1.9 },
{ parcel: "Swift-1001", carrier: "Swift", days: 1.2 },
{ parcel: "Swift-1002", carrier: "Swift", days: 1.3 },
{ parcel: "Swift-1003", carrier: "Swift", days: 1.3 },
{ parcel: "Swift-1004", carrier: "Swift", days: 1.1 },
{ parcel: "Swift-1005", carrier: "Swift", days: 0.9 },
{ parcel: "Swift-1006", carrier: "Swift", days: 1.5 },
{ parcel: "Swift-1007", carrier: "Swift", days: 1.7 },
// ... 172 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/box-plot.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/box-plot.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Bubble network (`bubble-network`)
> Draws a small set of circles sized by value and joined by thin lines, with one circle highlighted and tagged.
- Component: `BubbleNetwork` (import from `insightcharts`)
- Spec type: `"bubble-network"`
- Level: atom. Category: poster.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
Showing which of up to about 12 named things are connected, and how big each is: trade blocs, route maps between hubs, partnerships, shared ownership. Give `x` and `y` to place the circles by hand, for example roughly geographically.
### Avoid
Flows with magnitudes between stages (use sankey-chart); more than about 12 circles or a dense web of links (use heatmap as a matrix, or data-table); comparison of the sizes alone (use bar-chart or circle-pack).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/bubble-network
```
The file lands at `components/insightcharts/atoms/bubble-network.tsx`. `@insightcharts/bubble-network-demo` adds a ready-made example next to it.
### Usage
```tsx
import { BubbleNetwork } from "insightcharts";
const data = [
{ airport: "Lisbon", m: 34 },
{ airport: "Madrid", m: 60 },
{ airport: "Paris", m: 67 },
{ airport: "Frankfurt", m: 59 },
{ airport: "Rome", m: 41 },
{ airport: "Athens", m: 28 },
{ airport: "Istanbul", m: 76 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "bubble-network",
"data": [
{ "airport": "Lisbon", "m": 34 },
{ "airport": "Madrid", "m": 60 },
{ "airport": "Paris", "m": 67 },
{ "airport": "Frankfurt", "m": 59 },
{ "airport": "Rome", "m": 41 },
{ "airport": "Athens", "m": 28 },
{ "airport": "Istanbul", "m": 76 }
],
"name": "airport",
"value": "m",
"links": [
{ "from": "Istanbul", "to": "Frankfurt" },
{ "from": "Istanbul", "to": "Paris" },
{ "from": "Istanbul", "to": "Rome" },
{ "from": "Istanbul", "to": "Athens" },
{ "from": "Istanbul", "to": "Madrid" },
{ "from": "Istanbul", "to": "Lisbon" },
{ "from": "Madrid", "to": "Lisbon" },
{ "from": "Paris", "to": "Frankfurt" },
{ "from": "Rome", "to": "Athens" }
],
"highlight": "Istanbul",
"tag": "Busiest",
"format": "{value}m",
"title": "Istanbul handled 76 million passengers in 2023",
"subtitle": "Passengers at seven airports, millions, 2023. Lines show the routes in this example.",
"note": "Rounded passenger figures; the routes drawn are illustrative"
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `name` (required) | `string` | | Name field: one circle per row. |
| `value` (required) | `string` | | Value field: circle area. |
| `x` | `string` | | Horizontal position field, 0 (left) to 100 (right). With `y`, places the circles by hand. |
| `y` | `string` | | Vertical position field, 0 (top) to 100 (bottom). |
| `links` | `{ from: string; to: string; value?: number }[]` | | Lines between circles, by name. `value` sets the line width and is printed on the line. |
| `tag` | `string` | | Short tag attached to the highlighted circle, e.g. "Joined 2024". |
| `linkFormat` | `FormatSpec` | | How to print link values. |
| `linkLabels` | `boolean` | | Print link values on the lines. Default: on when any link has a value. |
| `layout` | `"ring" \| "hub"` | | Without `x` and `y`: "ring" spaces the circles round an ellipse in data order, "hub" also moves the most-connected circle to the middle. Default: "hub" when one circle links to most of the others. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: [atlas-poster](https://insightcharts.dev/llms/atlas-poster.md), [network-poster](https://insightcharts.dev/llms/network-poster.md)
### More examples
#### Placed by hand, link values, click to select
```tsx
import { BubbleNetwork } from "insightcharts";
const data = [
{ depot: "Northgate", vans: 120, x: 22, y: 12 },
{ depot: "Riverside", vans: 310, x: 50, y: 46 },
{ depot: "Eastfield", vans: 85, x: 88, y: 30 },
{ depot: "Southwell", vans: 160, x: 62, y: 92 },
{ depot: "Westmere", vans: 44, x: 8, y: 70 },
{ depot: "Airport", vans: 205, x: 86, y: 78 },
];
```
#### Ring layout, no hub
```tsx
import { BubbleNetwork } from "insightcharts";
const data = [
{ course: "Algebra", students: 48 },
{ course: "Geometry", students: 31 },
{ course: "Statistics", students: 64 },
{ course: "Calculus", students: 22 },
{ course: "Logic", students: 12 },
{ course: "Number theory", students: 9 },
{ course: "Probability", students: 37 },
{ course: "Topology", students: 5 },
];
```
#### Stress: 12 circles, long names, values from 1 to 480
```tsx
import { BubbleNetwork } from "insightcharts";
const data = [
{ member: "Central Co-operative Wholesale Society", m: 480 },
{ member: "Dairy", m: 210 },
{ member: "Northern Grain and Seed Merchants", m: 96 },
{ member: "Bakery", m: 150 },
{ member: "Orchards", m: 33 },
{ member: "Fishery", m: 61 },
{ member: "Upland Sheep Farmers' Association", m: 18 },
{ member: "Brewery", m: 74 },
// ... 4 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/bubble-network.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/bubble-network.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Bubble scale (`bubble-scale`)
> Places circles sized by one measure along a single value scale set by another, spread sideways so none overlap, with boxed scale marks instead of an axis.
- Component: `BubbleScale` (import from `insightcharts`)
- Spec type: `"bubble-scale"`
- Level: atom. Category: poster.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
A poster answer to "where does each one sit on this scale, and how big is it": 5 to about 40 named things with a rate or score (the scale) and a magnitude (the area), such as import dependency against share of demand.
### Avoid
Two measures that should be read against each other on two axes (use scatter-chart); one measure only (use beeswarm-chart or strip-plot); precise comparison of the sizes (use bar-chart); more than about 60 rows (use beeswarm-chart).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/bubble-scale
```
The file lands at `components/insightcharts/atoms/bubble-scale.tsx`. `@insightcharts/bubble-scale-demo` adds a ready-made example next to it.
### Usage
```tsx
import { BubbleScale } from "insightcharts";
const data = [
{ material: "Lithium", imported: 100, kt: 38 },
{ material: "Cobalt", imported: 86, kt: 21 },
{ material: "Graphite", imported: 98, kt: 96 },
{ material: "Nickel", imported: 71, kt: 164 },
{ material: "Manganese", imported: 96, kt: 52 },
{ material: "Copper", imported: 48, kt: 410 },
{ material: "Aluminium", imported: 59, kt: 290 },
{ material: "Rare earths", imported: 100, kt: 9 },
{ material: "Silicon metal", imported: 64, kt: 73 },
{ material: "Zinc", imported: 22, kt: 118 },
{ material: "Steel scrap", imported: 4, kt: 520 },
{ material: "Tin", imported: 78, kt: 14 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "bubble-scale",
"data": [
{ "material": "Lithium", "imported": 100, "kt": 38 },
{ "material": "Cobalt", "imported": 86, "kt": 21 },
{ "material": "Graphite", "imported": 98, "kt": 96 },
{ "material": "Nickel", "imported": 71, "kt": 164 },
{ "material": "Manganese", "imported": 96, "kt": 52 },
{ "material": "Copper", "imported": 48, "kt": 410 },
{ "material": "Aluminium", "imported": 59, "kt": 290 },
{ "material": "Rare earths", "imported": 100, "kt": 9 },
{ "material": "Silicon metal", "imported": 64, "kt": 73 },
{ "material": "Zinc", "imported": 22, "kt": 118 },
{ "material": "Steel scrap", "imported": 4, "kt": 520 },
{ "material": "Tin", "imported": 78, "kt": 14 }
],
"name": "material",
"value": "imported",
"size": "kt",
"format": "{value}%",
"sizeFormat": "{value} kt",
"domain": [0, 100],
"ticks": [0, 20, 40, 60, 80, 100],
"scaleLabel": "Imported",
"title": "Imports supply between 4% and 100% of the region's demand for 12 materials",
"subtitle": "Share of each material imported (position) and annual demand in thousand tonnes (area), 12 battery and grid materials",
"note": "Illustrative figures for an invented region"
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `name` (required) | `string` | | Name field: one circle per row. |
| `value` (required) | `string` | | Numeric field that positions each circle along the scale. Printed with `format`. |
| `size` (required) | `string` | | Numeric field for circle area. Printed inside or beside each circle with `sizeFormat`. |
| `series` | `string` | | Field that colours circles by category. Default: circles are shaded along the scale with the sequential ramp. |
| `orientation` | `"vertical" \| "horizontal"` | `"vertical"` | Direction of the scale. Narrow containers always use vertical. |
| `domain` | `[number, number]` | | Scale range. Default: the data extent rounded out to the tick step. |
| `ticks` | `number[]` | | Values to mark on the scale. Default: about five round values. |
| `scaleLabel` | `string` | | Short name of the scale measure, printed at the head of the scale. |
| `sizeFormat` | `FormatSpec` | | How to print the size measure. |
| `guides` | `boolean` | `true` | Thin dotted guide line at each scale mark. |
| `names` | `Record` | | Display names for series keys. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: [flow-poster](https://insightcharts.dev/llms/flow-poster.md)
### More examples
#### Colour by series, horizontal
```tsx
import { BubbleScale } from "insightcharts";
const data = [
{ district: "Harbourside", ring: "Inner", carFree: 71, thousand: 18 },
{ district: "Old Town", ring: "Inner", carFree: 64, thousand: 31 },
{ district: "Canal Quarter", ring: "Inner", carFree: 58, thousand: 22 },
{ district: "Station Fields", ring: "Inner", carFree: 49, thousand: 12 },
{ district: "Marketgate", ring: "Inner", carFree: 44, thousand: 27 },
{ district: "Northmoor", ring: "Outer", carFree: 37, thousand: 46 },
{ district: "Eastcliff", ring: "Outer", carFree: 33, thousand: 39 },
{ district: "Larkhill", ring: "Outer", carFree: 29, thousand: 52 },
// ... 9 more rows
];
```
#### Highlight one circle, click to select
```tsx
import { BubbleScale } from "insightcharts";
const data = [
{ district: "Harbourside", ring: "Inner", carFree: 71, thousand: 18 },
{ district: "Old Town", ring: "Inner", carFree: 64, thousand: 31 },
{ district: "Canal Quarter", ring: "Inner", carFree: 58, thousand: 22 },
{ district: "Station Fields", ring: "Inner", carFree: 49, thousand: 12 },
{ district: "Marketgate", ring: "Inner", carFree: 44, thousand: 27 },
{ district: "Northmoor", ring: "Outer", carFree: 37, thousand: 46 },
{ district: "Eastcliff", ring: "Outer", carFree: 33, thousand: 39 },
{ district: "Larkhill", ring: "Outer", carFree: 29, thousand: 52 },
// ... 9 more rows
];
```
#### Stress: 40 circles crowded at one end, sizes from 1 to 900
```tsx
import { BubbleScale } from "insightcharts";
const data = [
{ fund: "Fund A", fee: 1.15, bn: 900 },
{ fund: "Fund B", fee: 0.22, bn: 413 },
{ fund: "Fund C", fee: 0.11, bn: 226 },
{ fund: "Fund D", fee: 0.28, bn: 139 },
{ fund: "Fund E", fee: 0.17, bn: 87 },
{ fund: "Fund F", fee: 0.66, bn: 48 },
{ fund: "Fund G", fee: 0.23, bn: 33 },
{ fund: "Fund H", fee: 0.12, bn: 24 },
// ... 32 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/bubble-scale.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/bubble-scale.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Bullet chart (`bullet-chart`)
> Compares one measure per row with its target, optionally against qualitative bands such as poor / fair / good.
- Component: `BulletChart` (import from `insightcharts`)
- Spec type: `"bullet-chart"`
- Level: atom. Category: comparison.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
Progress against a target or budget for one to about 10 measures, including measures in different units (`scale: "independent"`); a compact scorecard instead of a row of gauges.
### Avoid
No target or thresholds to compare with (use bar-chart); a single share of a whole (use progress-meter); change over time (use line-chart).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/bullet-chart
```
The file lands at `components/insightcharts/atoms/bullet-chart.tsx`. `@insightcharts/bullet-chart-demo` adds a ready-made example next to it.
### Usage
```tsx
import { BulletChart } from "insightcharts";
const data = [
{ region: "North America", sales: 94, target: 80 },
{ region: "Europe", sales: 71, target: 85 },
{ region: "Asia Pacific", sales: 63, target: 60 },
{ region: "Latin America", sales: 38, target: 55 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "bullet-chart",
"data": [
{ "region": "North America", "sales": 94, "target": 80 },
{ "region": "Europe", "sales": 71, "target": 85 },
{ "region": "Asia Pacific", "sales": 63, "target": 60 },
{ "region": "Latin America", "sales": 38, "target": 55 }
],
"category": "region",
"value": "sales",
"target": "target",
"bands": [50, 75, 110],
"bandNames": ["Below plan", "On plan", "Stretch"],
"names": { "sales": "Sales", "target": "Target" },
"format": "currency:USD",
"height": 240,
"title": "Europe sold $71 million against a target of $85 million in 2024",
"subtitle": "Sales against target by region, $ millions, 2024",
"source": "Company accounts",
"note": "Illustrative figures."
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `category` (required) | `string` | | Field naming each row. |
| `value` (required) | `string` | | Field with the measured value. Values below zero are not supported. |
| `target` | `string` | | Field with the target, drawn as a tick across the bar. |
| `bands` | `number[] \| string[]` | | Ascending upper bounds of qualitative bands, or the fields holding them per row. Omit unless the thresholds are real. |
| `bandNames` | `string[]` | | Band names, lowest first, for the legend and tooltip. |
| `max` | `number \| string` | | End of the scale: a number or a per-row field. Default: just past the largest value, target or band. |
| `scale` | `"shared" \| "independent"` | `"shared"` | One axis for all rows, or each row on its own scale (different units). |
| `formatField` | `string` | | Field holding a format spec per row, for mixed units. |
| `names` | `Record` | | Display names for the value and target fields. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: no molecule or template yet
### More examples
#### Scorecard with a separate scale per row
```tsx
import { BulletChart } from "insightcharts";
const data = [
{ kpi: "Revenue", actual: 4.6, goal: 5, low: 3.5, mid: 4.5, high: 6, unit: "${value}m" },
{ kpi: "Gross margin", actual: 41, goal: 38, low: 25, mid: 35, high: 50, unit: "{value}%" },
{
kpi: "New customers",
actual: 1320,
goal: 1500,
low: 900,
mid: 1300,
high: 1800,
unit: "integer",
},
{
kpi: "Customer satisfaction",
actual: 4.4,
goal: 4.5,
low: 3.5,
mid: 4.2,
high: 5,
unit: "decimal:1",
},
{
kpi: "Average order value",
actual: 58,
goal: 52,
low: 40,
mid: 50,
high: 65,
unit: "currency:EUR",
},
];
```
#### No thresholds, one row highlighted
```tsx
import { BulletChart } from "insightcharts";
const data = [
{ team: "Payments", done: 86, plan: 80 },
{ team: "Search", done: 74, plan: 80 },
{ team: "Onboarding", done: 52, plan: 80 },
{ team: "Mobile", done: 91, plan: 80 },
{ team: "Data platform", done: 79, plan: 80 },
];
```
#### Stress: 10 rows with long labels
```tsx
import { BulletChart } from "insightcharts";
const data = [
{ measure: "Percentage of emergency patients seen within four hours", actual: 71, standard: 95 },
{
measure: "Ambulance response to life-threatening calls within target",
actual: 64,
standard: 90,
},
{
measure: "Cancer patients starting treatment within 62 days of referral",
actual: 62,
standard: 85,
},
{ measure: "Planned operations within 18 weeks", actual: 58, standard: 92 },
{ measure: "Diagnostic tests within six weeks", actual: 76, standard: 99 },
{ measure: "GP appointments within two weeks", actual: 83, standard: 85 },
{ measure: "Mental health referrals seen within four weeks", actual: 69, standard: 75 },
{ measure: "Beds occupied by patients fit for discharge (inverted)", actual: 88, standard: 95 },
// ... 2 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/bullet-chart.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/bullet-chart.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Bump chart (`bump-chart`)
> Shows how the ranking of entities changes over time, with rank 1 at the top and each line named at its end.
- Component: `BumpChart` (import from `insightcharts`)
- Spec type: `"bump-chart"`
- Level: atom. Category: trend.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
League tables over 3 to about 15 periods for up to about 12 entities, when the order and the changes in rank are what matter and the underlying values differ too much (or too little) to share an axis.
### Avoid
When the size of the values matters (use line-chart); only two periods (use slope-chart); a single ranking (use bar-chart or lollipop-chart).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/bump-chart
```
The file lands at `components/insightcharts/atoms/bump-chart.tsx`. `@insightcharts/bump-chart-demo` adds a ready-made example next to it.
### Usage
```tsx
import { BumpChart } from "insightcharts";
const data = [
{ year: "2019", Tesla: 0.37, BYD: 0.23, SAIC: 0.16, Volkswagen: 0.14, Hyundai: 0.13, Geely: 0.12 },
{ year: "2020", Tesla: 0.5, BYD: 0.18, SAIC: 0.27, Volkswagen: 0.42, Hyundai: 0.2, Geely: 0.1 },
{ year: "2021", Tesla: 0.94, BYD: 0.6, SAIC: 0.68, Volkswagen: 0.76, Hyundai: 0.35, Geely: 0.2 },
{ year: "2022", Tesla: 1.31, BYD: 1.86, SAIC: 0.72, Volkswagen: 0.83, Hyundai: 0.51, Geely: 0.65 },
{ year: "2023", Tesla: 1.81, BYD: 3.02, SAIC: 0.79, Volkswagen: 1, Hyundai: 0.57, Geely: 0.93 },
{ year: "2024", Tesla: 1.79, BYD: 4.27, SAIC: 0.9, Volkswagen: 1.04, Hyundai: 0.56, Geely: 1.39 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "bump-chart",
"data": [
{
"year": "2019",
"Tesla": 0.37,
"BYD": 0.23,
"SAIC": 0.16,
"Volkswagen": 0.14,
"Hyundai": 0.13,
"Geely": 0.12
},
{
"year": "2020",
"Tesla": 0.5,
"BYD": 0.18,
"SAIC": 0.27,
"Volkswagen": 0.42,
"Hyundai": 0.2,
"Geely": 0.1
},
{
"year": "2021",
"Tesla": 0.94,
"BYD": 0.6,
"SAIC": 0.68,
"Volkswagen": 0.76,
"Hyundai": 0.35,
"Geely": 0.2
},
{
"year": "2022",
"Tesla": 1.31,
"BYD": 1.86,
"SAIC": 0.72,
"Volkswagen": 0.83,
"Hyundai": 0.51,
"Geely": 0.65
},
{
"year": "2023",
"Tesla": 1.81,
"BYD": 3.02,
"SAIC": 0.79,
"Volkswagen": 1,
"Hyundai": 0.57,
"Geely": 0.93
},
{
"year": "2024",
"Tesla": 1.79,
"BYD": 4.27,
"SAIC": 0.9,
"Volkswagen": 1.04,
"Hyundai": 0.56,
"Geely": 1.39
}
],
"x": "year",
"y": ["Tesla", "BYD", "SAIC", "Volkswagen", "Hyundai", "Geely"],
"highlight": "BYD",
"format": "{value}m",
"height": 300,
"title": "BYD rose from fifth in 2020 to first in 2022",
"subtitle": "Rank among six carmakers by plug-in vehicles sold worldwide, 2019 to 2024",
"source": "Company reports",
"note": "Approximate figures."
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `x` (required) | `string` | | Time or ordered category field. |
| `y` (required) | `string \| string[]` | | Value field to rank by, or several (one line each). |
| `series` | `string` | | Field that splits rows into entities (long data). |
| `ranked` | `boolean` | `false` | The values are already ranks (1 = best) and are plotted as given. |
| `order` | `"desc" \| "asc"` | `"desc"` | Which values rank first when ranks are computed: largest (`desc`) or smallest (`asc`). |
| `startLabels` | `boolean` | | Name lines at the start as well as the end. Default: on when the chart is wide enough. |
| `names` | `Record` | | Display names for series keys. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: no molecule or template yet
### More examples
#### Every line coloured
```tsx
import { BumpChart } from "insightcharts";
const data = [
{ season: "2020", Arsenal: 8, Liverpool: 1, "Manchester City": 2, Chelsea: 4, Tottenham: 6 },
{ season: "2021", Arsenal: 8, Liverpool: 3, "Manchester City": 1, Chelsea: 4, Tottenham: 7 },
{ season: "2022", Arsenal: 5, Liverpool: 2, "Manchester City": 1, Chelsea: 3, Tottenham: 4 },
{ season: "2023", Arsenal: 2, Liverpool: 5, "Manchester City": 1, Chelsea: 12, Tottenham: 8 },
{ season: "2024", Arsenal: 2, Liverpool: 3, "Manchester City": 1, Chelsea: 6, Tottenham: 5 },
];
```
#### Long format with a late entrant
```tsx
import { BumpChart } from "insightcharts";
const data = [
{ year: "2021", language: "Python", share: 30 },
{ year: "2021", language: "JavaScript", share: 34 },
{ year: "2021", language: "Java", share: 22 },
{ year: "2021", language: "Go", share: 6 },
{ year: "2022", language: "Python", share: 33 },
{ year: "2022", language: "JavaScript", share: 32 },
{ year: "2022", language: "Java", share: 20 },
{ year: "2022", language: "Go", share: 8 },
// ... 11 more rows
];
```
#### Stress: 12 entities over 10 periods
```tsx
import { BumpChart } from "insightcharts";
const data = [
{
year: "2015",
"United States": 100,
China: 97,
Japan: 94,
Germany: 82,
India: 79,
"United Kingdom": 67,
France: 64,
Italy: 52,
Brazil: 49,
Canada: 37,
"Republic of Korea": 34,
"Russian Federation": 31,
},
{
year: "2016",
"United States": 107,
China: 111,
Japan: 101,
Germany: 83,
India: 92,
"United Kingdom": 74,
France: 66,
Italy: 59,
Brazil: 55,
Canada: 48,
"Republic of Korea": 33,
"Russian Federation": 34,
},
{
year: "2017",
"United States": 105,
China: 128,
Japan: 109,
Germany: 93,
India: 108,
"United Kingdom": 91,
France: 68,
Italy: 57,
Brazil: 62,
Canada: 50,
"Republic of Korea": 32,
"Russian Federation": 38,
},
{
year: "2018",
"United States": 103,
China: 158,
Japan: 118,
Germany: 94,
India: 129,
"United Kingdom": 101,
France: 71,
Italy: 55,
Brazil: 79,
Canada: 53,
"Republic of Korea": 40,
"Russian Federation": 43,
},
{
year: "2019",
"United States": 101,
China: 182,
Japan: 128,
Germany: 95,
India: 162,
"United Kingdom": 113,
France: 83,
Italy: 53,
Brazil: 90,
Canada: 56,
"Republic of Korea": 39,
"Russian Federation": 49,
},
{
year: "2020",
"United States": 108,
China: 211,
Japan: 149,
Germany: 96,
India: 193,
"United Kingdom": 126,
France: 86,
Italy: 51,
Brazil: 103,
Canada: 69,
"Republic of Korea": 39,
"Russian Federation": 67,
},
{
year: "2021",
"United States": 106,
China: 246,
Japan: 162,
Germany: 97,
India: 230,
"United Kingdom": 151,
France: 90,
Italy: 58,
Brazil: 118,
Canada: 73,
"Republic of Korea": 38,
"Russian Federation": 77,
},
{
year: "2022",
"United States": 104,
China: 296,
Japan: 176,
Germany: 108,
India: 275,
"United Kingdom": 169,
France: 94,
Italy: 56,
Brazil: 136,
Canada: 78,
"Republic of Korea": 46,
"Russian Federation": 90,
},
// ... 2 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/bump-chart.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/bump-chart.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Calendar heatmap (`calendar-heatmap`)
> Colours every day of one or more years by a value, laid out as a grid of weeks.
- Component: `CalendarHeatmap` (import from `insightcharts`)
- Spec type: `"calendar-heatmap"`
- Level: atom. Category: trend.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
Daily data over months or years where weekly rhythm, seasonality, streaks and gaps matter: activity, sales, incidents, habits.
### Avoid
Reading the trend or exact values (use line-chart); fewer than about three months of days (use bar-chart); a non-daily grain such as hour by weekday (use heatmap).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/calendar-heatmap
```
The file lands at `components/insightcharts/atoms/calendar-heatmap.tsx`. `@insightcharts/calendar-heatmap-demo` adds a ready-made example next to it.
### Usage
```tsx
import { CalendarHeatmap } from "insightcharts";
const data = [
{ date: "2024-01-01", commits: 16 },
{ date: "2024-01-02", commits: 9 },
{ date: "2024-01-03", commits: 8 },
{ date: "2024-01-04", commits: 6 },
{ date: "2024-01-05", commits: 8 },
{ date: "2024-01-06", commits: 1 },
{ date: "2024-01-07", commits: 1 },
{ date: "2024-01-08", commits: 17 },
{ date: "2024-01-09", commits: 12 },
{ date: "2024-01-10", commits: 13 },
{ date: "2024-01-11", commits: 14 },
{ date: "2024-01-12", commits: 9 },
{ date: "2024-01-13", commits: 2 },
{ date: "2024-01-14", commits: 2 },
{ date: "2024-01-15", commits: 11 },
{ date: "2024-01-16", commits: 14 },
{ date: "2024-01-17", commits: 10 },
{ date: "2024-01-18", commits: 11 },
{ date: "2024-01-19", commits: 14 },
{ date: "2024-01-20", commits: 2 },
{ date: "2024-01-21", commits: 2 },
{ date: "2024-01-22", commits: 11 },
{ date: "2024-01-23", commits: 9 },
{ date: "2024-01-24", commits: 14 },
{ date: "2024-01-25", commits: 8 },
{ date: "2024-01-26", commits: 14 },
{ date: "2024-01-27", commits: 1 },
{ date: "2024-01-28", commits: 1 },
{ date: "2024-01-29", commits: 10 },
{ date: "2024-01-30", commits: 5 },
{ date: "2024-01-31", commits: 11 },
{ date: "2024-02-01", commits: 12 },
{ date: "2024-02-02", commits: 15 },
{ date: "2024-02-03", commits: 1 },
{ date: "2024-02-04", commits: 2 },
{ date: "2024-02-05", commits: 10 },
{ date: "2024-02-06", commits: 9 },
{ date: "2024-02-07", commits: 12 },
{ date: "2024-02-08", commits: 6 },
{ date: "2024-02-09", commits: 13 },
{ date: "2024-02-10", commits: 1 },
{ date: "2024-02-11", commits: 1 },
{ date: "2024-02-12", commits: 7 },
{ date: "2024-02-13", commits: 17 },
{ date: "2024-02-14", commits: 16 },
{ date: "2024-02-15", commits: 17 },
{ date: "2024-02-16", commits: 10 },
{ date: "2024-02-17", commits: 1 },
{ date: "2024-02-18", commits: 1 },
{ date: "2024-02-19", commits: 11 },
{ date: "2024-02-20", commits: 6 },
{ date: "2024-02-21", commits: 15 },
{ date: "2024-02-22", commits: 17 },
{ date: "2024-02-23", commits: 5 },
{ date: "2024-02-24", commits: 1 },
{ date: "2024-02-25", commits: 2 },
{ date: "2024-02-26", commits: 14 },
{ date: "2024-02-27", commits: 12 },
{ date: "2024-02-28", commits: 18 },
{ date: "2024-02-29", commits: 9 },
{ date: "2024-03-01", commits: 16 },
{ date: "2024-03-02", commits: 1 },
{ date: "2024-03-03", commits: 1 },
{ date: "2024-03-04", commits: 15 },
{ date: "2024-03-05", commits: 5 },
{ date: "2024-03-06", commits: 9 },
{ date: "2024-03-07", commits: 12 },
{ date: "2024-03-08", commits: 16 },
{ date: "2024-03-09", commits: 2 },
{ date: "2024-03-10", commits: 1 },
{ date: "2024-03-11", commits: 11 },
{ date: "2024-03-12", commits: 14 },
{ date: "2024-03-13", commits: 18 },
{ date: "2024-03-14", commits: 14 },
{ date: "2024-03-15", commits: 9 },
{ date: "2024-03-16", commits: 1 },
{ date: "2024-03-17", commits: 2 },
{ date: "2024-03-18", commits: 9 },
{ date: "2024-03-19", commits: 18 },
{ date: "2024-03-20", commits: 16 },
{ date: "2024-03-21", commits: 8 },
{ date: "2024-03-22", commits: 18 },
{ date: "2024-03-23", commits: 1 },
{ date: "2024-03-24", commits: 3 },
{ date: "2024-03-25", commits: 30 },
{ date: "2024-03-26", commits: 19 },
{ date: "2024-03-27", commits: 12 },
{ date: "2024-03-28", commits: 11 },
{ date: "2024-03-29", commits: 9 },
{ date: "2024-03-30", commits: 1 },
{ date: "2024-03-31", commits: 2 },
{ date: "2024-04-01", commits: 7 },
{ date: "2024-04-02", commits: 18 },
{ date: "2024-04-03", commits: 19 },
{ date: "2024-04-04", commits: 15 },
{ date: "2024-04-05", commits: 10 },
{ date: "2024-04-06", commits: 1 },
{ date: "2024-04-07", commits: 1 },
{ date: "2024-04-08", commits: 18 },
{ date: "2024-04-09", commits: 9 },
{ date: "2024-04-10", commits: 14 },
{ date: "2024-04-11", commits: 16 },
{ date: "2024-04-12", commits: 14 },
{ date: "2024-04-13", commits: 2 },
{ date: "2024-04-14", commits: 2 },
{ date: "2024-04-15", commits: 15 },
{ date: "2024-04-16", commits: 7 },
{ date: "2024-04-17", commits: 15 },
{ date: "2024-04-18", commits: 17 },
{ date: "2024-04-19", commits: 8 },
{ date: "2024-04-20", commits: 1 },
{ date: "2024-04-21", commits: 2 },
{ date: "2024-04-22", commits: 11 },
{ date: "2024-04-23", commits: 19 },
{ date: "2024-04-24", commits: 18 },
{ date: "2024-04-25", commits: 6 },
{ date: "2024-04-26", commits: 16 },
{ date: "2024-04-27", commits: 1 },
{ date: "2024-04-28", commits: 2 },
{ date: "2024-04-29", commits: 13 },
{ date: "2024-04-30", commits: 6 },
{ date: "2024-05-01", commits: 16 },
{ date: "2024-05-02", commits: 7 },
{ date: "2024-05-03", commits: 19 },
{ date: "2024-05-04", commits: 1 },
{ date: "2024-05-05", commits: 1 },
{ date: "2024-05-06", commits: 12 },
{ date: "2024-05-07", commits: 17 },
{ date: "2024-05-08", commits: 11 },
{ date: "2024-05-09", commits: 10 },
{ date: "2024-05-10", commits: 19 },
{ date: "2024-05-11", commits: 2 },
{ date: "2024-05-12", commits: 1 },
{ date: "2024-05-13", commits: 7 },
{ date: "2024-05-14", commits: 17 },
{ date: "2024-05-15", commits: 7 },
{ date: "2024-05-16", commits: 18 },
{ date: "2024-05-17", commits: 10 },
{ date: "2024-05-18", commits: 1 },
{ date: "2024-05-19", commits: 1 },
{ date: "2024-05-20", commits: 8 },
{ date: "2024-05-21", commits: 18 },
{ date: "2024-05-22", commits: 12 },
{ date: "2024-05-23", commits: 19 },
{ date: "2024-05-24", commits: 17 },
{ date: "2024-05-25", commits: 2 },
{ date: "2024-05-26", commits: 2 },
{ date: "2024-05-27", commits: 11 },
{ date: "2024-05-28", commits: 13 },
{ date: "2024-05-29", commits: 14 },
{ date: "2024-05-30", commits: 14 },
{ date: "2024-05-31", commits: 12 },
{ date: "2024-06-01", commits: 1 },
{ date: "2024-06-02", commits: 1 },
{ date: "2024-06-03", commits: 15 },
{ date: "2024-06-04", commits: 15 },
{ date: "2024-06-05", commits: 17 },
{ date: "2024-06-06", commits: 8 },
{ date: "2024-06-07", commits: 17 },
{ date: "2024-06-08", commits: 2 },
{ date: "2024-06-09", commits: 1 },
{ date: "2024-06-10", commits: 9 },
{ date: "2024-06-11", commits: 11 },
{ date: "2024-06-12", commits: 16 },
{ date: "2024-06-13", commits: 12 },
{ date: "2024-06-14", commits: 11 },
{ date: "2024-06-15", commits: 1 },
{ date: "2024-06-16", commits: 2 },
{ date: "2024-06-17", commits: 13 },
{ date: "2024-06-18", commits: 16 },
{ date: "2024-06-19", commits: 12 },
{ date: "2024-06-20", commits: 18 },
{ date: "2024-06-21", commits: 9 },
{ date: "2024-06-22", commits: 1 },
{ date: "2024-06-23", commits: 2 },
{ date: "2024-06-24", commits: 12 },
{ date: "2024-06-25", commits: 12 },
{ date: "2024-06-26", commits: 14 },
{ date: "2024-06-27", commits: 14 },
{ date: "2024-06-28", commits: 24 },
{ date: "2024-06-29", commits: 2 },
{ date: "2024-06-30", commits: 2 },
{ date: "2024-07-01", commits: 17 },
{ date: "2024-07-02", commits: 9 },
{ date: "2024-07-03", commits: 18 },
{ date: "2024-07-04", commits: 16 },
{ date: "2024-07-05", commits: 7 },
{ date: "2024-07-06", commits: 1 },
{ date: "2024-07-07", commits: 1 },
{ date: "2024-07-08", commits: 21 },
{ date: "2024-07-09", commits: 15 },
{ date: "2024-07-10", commits: 7 },
{ date: "2024-07-11", commits: 9 },
{ date: "2024-07-12", commits: 21 },
{ date: "2024-07-13", commits: 1 },
{ date: "2024-07-14", commits: 2 },
{ date: "2024-07-15", commits: 20 },
{ date: "2024-07-16", commits: 20 },
{ date: "2024-07-17", commits: 16 },
{ date: "2024-07-18", commits: 22 },
{ date: "2024-07-19", commits: 18 },
{ date: "2024-07-20", commits: 2 },
{ date: "2024-07-21", commits: 2 },
{ date: "2024-07-22", commits: 7 },
{ date: "2024-07-23", commits: 20 },
{ date: "2024-07-24", commits: 22 },
{ date: "2024-07-25", commits: 14 },
{ date: "2024-07-26", commits: 18 },
{ date: "2024-07-27", commits: 1 },
{ date: "2024-07-28", commits: 2 },
{ date: "2024-07-29", commits: 9 },
{ date: "2024-07-30", commits: 17 },
{ date: "2024-07-31", commits: 18 },
{ date: "2024-08-01", commits: 17 },
{ date: "2024-08-02", commits: 19 },
{ date: "2024-08-03", commits: 1 },
{ date: "2024-08-04", commits: 1 },
{ date: "2024-08-05", commits: 2 },
{ date: "2024-08-06", commits: 1 },
{ date: "2024-08-07", commits: 1 },
{ date: "2024-08-08", commits: 2 },
{ date: "2024-08-09", commits: 2 },
{ date: "2024-08-12", commits: 2 },
{ date: "2024-08-13", commits: 1 },
{ date: "2024-08-14", commits: 3 },
{ date: "2024-08-15", commits: 2 },
{ date: "2024-08-16", commits: 2 },
{ date: "2024-08-19", commits: 18 },
{ date: "2024-08-20", commits: 15 },
{ date: "2024-08-21", commits: 19 },
{ date: "2024-08-22", commits: 18 },
{ date: "2024-08-23", commits: 7 },
{ date: "2024-08-24", commits: 2 },
{ date: "2024-08-25", commits: 1 },
{ date: "2024-08-26", commits: 12 },
{ date: "2024-08-27", commits: 14 },
{ date: "2024-08-28", commits: 12 },
{ date: "2024-08-29", commits: 11 },
{ date: "2024-08-30", commits: 11 },
{ date: "2024-08-31", commits: 1 },
{ date: "2024-09-01", commits: 1 },
{ date: "2024-09-02", commits: 20 },
{ date: "2024-09-03", commits: 22 },
{ date: "2024-09-04", commits: 11 },
{ date: "2024-09-05", commits: 19 },
{ date: "2024-09-06", commits: 15 },
{ date: "2024-09-07", commits: 2 },
{ date: "2024-09-08", commits: 1 },
{ date: "2024-09-09", commits: 8 },
{ date: "2024-09-10", commits: 22 },
{ date: "2024-09-11", commits: 23 },
{ date: "2024-09-12", commits: 9 },
{ date: "2024-09-13", commits: 19 },
{ date: "2024-09-14", commits: 1 },
{ date: "2024-09-15", commits: 2 },
{ date: "2024-09-16", commits: 11 },
{ date: "2024-09-17", commits: 13 },
{ date: "2024-09-18", commits: 11 },
{ date: "2024-09-19", commits: 12 },
{ date: "2024-09-20", commits: 18 },
{ date: "2024-09-21", commits: 2 },
{ date: "2024-09-22", commits: 1 },
{ date: "2024-09-23", commits: 15 },
{ date: "2024-09-24", commits: 37 },
{ date: "2024-09-25", commits: 38 },
{ date: "2024-09-26", commits: 30 },
{ date: "2024-09-27", commits: 39 },
{ date: "2024-09-28", commits: 2 },
{ date: "2024-09-29", commits: 2 },
{ date: "2024-09-30", commits: 30 },
{ date: "2024-10-01", commits: 22 },
{ date: "2024-10-02", commits: 19 },
{ date: "2024-10-03", commits: 24 },
{ date: "2024-10-04", commits: 19 },
{ date: "2024-10-05", commits: 1 },
{ date: "2024-10-06", commits: 1 },
{ date: "2024-10-07", commits: 23 },
{ date: "2024-10-08", commits: 9 },
{ date: "2024-10-09", commits: 21 },
{ date: "2024-10-10", commits: 14 },
{ date: "2024-10-11", commits: 12 },
{ date: "2024-10-12", commits: 1 },
{ date: "2024-10-13", commits: 1 },
{ date: "2024-10-14", commits: 8 },
{ date: "2024-10-15", commits: 8 },
{ date: "2024-10-16", commits: 21 },
{ date: "2024-10-17", commits: 13 },
{ date: "2024-10-18", commits: 23 },
{ date: "2024-10-19", commits: 1 },
{ date: "2024-10-20", commits: 1 },
{ date: "2024-10-21", commits: 15 },
{ date: "2024-10-22", commits: 17 },
{ date: "2024-10-23", commits: 19 },
{ date: "2024-10-24", commits: 24 },
{ date: "2024-10-25", commits: 19 },
{ date: "2024-10-26", commits: 1 },
{ date: "2024-10-27", commits: 2 },
{ date: "2024-10-28", commits: 16 },
{ date: "2024-10-29", commits: 22 },
{ date: "2024-10-30", commits: 13 },
{ date: "2024-10-31", commits: 23 },
{ date: "2024-11-01", commits: 16 },
{ date: "2024-11-02", commits: 1 },
{ date: "2024-11-03", commits: 2 },
{ date: "2024-11-04", commits: 20 },
{ date: "2024-11-05", commits: 17 },
{ date: "2024-11-06", commits: 13 },
{ date: "2024-11-07", commits: 14 },
{ date: "2024-11-08", commits: 19 },
{ date: "2024-11-09", commits: 1 },
{ date: "2024-11-10", commits: 1 },
{ date: "2024-11-11", commits: 25 },
{ date: "2024-11-12", commits: 8 },
{ date: "2024-11-13", commits: 24 },
{ date: "2024-11-14", commits: 10 },
{ date: "2024-11-15", commits: 15 },
{ date: "2024-11-16", commits: 2 },
{ date: "2024-11-17", commits: 2 },
{ date: "2024-11-18", commits: 11 },
{ date: "2024-11-19", commits: 7 },
{ date: "2024-11-20", commits: 15 },
{ date: "2024-11-21", commits: 22 },
{ date: "2024-11-22", commits: 8 },
{ date: "2024-11-23", commits: 1 },
{ date: "2024-11-24", commits: 2 },
{ date: "2024-11-25", commits: 19 },
{ date: "2024-11-26", commits: 8 },
{ date: "2024-11-27", commits: 17 },
{ date: "2024-11-28", commits: 9 },
{ date: "2024-11-29", commits: 25 },
{ date: "2024-11-30", commits: 1 },
{ date: "2024-12-01", commits: 1 },
{ date: "2024-12-02", commits: 13 },
{ date: "2024-12-03", commits: 12 },
{ date: "2024-12-04", commits: 10 },
{ date: "2024-12-05", commits: 14 },
{ date: "2024-12-06", commits: 8 },
{ date: "2024-12-07", commits: 1 },
{ date: "2024-12-08", commits: 1 },
{ date: "2024-12-09", commits: 16 },
{ date: "2024-12-10", commits: 8 },
{ date: "2024-12-11", commits: 19 },
{ date: "2024-12-12", commits: 23 },
{ date: "2024-12-13", commits: 22 },
{ date: "2024-12-14", commits: 2 },
{ date: "2024-12-15", commits: 2 },
{ date: "2024-12-16", commits: 9 },
{ date: "2024-12-17", commits: 24 },
{ date: "2024-12-18", commits: 21 },
{ date: "2024-12-19", commits: 18 },
{ date: "2024-12-20", commits: 21 },
{ date: "2024-12-21", commits: 1 },
{ date: "2024-12-22", commits: 1 },
{ date: "2024-12-23", commits: 24 },
{ date: "2024-12-24", commits: 37 },
{ date: "2024-12-25", commits: 17 },
{ date: "2024-12-26", commits: 34 },
{ date: "2024-12-27", commits: 24 },
{ date: "2024-12-28", commits: 2 },
{ date: "2024-12-29", commits: 3 },
{ date: "2024-12-30", commits: 24 },
{ date: "2024-12-31", commits: 33 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "calendar-heatmap",
"data": [
{ "date": "2024-01-01", "commits": 16 },
{ "date": "2024-01-02", "commits": 9 },
{ "date": "2024-01-03", "commits": 8 },
{ "date": "2024-01-04", "commits": 6 },
{ "date": "2024-01-05", "commits": 8 },
{ "date": "2024-01-06", "commits": 1 },
{ "date": "2024-01-07", "commits": 1 },
{ "date": "2024-01-08", "commits": 17 },
{ "date": "2024-01-09", "commits": 12 },
{ "date": "2024-01-10", "commits": 13 },
{ "date": "2024-01-11", "commits": 14 },
{ "date": "2024-01-12", "commits": 9 },
{ "date": "2024-01-13", "commits": 2 },
{ "date": "2024-01-14", "commits": 2 },
{ "date": "2024-01-15", "commits": 11 },
{ "date": "2024-01-16", "commits": 14 },
{ "date": "2024-01-17", "commits": 10 },
{ "date": "2024-01-18", "commits": 11 },
{ "date": "2024-01-19", "commits": 14 },
{ "date": "2024-01-20", "commits": 2 },
{ "date": "2024-01-21", "commits": 2 },
{ "date": "2024-01-22", "commits": 11 },
{ "date": "2024-01-23", "commits": 9 },
{ "date": "2024-01-24", "commits": 14 },
{ "date": "2024-01-25", "commits": 8 },
{ "date": "2024-01-26", "commits": 14 },
{ "date": "2024-01-27", "commits": 1 },
{ "date": "2024-01-28", "commits": 1 },
{ "date": "2024-01-29", "commits": 10 },
{ "date": "2024-01-30", "commits": 5 },
{ "date": "2024-01-31", "commits": 11 },
{ "date": "2024-02-01", "commits": 12 },
{ "date": "2024-02-02", "commits": 15 },
{ "date": "2024-02-03", "commits": 1 },
{ "date": "2024-02-04", "commits": 2 },
{ "date": "2024-02-05", "commits": 10 },
{ "date": "2024-02-06", "commits": 9 },
{ "date": "2024-02-07", "commits": 12 },
{ "date": "2024-02-08", "commits": 6 },
{ "date": "2024-02-09", "commits": 13 },
{ "date": "2024-02-10", "commits": 1 },
{ "date": "2024-02-11", "commits": 1 },
{ "date": "2024-02-12", "commits": 7 },
{ "date": "2024-02-13", "commits": 17 },
{ "date": "2024-02-14", "commits": 16 },
{ "date": "2024-02-15", "commits": 17 },
{ "date": "2024-02-16", "commits": 10 },
{ "date": "2024-02-17", "commits": 1 },
{ "date": "2024-02-18", "commits": 1 },
{ "date": "2024-02-19", "commits": 11 },
{ "date": "2024-02-20", "commits": 6 },
{ "date": "2024-02-21", "commits": 15 },
{ "date": "2024-02-22", "commits": 17 },
{ "date": "2024-02-23", "commits": 5 },
{ "date": "2024-02-24", "commits": 1 },
{ "date": "2024-02-25", "commits": 2 },
{ "date": "2024-02-26", "commits": 14 },
{ "date": "2024-02-27", "commits": 12 },
{ "date": "2024-02-28", "commits": 18 },
{ "date": "2024-02-29", "commits": 9 },
{ "date": "2024-03-01", "commits": 16 },
{ "date": "2024-03-02", "commits": 1 },
{ "date": "2024-03-03", "commits": 1 },
{ "date": "2024-03-04", "commits": 15 },
{ "date": "2024-03-05", "commits": 5 },
{ "date": "2024-03-06", "commits": 9 },
{ "date": "2024-03-07", "commits": 12 },
{ "date": "2024-03-08", "commits": 16 },
{ "date": "2024-03-09", "commits": 2 },
{ "date": "2024-03-10", "commits": 1 },
{ "date": "2024-03-11", "commits": 11 },
{ "date": "2024-03-12", "commits": 14 },
{ "date": "2024-03-13", "commits": 18 },
{ "date": "2024-03-14", "commits": 14 },
{ "date": "2024-03-15", "commits": 9 },
{ "date": "2024-03-16", "commits": 1 },
{ "date": "2024-03-17", "commits": 2 },
{ "date": "2024-03-18", "commits": 9 },
{ "date": "2024-03-19", "commits": 18 },
{ "date": "2024-03-20", "commits": 16 },
{ "date": "2024-03-21", "commits": 8 },
{ "date": "2024-03-22", "commits": 18 },
{ "date": "2024-03-23", "commits": 1 },
{ "date": "2024-03-24", "commits": 3 },
{ "date": "2024-03-25", "commits": 30 },
{ "date": "2024-03-26", "commits": 19 },
{ "date": "2024-03-27", "commits": 12 },
{ "date": "2024-03-28", "commits": 11 },
{ "date": "2024-03-29", "commits": 9 },
{ "date": "2024-03-30", "commits": 1 },
{ "date": "2024-03-31", "commits": 2 },
{ "date": "2024-04-01", "commits": 7 },
{ "date": "2024-04-02", "commits": 18 },
{ "date": "2024-04-03", "commits": 19 },
{ "date": "2024-04-04", "commits": 15 },
{ "date": "2024-04-05", "commits": 10 },
{ "date": "2024-04-06", "commits": 1 },
{ "date": "2024-04-07", "commits": 1 },
{ "date": "2024-04-08", "commits": 18 },
{ "date": "2024-04-09", "commits": 9 },
{ "date": "2024-04-10", "commits": 14 },
{ "date": "2024-04-11", "commits": 16 },
{ "date": "2024-04-12", "commits": 14 },
{ "date": "2024-04-13", "commits": 2 },
{ "date": "2024-04-14", "commits": 2 },
{ "date": "2024-04-15", "commits": 15 },
{ "date": "2024-04-16", "commits": 7 },
{ "date": "2024-04-17", "commits": 15 },
{ "date": "2024-04-18", "commits": 17 },
{ "date": "2024-04-19", "commits": 8 },
{ "date": "2024-04-20", "commits": 1 },
{ "date": "2024-04-21", "commits": 2 },
{ "date": "2024-04-22", "commits": 11 },
{ "date": "2024-04-23", "commits": 19 },
{ "date": "2024-04-24", "commits": 18 },
{ "date": "2024-04-25", "commits": 6 },
{ "date": "2024-04-26", "commits": 16 },
{ "date": "2024-04-27", "commits": 1 },
{ "date": "2024-04-28", "commits": 2 },
{ "date": "2024-04-29", "commits": 13 },
{ "date": "2024-04-30", "commits": 6 },
{ "date": "2024-05-01", "commits": 16 },
{ "date": "2024-05-02", "commits": 7 },
{ "date": "2024-05-03", "commits": 19 },
{ "date": "2024-05-04", "commits": 1 },
{ "date": "2024-05-05", "commits": 1 },
{ "date": "2024-05-06", "commits": 12 },
{ "date": "2024-05-07", "commits": 17 },
{ "date": "2024-05-08", "commits": 11 },
{ "date": "2024-05-09", "commits": 10 },
{ "date": "2024-05-10", "commits": 19 },
{ "date": "2024-05-11", "commits": 2 },
{ "date": "2024-05-12", "commits": 1 },
{ "date": "2024-05-13", "commits": 7 },
{ "date": "2024-05-14", "commits": 17 },
{ "date": "2024-05-15", "commits": 7 },
{ "date": "2024-05-16", "commits": 18 },
{ "date": "2024-05-17", "commits": 10 },
{ "date": "2024-05-18", "commits": 1 },
{ "date": "2024-05-19", "commits": 1 },
{ "date": "2024-05-20", "commits": 8 },
{ "date": "2024-05-21", "commits": 18 },
{ "date": "2024-05-22", "commits": 12 },
{ "date": "2024-05-23", "commits": 19 },
{ "date": "2024-05-24", "commits": 17 },
{ "date": "2024-05-25", "commits": 2 },
{ "date": "2024-05-26", "commits": 2 },
{ "date": "2024-05-27", "commits": 11 },
{ "date": "2024-05-28", "commits": 13 },
{ "date": "2024-05-29", "commits": 14 },
{ "date": "2024-05-30", "commits": 14 },
{ "date": "2024-05-31", "commits": 12 },
{ "date": "2024-06-01", "commits": 1 },
{ "date": "2024-06-02", "commits": 1 },
{ "date": "2024-06-03", "commits": 15 },
{ "date": "2024-06-04", "commits": 15 },
{ "date": "2024-06-05", "commits": 17 },
{ "date": "2024-06-06", "commits": 8 },
{ "date": "2024-06-07", "commits": 17 },
{ "date": "2024-06-08", "commits": 2 },
{ "date": "2024-06-09", "commits": 1 },
{ "date": "2024-06-10", "commits": 9 },
{ "date": "2024-06-11", "commits": 11 },
{ "date": "2024-06-12", "commits": 16 },
{ "date": "2024-06-13", "commits": 12 },
{ "date": "2024-06-14", "commits": 11 },
{ "date": "2024-06-15", "commits": 1 },
{ "date": "2024-06-16", "commits": 2 },
{ "date": "2024-06-17", "commits": 13 },
{ "date": "2024-06-18", "commits": 16 },
{ "date": "2024-06-19", "commits": 12 },
{ "date": "2024-06-20", "commits": 18 },
{ "date": "2024-06-21", "commits": 9 },
{ "date": "2024-06-22", "commits": 1 },
{ "date": "2024-06-23", "commits": 2 },
{ "date": "2024-06-24", "commits": 12 },
{ "date": "2024-06-25", "commits": 12 },
{ "date": "2024-06-26", "commits": 14 },
{ "date": "2024-06-27", "commits": 14 },
{ "date": "2024-06-28", "commits": 24 },
{ "date": "2024-06-29", "commits": 2 },
{ "date": "2024-06-30", "commits": 2 },
{ "date": "2024-07-01", "commits": 17 },
{ "date": "2024-07-02", "commits": 9 },
{ "date": "2024-07-03", "commits": 18 },
{ "date": "2024-07-04", "commits": 16 },
{ "date": "2024-07-05", "commits": 7 },
{ "date": "2024-07-06", "commits": 1 },
{ "date": "2024-07-07", "commits": 1 },
{ "date": "2024-07-08", "commits": 21 },
{ "date": "2024-07-09", "commits": 15 },
{ "date": "2024-07-10", "commits": 7 },
{ "date": "2024-07-11", "commits": 9 },
{ "date": "2024-07-12", "commits": 21 },
{ "date": "2024-07-13", "commits": 1 },
{ "date": "2024-07-14", "commits": 2 },
{ "date": "2024-07-15", "commits": 20 },
{ "date": "2024-07-16", "commits": 20 },
{ "date": "2024-07-17", "commits": 16 },
{ "date": "2024-07-18", "commits": 22 },
{ "date": "2024-07-19", "commits": 18 },
{ "date": "2024-07-20", "commits": 2 },
{ "date": "2024-07-21", "commits": 2 },
{ "date": "2024-07-22", "commits": 7 },
{ "date": "2024-07-23", "commits": 20 },
{ "date": "2024-07-24", "commits": 22 },
{ "date": "2024-07-25", "commits": 14 },
{ "date": "2024-07-26", "commits": 18 },
{ "date": "2024-07-27", "commits": 1 },
{ "date": "2024-07-28", "commits": 2 },
{ "date": "2024-07-29", "commits": 9 },
{ "date": "2024-07-30", "commits": 17 },
{ "date": "2024-07-31", "commits": 18 },
{ "date": "2024-08-01", "commits": 17 },
{ "date": "2024-08-02", "commits": 19 },
{ "date": "2024-08-03", "commits": 1 },
{ "date": "2024-08-04", "commits": 1 },
{ "date": "2024-08-05", "commits": 2 },
{ "date": "2024-08-06", "commits": 1 },
{ "date": "2024-08-07", "commits": 1 },
{ "date": "2024-08-08", "commits": 2 },
{ "date": "2024-08-09", "commits": 2 },
{ "date": "2024-08-12", "commits": 2 },
{ "date": "2024-08-13", "commits": 1 },
{ "date": "2024-08-14", "commits": 3 },
{ "date": "2024-08-15", "commits": 2 },
{ "date": "2024-08-16", "commits": 2 },
{ "date": "2024-08-19", "commits": 18 },
{ "date": "2024-08-20", "commits": 15 },
{ "date": "2024-08-21", "commits": 19 },
{ "date": "2024-08-22", "commits": 18 },
{ "date": "2024-08-23", "commits": 7 },
{ "date": "2024-08-24", "commits": 2 },
{ "date": "2024-08-25", "commits": 1 },
{ "date": "2024-08-26", "commits": 12 },
{ "date": "2024-08-27", "commits": 14 },
{ "date": "2024-08-28", "commits": 12 },
{ "date": "2024-08-29", "commits": 11 },
{ "date": "2024-08-30", "commits": 11 },
{ "date": "2024-08-31", "commits": 1 },
{ "date": "2024-09-01", "commits": 1 },
{ "date": "2024-09-02", "commits": 20 },
{ "date": "2024-09-03", "commits": 22 },
{ "date": "2024-09-04", "commits": 11 },
{ "date": "2024-09-05", "commits": 19 },
{ "date": "2024-09-06", "commits": 15 },
{ "date": "2024-09-07", "commits": 2 },
{ "date": "2024-09-08", "commits": 1 },
{ "date": "2024-09-09", "commits": 8 },
{ "date": "2024-09-10", "commits": 22 },
{ "date": "2024-09-11", "commits": 23 },
{ "date": "2024-09-12", "commits": 9 },
{ "date": "2024-09-13", "commits": 19 },
{ "date": "2024-09-14", "commits": 1 },
{ "date": "2024-09-15", "commits": 2 },
{ "date": "2024-09-16", "commits": 11 },
{ "date": "2024-09-17", "commits": 13 },
{ "date": "2024-09-18", "commits": 11 },
{ "date": "2024-09-19", "commits": 12 },
{ "date": "2024-09-20", "commits": 18 },
{ "date": "2024-09-21", "commits": 2 },
{ "date": "2024-09-22", "commits": 1 },
{ "date": "2024-09-23", "commits": 15 },
{ "date": "2024-09-24", "commits": 37 },
{ "date": "2024-09-25", "commits": 38 },
{ "date": "2024-09-26", "commits": 30 },
{ "date": "2024-09-27", "commits": 39 },
{ "date": "2024-09-28", "commits": 2 },
{ "date": "2024-09-29", "commits": 2 },
{ "date": "2024-09-30", "commits": 30 },
{ "date": "2024-10-01", "commits": 22 },
{ "date": "2024-10-02", "commits": 19 },
{ "date": "2024-10-03", "commits": 24 },
{ "date": "2024-10-04", "commits": 19 },
{ "date": "2024-10-05", "commits": 1 },
{ "date": "2024-10-06", "commits": 1 },
{ "date": "2024-10-07", "commits": 23 },
{ "date": "2024-10-08", "commits": 9 },
{ "date": "2024-10-09", "commits": 21 },
{ "date": "2024-10-10", "commits": 14 },
{ "date": "2024-10-11", "commits": 12 },
{ "date": "2024-10-12", "commits": 1 },
{ "date": "2024-10-13", "commits": 1 },
{ "date": "2024-10-14", "commits": 8 },
{ "date": "2024-10-15", "commits": 8 },
{ "date": "2024-10-16", "commits": 21 },
{ "date": "2024-10-17", "commits": 13 },
{ "date": "2024-10-18", "commits": 23 },
{ "date": "2024-10-19", "commits": 1 },
{ "date": "2024-10-20", "commits": 1 },
{ "date": "2024-10-21", "commits": 15 },
{ "date": "2024-10-22", "commits": 17 },
{ "date": "2024-10-23", "commits": 19 },
{ "date": "2024-10-24", "commits": 24 },
{ "date": "2024-10-25", "commits": 19 },
{ "date": "2024-10-26", "commits": 1 },
{ "date": "2024-10-27", "commits": 2 },
{ "date": "2024-10-28", "commits": 16 },
{ "date": "2024-10-29", "commits": 22 },
{ "date": "2024-10-30", "commits": 13 },
{ "date": "2024-10-31", "commits": 23 },
{ "date": "2024-11-01", "commits": 16 },
{ "date": "2024-11-02", "commits": 1 },
{ "date": "2024-11-03", "commits": 2 },
{ "date": "2024-11-04", "commits": 20 },
{ "date": "2024-11-05", "commits": 17 },
{ "date": "2024-11-06", "commits": 13 },
{ "date": "2024-11-07", "commits": 14 },
{ "date": "2024-11-08", "commits": 19 },
{ "date": "2024-11-09", "commits": 1 },
{ "date": "2024-11-10", "commits": 1 },
{ "date": "2024-11-11", "commits": 25 },
{ "date": "2024-11-12", "commits": 8 },
{ "date": "2024-11-13", "commits": 24 },
{ "date": "2024-11-14", "commits": 10 },
{ "date": "2024-11-15", "commits": 15 },
{ "date": "2024-11-16", "commits": 2 },
{ "date": "2024-11-17", "commits": 2 },
{ "date": "2024-11-18", "commits": 11 },
{ "date": "2024-11-19", "commits": 7 },
{ "date": "2024-11-20", "commits": 15 },
{ "date": "2024-11-21", "commits": 22 },
{ "date": "2024-11-22", "commits": 8 },
{ "date": "2024-11-23", "commits": 1 },
{ "date": "2024-11-24", "commits": 2 },
{ "date": "2024-11-25", "commits": 19 },
{ "date": "2024-11-26", "commits": 8 },
{ "date": "2024-11-27", "commits": 17 },
{ "date": "2024-11-28", "commits": 9 },
{ "date": "2024-11-29", "commits": 25 },
{ "date": "2024-11-30", "commits": 1 },
{ "date": "2024-12-01", "commits": 1 },
{ "date": "2024-12-02", "commits": 13 },
{ "date": "2024-12-03", "commits": 12 },
{ "date": "2024-12-04", "commits": 10 },
{ "date": "2024-12-05", "commits": 14 },
{ "date": "2024-12-06", "commits": 8 },
{ "date": "2024-12-07", "commits": 1 },
{ "date": "2024-12-08", "commits": 1 },
{ "date": "2024-12-09", "commits": 16 },
{ "date": "2024-12-10", "commits": 8 },
{ "date": "2024-12-11", "commits": 19 },
{ "date": "2024-12-12", "commits": 23 },
{ "date": "2024-12-13", "commits": 22 },
{ "date": "2024-12-14", "commits": 2 },
{ "date": "2024-12-15", "commits": 2 },
{ "date": "2024-12-16", "commits": 9 },
{ "date": "2024-12-17", "commits": 24 },
{ "date": "2024-12-18", "commits": 21 },
{ "date": "2024-12-19", "commits": 18 },
{ "date": "2024-12-20", "commits": 21 },
{ "date": "2024-12-21", "commits": 1 },
{ "date": "2024-12-22", "commits": 1 },
{ "date": "2024-12-23", "commits": 24 },
{ "date": "2024-12-24", "commits": 37 },
{ "date": "2024-12-25", "commits": 17 },
{ "date": "2024-12-26", "commits": 34 },
{ "date": "2024-12-27", "commits": 24 },
{ "date": "2024-12-28", "commits": 2 },
{ "date": "2024-12-29", "commits": 3 },
{ "date": "2024-12-30", "commits": 24 },
{ "date": "2024-12-31", "commits": 33 }
],
"date": "date",
"value": "commits",
"title": "Commits per day to the main repository, 2024",
"subtitle": "Number of commits each day",
"note": "Illustrative data. Pale cells are days with no commits."
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `date` (required) | `string` | | Date field: ISO strings, Dates or epoch milliseconds. One cell per calendar day. |
| `value` (required) | `string` | | Numeric field that colours each day. Rows sharing a day are summed. |
| `orientation` | `"auto" \| "horizontal" \| "vertical"` | `"auto"` | Weeks left to right, or top to bottom. `auto` goes vertical under 480px. |
| `weekStart` | `"monday" \| "sunday"` | `"monday"` | First day of the week. |
| `domain` | `[number, number]` | | Fix the ends of the colour scale. Default: 0 to the maximum. |
| `height` | `number \| string` | | Common prop, but note the default: when omitted the height follows the cell size, which follows the width, so cells stay square. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: no molecule or template yet
### More examples
#### Three years, with a gap in the record
```tsx
import { CalendarHeatmap } from "insightcharts";
const data = [
{ day: "2022-01-01", steps: 3996 },
{ day: "2022-01-02", steps: 1946 },
{ day: "2022-01-03", steps: 3651 },
{ day: "2022-01-04", steps: 4872 },
{ day: "2022-01-05", steps: 2206 },
{ day: "2022-01-06", steps: 1736 },
{ day: "2022-01-07", steps: 4042 },
{ day: "2022-01-08", steps: 5780 },
// ... 1058 more rows
];
```
#### Sparse events, Sunday start
```tsx
import { CalendarHeatmap } from "insightcharts";
const data = [
{ date: "2025-01-01", incidents: 3 },
{ date: "2025-01-06", incidents: 3 },
{ date: "2025-01-08", incidents: 2 },
{ date: "2025-01-11", incidents: 1 },
{ date: "2025-01-15", incidents: 2 },
{ date: "2025-01-16", incidents: 3 },
{ date: "2025-01-21", incidents: 2 },
{ date: "2025-01-22", incidents: 3 },
// ... 106 more rows
];
```
#### Forced vertical with a fixed height
```tsx
import { CalendarHeatmap } from "insightcharts";
const data = [
{ date: "2024-01-01", commits: 16 },
{ date: "2024-01-02", commits: 9 },
{ date: "2024-01-03", commits: 8 },
{ date: "2024-01-04", commits: 6 },
{ date: "2024-01-05", commits: 8 },
{ date: "2024-01-06", commits: 1 },
{ date: "2024-01-07", commits: 1 },
{ date: "2024-01-08", commits: 17 },
// ... 354 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/calendar-heatmap.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/calendar-heatmap.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Callout (`callout`)
> A boxed statement of fact with a short bold lead and a sentence of explanation. The tone is marked by icon and tint.
- Component: `Callout` (import from `insightcharts`)
- Spec type: `"callout"`
- Level: atom. Category: text.
- An HTML component: it takes only the props listed below, plus `theme`, `mode`, `className` and `style` where its source declares them. It does not take the common chart props.
### When to use
Beside or under a chart, to state its main fact in words, with the number. Also for a caveat the reader must not miss (`negative`).
### Avoid
Long narration (use `prose`), a featured number (use `big-number` or `stat-tile`), or more than one callout per chart.
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/callout
```
The file lands at `components/insightcharts/atoms/callout.tsx`. `@insightcharts/callout-demo` adds a ready-made example next to it.
### Usage
```tsx
import { Callout } from "insightcharts";
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "callout",
"tone": "accent",
"label": "Summary",
"lead": "Solar supplied more than half of demand on 4 in 10 summer afternoons in 2024",
"text": "Wholesale prices were below zero on those afternoons."
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `lead` | `string` | | The fact in a few bold words. State it with its number. |
| `text` | `string` | | One sentence of explanation or evidence. |
| `children` | `ReactNode` | | JSX alternative to `text`. |
| `tone` | `"neutral" \| "positive" \| "negative" \| "accent"` | `"neutral"` | Sets the icon shape and the tint together. |
| `label` | `string` | | Small label above the lead, e.g. "Summary". |
| `variant` | `"soft" \| "outline" \| "plain"` | `"soft"` | Tinted box, bordered box, or no box. |
### Composition
- Composes: nothing (it is a primitive)
- Used by: [chart-callout](https://insightcharts.dev/llms/chart-callout.md), [comparison-brief](https://insightcharts.dev/llms/comparison-brief.md), [data-explorer](https://insightcharts.dev/llms/data-explorer.md), [explainer](https://insightcharts.dev/llms/explainer.md)
### More examples
#### Positive
```tsx
import { Callout } from "insightcharts";
```
#### Negative caveat, outline
```tsx
import { Callout } from "insightcharts";
```
#### Neutral, plain
```tsx
import { Callout } from "insightcharts";
```
### Links
- Registry item: https://insightcharts.dev/r/callout.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/callout.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Choropleth map (`choropleth-map`)
> Colours the regions of a map by a normalised value, from GeoJSON you supply.
- Component: `ChoroplethMap` (import from `insightcharts`)
- Spec type: `"choropleth-map"`
- Level: atom. Category: geo.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
A rate, share or per-capita figure that varies by place, where the geographic pattern (a north-south divide, a cluster) is what the reader needs to see. Use a diverging scale for change or for values either side of a meaningful midpoint.
### Avoid
Raw counts, which mostly map population (normalise first, or use bar-chart); ranking regions precisely (use bar-chart); a handful of regions with no spatial pattern (use bar-chart); regions so different in size that small ones vanish (use a tile-grid GeoJSON).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/choropleth-map
```
The file lands at `components/insightcharts/atoms/choropleth-map.tsx`. `@insightcharts/choropleth-map-demo` adds a ready-made example next to it.
### Usage
```tsx
import { ChoroplethMap } from "insightcharts";
const data = [
{ state: "Queensland", share: 45 },
{ state: "South Australia", share: 47 },
{ state: "Western Australia", share: 39 },
{ state: "New South Wales", share: 31 },
{ state: "Australian Capital Territory", share: 29 },
{ state: "Victoria", share: 26 },
{ state: "Northern Territory", share: 22 },
{ state: "Tasmania", share: 19 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "choropleth-map",
"geo": {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": { "name": "Western Australia", "code": "WA" },
"geometry": { "type": "Polygon", "coordinates": [[[0, 1], [4, 1], [4, 6], [0, 6], [0, 1]]] }
},
{
"type": "Feature",
"properties": { "name": "Northern Territory", "code": "NT" },
"geometry": {
"type": "Polygon",
"coordinates": [[[4, 3.5], [6.5, 3.5], [6.5, 6.5], [4, 6.5], [4, 3.5]]]
}
},
{
"type": "Feature",
"properties": { "name": "South Australia", "code": "SA" },
"geometry": { "type": "Polygon", "coordinates": [[[4, 1], [6.5, 1], [6.5, 3.5], [4, 3.5], [4, 1]]] }
},
{
"type": "Feature",
"properties": { "name": "Queensland", "code": "Qld" },
"geometry": {
"type": "Polygon",
"coordinates": [
[
[6.5, 3],
[10, 3],
[10, 5.2],
[8.6, 5.2],
[8.6, 7.2],
[7.6, 7.2],
[7.6, 6.2],
[6.5, 6.2],
[6.5, 3]
]
]
}
},
{
"type": "Feature",
"properties": { "name": "New South Wales", "code": "NSW" },
"geometry": {
"type": "Polygon",
"coordinates": [
[[6.5, 1.4], [10, 1.4], [10, 3], [6.5, 3], [6.5, 1.4]],
[[8.7, 1.8], [8.7, 2.4], [9.4, 2.4], [9.4, 1.8], [8.7, 1.8]]
]
}
},
{
"type": "Feature",
"properties": { "name": "Australian Capital Territory", "code": "ACT" },
"geometry": {
"type": "Polygon",
"coordinates": [[[8.7, 1.8], [9.4, 1.8], [9.4, 2.4], [8.7, 2.4], [8.7, 1.8]]]
}
},
{
"type": "Feature",
"properties": { "name": "Victoria", "code": "Vic" },
"geometry": {
"type": "Polygon",
"coordinates": [[[6.5, 0.3], [9.2, 0.3], [9.2, 1.4], [6.5, 1.4], [6.5, 0.3]]]
}
},
{
"type": "Feature",
"properties": { "name": "Tasmania", "code": "Tas" },
"geometry": {
"type": "Polygon",
"coordinates": [[[7.6, -1.1], [8.7, -1.1], [8.7, -0.3], [7.6, -0.3], [7.6, -1.1]]]
}
}
]
},
"data": [
{ "state": "Queensland", "share": 45 },
{ "state": "South Australia", "share": 47 },
{ "state": "Western Australia", "share": 39 },
{ "state": "New South Wales", "share": 31 },
{ "state": "Australian Capital Territory", "share": 29 },
{ "state": "Victoria", "share": 26 },
{ "state": "Northern Territory", "share": 22 },
{ "state": "Tasmania", "share": 19 }
],
"region": "state",
"value": "share",
"format": "{value}%",
"aspectScale": 1,
"labels": true,
"labelProperty": "code",
"height": 380,
"title": "47% of homes in South Australia had rooftop solar in 2023",
"subtitle": "Share of dwellings with a solar installation, by state and territory, 2023",
"source": "Australian PV Institute",
"note": "Approximate figures on a simplified block map"
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `geo` (required) | `GeoJSON FeatureCollection \| string` | | The map: a GeoJSON object, or a URL to fetch one from. Registered with ECharts automatically. |
| `region` (required) | `string` | | Row field holding the region name that joins to the GeoJSON. |
| `value` (required) | `string` | | Row field holding the value. Use a rate or share, not a raw count. |
| `nameProperty` | `string` | `"name"` | GeoJSON feature property holding each region's name. |
| `scale` | `"sequential" \| "diverging"` | `"sequential"` | Sequential for magnitude; diverging for values either side of a midpoint. |
| `midpoint` | `number` | `0` | Centre of the diverging scale. |
| `domain` | `[number, number]` | | Fix the colour scale instead of using the data's range. |
| `labels` | `boolean \| "value"` | `false` | Print region names on the map; `"value"` adds the value on a second line. |
| `labelProperty` | `string` | | GeoJSON feature property to print instead of the full name, e.g. a postal or ISO code. |
| `missingLabel` | `string` | `"No data"` | Legend text for regions without a value. |
| `aspectScale` | `number` | `0.75` | Horizontal stretch for longitude; 1 for tile maps and projected coordinates. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: [atlas-poster](https://insightcharts.dev/llms/atlas-poster.md)
### More examples
#### Diverging, with missing regions
```tsx
import { ChoroplethMap } from "insightcharts";
const data = [
{ state: "Queensland", net: 3.1 },
{ state: "Western Australia", net: 1.9 },
{ state: "South Australia", net: -0.2 },
{ state: "New South Wales", net: -3.4 },
{ state: "Victoria", net: -0.9 },
{ state: "Tasmania", net: -1.6 },
];
```
#### Tile grid with labels, loaded from a URL
```tsx
import { ChoroplethMap } from "insightcharts";
const data = [
{ region: "North East", pay: 31.2 },
{ region: "North West", pay: 33.1 },
{ region: "Yorkshire", pay: 32.4 },
{ region: "West Midlands", pay: 33 },
{ region: "East Midlands", pay: 32.1 },
{ region: "East", pay: 36.3 },
{ region: "South West", pay: 33.5 },
{ region: "South East", pay: 38.4 },
// ... 1 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/choropleth-map.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/choropleth-map.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Circle pack (`circle-pack`)
> Shows parts as tightly packed circles sized by value, optionally clustered into groups, with the value and name printed inside each circle.
- Component: `CirclePack` (import from `insightcharts`)
- Spec type: `"circle-pack"`
- Level: atom. Category: poster.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
A poster view of 5 to about 60 parts of very different sizes where the reader should see the largest ones and their rough proportions at once: budgets by programme, emissions by source, users by product. One level of grouping is supported.
### Avoid
Values that need precise comparison (use bar-chart, where bars share a baseline); parts that must visibly add up to a whole (use treemap or waffle-chart); more than about 80 parts (use treemap); negative values (use diverging-bar-chart).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/circle-pack
```
The file lands at `components/insightcharts/atoms/circle-pack.tsx`. `@insightcharts/circle-pack-demo` adds a ready-made example next to it.
### Usage
```tsx
import { CirclePack } from "insightcharts";
const data = [
{ crop: "Maize", group: "Cereals", mt: 1160 },
{ crop: "Wheat", group: "Cereals", mt: 780 },
{ crop: "Rice", group: "Cereals", mt: 790 },
{ crop: "Barley", group: "Cereals", mt: 150 },
{ crop: "Sorghum", group: "Cereals", mt: 60 },
{ crop: "Potatoes", group: "Roots", mt: 375 },
{ crop: "Cassava", group: "Roots", mt: 330 },
{ crop: "Sweet potatoes", group: "Roots", mt: 90 },
{ crop: "Yams", group: "Roots", mt: 85 },
{ crop: "Soybeans", group: "Oil crops", mt: 350 },
{ crop: "Oil palm fruit", group: "Oil crops", mt: 420 },
{ crop: "Rapeseed", group: "Oil crops", mt: 87 },
{ crop: "Sunflower seed", group: "Oil crops", mt: 54 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "circle-pack",
"data": [
{ "crop": "Maize", "group": "Cereals", "mt": 1160 },
{ "crop": "Wheat", "group": "Cereals", "mt": 780 },
{ "crop": "Rice", "group": "Cereals", "mt": 790 },
{ "crop": "Barley", "group": "Cereals", "mt": 150 },
{ "crop": "Sorghum", "group": "Cereals", "mt": 60 },
{ "crop": "Potatoes", "group": "Roots", "mt": 375 },
{ "crop": "Cassava", "group": "Roots", "mt": 330 },
{ "crop": "Sweet potatoes", "group": "Roots", "mt": 90 },
{ "crop": "Yams", "group": "Roots", "mt": 85 },
{ "crop": "Soybeans", "group": "Oil crops", "mt": 350 },
{ "crop": "Oil palm fruit", "group": "Oil crops", "mt": 420 },
{ "crop": "Rapeseed", "group": "Oil crops", "mt": 87 },
{ "crop": "Sunflower seed", "group": "Oil crops", "mt": 54 }
],
"name": "crop",
"value": "mt",
"group": "group",
"format": "{value} Mt",
"title": "The world harvested 1,160 million tonnes of maize in 2022",
"subtitle": "Harvest of 13 major crops by group, million tonnes, 2022",
"source": "FAOSTAT",
"note": "Rounded, approximate figures for illustration"
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `name` (required) | `string` | | Name field: one circle per row. |
| `value` (required) | `string` | | Value field: circle area. Non-positive values are dropped. |
| `group` | `string` | | Parent field. Circles sharing a group sit together and share a colour. |
| `ring` | `"none" \| "group" \| "all"` | | Thin dotted enclosing ring: one per group with its name and total, or one around the whole pack. Default "group" when `group` is set, else "none". |
| `colorBy` | `"group" \| "value" \| "single"` | | Colour by group, shade by value along the sequential ramp, or use the accent for every circle. Default "group" when `group` is set, else "single". |
| `icon` | `string \| Record` | | Icon drawn above the number in large circles: one for all, or a map from circle or group name to icon. Built-in name or `path://`. |
| `sizeLegend` | `boolean \| number[]` | `false` | Size key of nested reference circles. `true` picks round values; an array sets them. |
| `labels` | `boolean` | `true` | Print names and values inside circles, and beside the ones too small to hold them. |
| `names` | `Record` | | Display names for group keys. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: [ranking-poster](https://insightcharts.dev/llms/ranking-poster.md)
### More examples
#### Flat, with highlight, icons and a size key
```tsx
import { CirclePack } from "insightcharts";
const data = [
{ service: "Schools", m: 412 },
{ service: "Public transport", m: 268 },
{ service: "Police", m: 231 },
{ service: "Waste and recycling", m: 96 },
{ service: "Social housing", m: 187 },
{ service: "Parks", m: 44 },
{ service: "Libraries", m: 23 },
{ service: "Street lighting", m: 17 },
// ... 10 more rows
];
```
#### Shaded by value inside one ring
```tsx
import { CirclePack } from "insightcharts";
const data = [
{ service: "Schools", m: 412 },
{ service: "Public transport", m: 268 },
{ service: "Police", m: 231 },
{ service: "Waste and recycling", m: 96 },
{ service: "Social housing", m: 187 },
{ service: "Parks", m: 44 },
{ service: "Libraries", m: 23 },
{ service: "Street lighting", m: 17 },
// ... 1 more rows
];
```
#### Stress: 27 circles in 5 groups, long names, values from 2 to 1,280
```tsx
import { CirclePack } from "insightcharts";
const data = [
{ category: "Messaging", app: "Chatter", users: 940 },
{ category: "Messaging", app: "Paperplane", users: 310 },
{ category: "Messaging", app: "Hello Neighbour Community Boards", users: 42 },
{ category: "Messaging", app: "Ping", users: 18 },
{ category: "Messaging", app: "Semaphore", users: 6 },
{ category: "Video", app: "Loop", users: 1280 },
{ category: "Video", app: "Screening Room", users: 260 },
{ category: "Video", app: "Clipboard", users: 95 },
// ... 19 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/circle-pack.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/circle-pack.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Curve rank (`curve-rank`)
> Places each entity on two parallel scales, one per measure, and joins its two positions with a smooth S-curve labelled with the name and both values.
- Component: `CurveRank` (import from `insightcharts`)
- Spec type: `"curve-rank"`
- Level: atom. Category: poster.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
Two different measures of the same 4 to about 15 entities, when the point is how standing on one relates to standing on the other: price against sales, staff against output, cost against usage. Each side has its own scale, stated in its heading.
### Avoid
The same measure at two moments (use slope-chart); ranks across many periods (use bump-chart); more than about 15 entities or a statistical relationship (use scatter-chart); exact comparison on one measure (use bar-chart).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/curve-rank
```
The file lands at `components/insightcharts/atoms/curve-rank.tsx`. `@insightcharts/curve-rank-demo` adds a ready-made example next to it.
### Usage
```tsx
import { CurveRank } from "insightcharts";
const data = [
{ drink: "Filter coffee", price: 2.4, cups: 310 },
{ drink: "Espresso", price: 2.6, cups: 145 },
{ drink: "Flat white", price: 3.6, cups: 260 },
{ drink: "Iced latte", price: 4.2, cups: 180 },
{ drink: "Matcha latte", price: 4.8, cups: 75 },
{ drink: "Hot chocolate", price: 3.9, cups: 40 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "curve-rank",
"data": [
{ "drink": "Filter coffee", "price": 2.4, "cups": 310 },
{ "drink": "Espresso", "price": 2.6, "cups": 145 },
{ "drink": "Flat white", "price": 3.6, "cups": 260 },
{ "drink": "Iced latte", "price": 4.2, "cups": 180 },
{ "drink": "Matcha latte", "price": 4.8, "cups": 75 },
{ "drink": "Hot chocolate", "price": 3.9, "cups": 40 }
],
"name": "drink",
"left": "price",
"right": "cups",
"leftLabel": "Price per cup",
"rightLabel": "Cups sold a day",
"leftFormat": { "style": "currency", "currency": "GBP" },
"leftTop": "low",
"title": "Filter coffee sold 310 cups a day at £2.40 a cup",
"subtitle": "Price per cup and cups sold per day for six drinks at one café, cheapest at the top",
"note": "Illustrative figures."
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `name` (required) | `string` | | Entity field: one curve per row. |
| `left` (required) | `string` | | Field for the left-hand measure. |
| `right` (required) | `string` | | Field for the right-hand measure. |
| `leftLabel` | `string` | | Heading over the left rule: the measure and its unit. Defaults to the field name. |
| `rightLabel` | `string` | | Heading over the right rule. Defaults to the field name. |
| `leftScale` | `"linear" \| "log"` | `"linear"` | Scale of the left measure. A log scale is stated in the heading. |
| `rightScale` | `"linear" \| "log"` | `"linear"` | Scale of the right measure. |
| `leftTop` | `"high" \| "low"` | `"high"` | Which end of the left measure sits at the top. |
| `rightTop` | `"high" \| "low"` | `"high"` | Which end of the right measure sits at the top. |
| `leftFormat` | `FormatSpec` | | How to print left values. Defaults to `format`. |
| `rightFormat` | `FormatSpec` | | How to print right values. Defaults to `format`. |
| `order` | `string[]` | | Entity names in the order that assigns colours. Pass the same list to glyph-table to match its row colours. |
| `colorBy` | `"series" \| "none"` | | `series`: one colour per entity; past the size of the theme's palette the extra colours are derived from the theme's own (glyph-table derives the same ones). `none`: every curve in the accent. Default: series while the entities fit the palette (at most 8), none beyond. |
| `names` | `Record` | | Display names for entity keys. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: [matrix-poster](https://insightcharts.dev/llms/matrix-poster.md)
### More examples
#### One highlighted, log scale on the right
```tsx
import { CurveRank } from "insightcharts";
const data = [
{ venue: "Corner bakery", staff: 4, visits: 380 },
{ venue: "Riverside café", staff: 7, visits: 910 },
{ venue: "Station kiosk", staff: 2, visits: 2400 },
{ venue: "Market hall", staff: 18, visits: 5200 },
{ venue: "Museum restaurant", staff: 22, visits: 640 },
{ venue: "Food truck", staff: 3, visits: 720 },
{ venue: "Hotel bar", staff: 9, visits: 210 },
];
```
#### Ten entities, one colour each
```tsx
import { CurveRank } from "insightcharts";
const data = [
{ district: "Old Town", rent: 1480, commute: 12 },
{ district: "Harbour", rent: 1390, commute: 15 },
{ district: "Riverside", rent: 1310, commute: 18 },
{ district: "University", rent: 1180, commute: 14 },
{ district: "Market", rent: 1120, commute: 20 },
{ district: "Hillcrest", rent: 1040, commute: 26 },
{ district: "Northgate", rent: 960, commute: 31 },
{ district: "Eastfield", rent: 890, commute: 34 },
// ... 2 more rows
];
```
#### Stress: 14 entities, close values, long names
```tsx
import { CurveRank } from "insightcharts";
const data = [
{ service: "Northern Line Extension to the Harbour", satisfaction: 41, cost: 12.4 },
{ service: "Ring Road Bus Rapid Transit", satisfaction: 39, cost: 11.9 },
{ service: "Airport Express", satisfaction: 38, cost: 31 },
{ service: "Old Town Tram", satisfaction: 37, cost: 9.8 },
{ service: "University Shuttle", satisfaction: 22, cost: 3.1 },
{ service: "Riverside Ferry", satisfaction: 21, cost: 6.4 },
{ service: "Hillcrest Funicular", satisfaction: 20, cost: 6.2 },
{ service: "Night Bus Network", satisfaction: 19, cost: 6 },
// ... 6 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/curve-rank.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/curve-rank.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Data table (`data-table`)
> A restrained editorial table from rows and a column config: aligned tabular numbers, an optional in-cell bar, row highlight and click-to-sort headers.
- Component: `DataTable` (import from `insightcharts`)
- Spec type: `"data-table"`
- Level: atom. Category: figure.
- An HTML component: it takes only the props listed below, plus `theme`, `mode`, `className` and `style` where its source declares them. It does not take the common chart props.
### When to use
When readers need to look up exact values, or compare a handful of entities across several measures with different units. Sort it by the column the caption refers to and highlight the row it names.
### Avoid
Showing a trend or a single comparison (use `line-chart` or `bar-chart`); more than about 7 columns or 30 rows without `maxHeight`.
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/data-table
```
The file lands at `components/insightcharts/atoms/data-table.tsx`. `@insightcharts/data-table-demo` adds a ready-made example next to it.
### Usage
```tsx
import { DataTable } from "insightcharts";
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "data-table",
"caption": "Wind and solar supplied 67% of Denmark's electricity in 2024",
"subtitle": "Electricity generation by country, 2024",
"source": "Ember (illustrative figures)",
"highlight": "Denmark",
"sort": { "key": "share", "direction": "desc" },
"columns": [
{ "key": "country", "label": "Country" },
{
"key": "share",
"label": "Wind and solar share",
"format": "percent",
"bar": true,
"width": "38%"
},
{ "key": "twh", "label": "Generation, TWh", "format": "decimal:1" },
{
"key": "change",
"label": "Change vs 2023",
"format": { "style": "percent", "signDisplay": "exceptZero", "maximumFractionDigits": 1 }
}
],
"rows": [
{ "country": "Germany", "share": 0.43, "twh": 209.4, "change": 0.031 },
{ "country": "Spain", "share": 0.42, "twh": 112.8, "change": 0.044 },
{ "country": "Denmark", "share": 0.67, "twh": 23.1, "change": 0.012 },
{ "country": "Netherlands", "share": 0.41, "twh": 49.6, "change": 0.058 },
{ "country": "Portugal", "share": 0.46, "twh": 22.9, "change": 0.027 },
{ "country": "France", "share": 0.14, "twh": 73.5, "change": 0.009 },
{ "country": "Poland", "share": 0.24, "twh": 40.2, "change": -0.004 }
]
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `rows` (required) | `Row[]` | | One object per row. |
| `columns` | `(string \| { key, label?, format?, align?, bar?, sortable?, width? })[]` | | Columns in order. Defaults to every key of the first row. Numeric columns are right-aligned. |
| `caption` | `string` | | The table's title, as a real `
`. |
| `subtitle` | `string` | | Units and period, under the caption. |
| `source` | `string` | | Shown under the table as "Source: ...". |
| `highlight` | `string \| number \| (string \| number)[]` | | Rows to emphasise, matched on the first column (or `highlightKey`). |
| `highlightKey` | `string` | | Field `highlight` matches against. |
| `sort` | `{ key, direction?: "asc" \| "desc" }` | | Initial sort. |
| `sortable` | `boolean` | `true` | Click or press Enter on a header to sort; a third press restores the original order. |
| `maxHeight` | `number \| string` | | Height before the body scrolls under the sticky header. |
| `density` | `"comfortable" \| "compact"` | `"comfortable"` | Row padding. |
### Composition
- Composes: nothing (it is a primitive)
- Used by: [data-explorer](https://insightcharts.dev/llms/data-explorer.md)
### More examples
#### Scrolling, sticky header, compact
```tsx
import { DataTable } from "insightcharts";
```
#### Default columns, no sorting
```tsx
import { DataTable } from "insightcharts";
```
#### Two highlighted rows, negative values
```tsx
import { DataTable } from "insightcharts";
```
### Links
- Registry item: https://insightcharts.dev/r/data-table.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/data-table.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Delta (`delta`)
> An inline change indicator: direction arrow, signed value and an optional baseline label.
- Component: `Delta` (import from `insightcharts`)
- Spec type: `"delta"`
- Level: atom. Category: figure.
- An HTML component: it takes only the props listed below, plus `theme`, `mode`, `className` and `style` where its source declares them. It does not take the common chart props.
### When to use
Next to a figure, in a sentence, a table cell or a tile, to say how much it changed and against what. Set `upIsGood: false` for measures where a fall is the good news (churn, emissions, wait times).
### Avoid
Standing alone as the main figure (use `big-number` or `stat-tile`, which include a delta). Do not omit the baseline label unless the surrounding text names it.
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/delta
```
The file lands at `components/insightcharts/atoms/delta.tsx`. `@insightcharts/delta-demo` adds a ready-made example next to it.
### Usage
```tsx
import { Delta } from "insightcharts";
```
### JSON spec
The same thing as data, rendered with ``:
```json
{ "type": "delta", "value": 0.124, "label": "vs last quarter", "size": "md" }
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `value` (required) | `number \| string` | | The change. Numbers format as signed percentages by default (0.12 is +12%). |
| `label` | `string` | | The baseline, e.g. "vs last quarter". |
| `upIsGood` | `boolean` | `true` | Whether an increase is good news; decides the colour only. |
| `direction` | `"up" \| "down" \| "flat"` | | Override the direction inferred from the sign. |
| `format` | `FormatSpec` | `signed percent` | Number format, e.g. `"signed"` for absolute changes or `"{value} pts"`. |
| `size` | `"sm" \| "md" \| "lg"` | | 12, 14 or 16px. Inherits the surrounding size when omitted. |
| `variant` | `"text" \| "pill"` | `"text"` | `pill` adds a tinted capsule behind the value. |
### Composition
- Composes: nothing (it is a primitive)
- Used by: [before-after](https://insightcharts.dev/llms/before-after.md), [comparison-table](https://insightcharts.dev/llms/comparison-table.md), [ranked-list](https://insightcharts.dev/llms/ranked-list.md)
### More examples
#### Down is good (churn)
```tsx
import { Delta } from "insightcharts";
```
#### Down is bad, pill
```tsx
import { Delta } from "insightcharts";
```
#### No change
```tsx
import { Delta } from "insightcharts";
```
#### String value, large pill
```tsx
import { Delta } from "insightcharts";
```
### Links
- Registry item: https://insightcharts.dev/r/delta.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/delta.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Diverging bar chart (`diverging-bar-chart`)
> Draws bars either side of a centre line: positive against negative values, or stacked agree / disagree responses around a neutral midpoint.
- Component: `DivergingBarChart` (import from `insightcharts`)
- Spec type: `"diverging-bar-chart"`
- Level: atom. Category: comparison.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
Values that can be above or below zero (net change, balance, deviation from average); survey scales where the split between the two sides matters; two opposed groups back to back.
### Avoid
Values that are all positive (use bar-chart); parts that do not have a natural two-sided order (use a stacked bar-chart); change between two dates (use arrow-chart or dumbbell-chart).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/diverging-bar-chart
```
The file lands at `components/insightcharts/atoms/diverging-bar-chart.tsx`. `@insightcharts/diverging-bar-chart-demo` adds a ready-made example next to it.
### Usage
```tsx
import { DivergingBarChart } from "insightcharts";
const data = [
{ city: "Austin", change: 21.3 },
{ city: "Phoenix", change: 11.2 },
{ city: "Seattle", change: 8.1 },
{ city: "Chicago", change: -2.9 },
{ city: "San Francisco", change: -7.4 },
{ city: "St. Louis", change: -11.6 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "diverging-bar-chart",
"data": [
{ "city": "Austin", "change": 21.3 },
{ "city": "Phoenix", "change": 11.2 },
{ "city": "Seattle", "change": 8.1 },
{ "city": "Chicago", "change": -2.9 },
{ "city": "San Francisco", "change": -7.4 },
{ "city": "St. Louis", "change": -11.6 }
],
"x": "city",
"y": "change",
"format": "{value}%",
"height": 260,
"title": "Austin's population grew 21.3% between 2010 and 2020",
"subtitle": "Population change, 2010 to 2020",
"note": "Illustrative figures."
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `x` (required) | `string` | | Category field. |
| `y` (required) | `string \| string[]` | | One signed value field, or several fields ordered from most negative to most positive. |
| `series` | `string` | | Field that splits rows into ordered series (long data). |
| `neutral` | `string \| false` | | Series centred on the midline. Default: the middle one of an odd number of series. |
| `percent` | `boolean` | `false` | Normalise each row to 100% (stacked series). |
| `colors` | `"polarity" \| "sentiment"` | `"polarity"` | Single series: diverging poles, or the positive / negative colours when one side is good and the other bad. |
| `sort` | `"none" \| "asc" \| "desc"` | | Row order. Default: descending by value or by the positive side; data order for dates. |
| `orientation` | `"auto" \| "horizontal" \| "vertical"` | `"auto"` | Horizontal, except vertical for a single series over dates. |
| `labels` | `boolean` | | Values at bar ends (single) or inside segments (stacked). Default: on when there is room. |
| `names` | `Record` | | Display names for series keys. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: no molecule or template yet
### More examples
#### Likert scale around a neutral midpoint
```tsx
import { DivergingBarChart } from "insightcharts";
const data = [
{ statement: "My manager gives me useful feedback", sd: 4, d: 9, n: 14, a: 45, sa: 28 },
{ statement: "I have the tools I need to do my job", sd: 6, d: 15, n: 17, a: 42, sa: 20 },
{ statement: "I see a path to advance here", sd: 14, d: 24, n: 22, a: 28, sa: 12 },
{ statement: "My workload is manageable", sd: 19, d: 30, n: 16, a: 27, sa: 8 },
{ statement: "Pay is fair for the work I do", sd: 23, d: 31, n: 18, a: 21, sa: 7 },
];
```
#### Two opposed groups, long format
```tsx
import { DivergingBarChart } from "insightcharts";
const data = [
{ age: "75+", sex: "Men", share: 3.1 },
{ age: "75+", sex: "Women", share: 4.6 },
{ age: "60 to 74", sex: "Men", share: 7.9 },
{ age: "60 to 74", sex: "Women", share: 8.6 },
{ age: "45 to 59", sex: "Men", share: 10.2 },
{ age: "45 to 59", sex: "Women", share: 10.4 },
{ age: "30 to 44", sex: "Men", share: 10.1 },
{ age: "30 to 44", sex: "Women", share: 9.9 },
// ... 4 more rows
];
```
#### Vertical over time, good and bad
```tsx
import { DivergingBarChart } from "insightcharts";
const data = [
{ month: "2024-01", jobs: 119 },
{ month: "2024-02", jobs: 222 },
{ month: "2024-03", jobs: 246 },
{ month: "2024-04", jobs: 118 },
{ month: "2024-05", jobs: 193 },
{ month: "2024-06", jobs: 87 },
{ month: "2024-07", jobs: -32 },
{ month: "2024-08", jobs: -71 },
// ... 4 more rows
];
```
#### Stress: 14 long statements, one highlighted, normalised
```tsx
import { DivergingBarChart } from "insightcharts";
const data = [
{
statement: "The government should raise taxes on the highest earners",
oppose: 420,
unsure: 90,
support: 180,
},
{
statement: "Public transport should be free for people under 25",
oppose: 367,
unsure: 107,
support: 251,
},
{
statement: "The retirement age should rise in line with life expectancy",
oppose: 314,
unsure: 124,
support: 322,
},
{
statement: "New housing should be allowed on protected countryside",
oppose: 261,
unsure: 141,
support: 393,
},
{
statement: "Private cars should be banned from the city centre",
oppose: 208,
unsure: 158,
support: 464,
},
{
statement: "University tuition should be paid entirely by the state",
oppose: 155,
unsure: 95,
support: 205,
},
{
statement: "The country should build new nuclear power stations",
oppose: 402,
unsure: 112,
support: 276,
},
{
statement: "Immigration quotas should be set by regional governments",
oppose: 349,
unsure: 129,
support: 347,
},
// ... 6 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/diverging-bar-chart.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/diverging-bar-chart.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Donut chart (`donut-chart`)
> Shows how 2 to 5 parts make up one whole, with each slice labelled by name and share.
- Component: `DonutChart` (import from `insightcharts`)
- Spec type: `"donut-chart"`
- Level: atom. Category: part-to-whole.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
A simple composition with one dominant part, or one part the title is about, where the total itself is worth printing in the centre.
### Avoid
More than 5 categories or parts of similar size that must be ranked (use bar-chart); a single figure (use big-number); composition compared across groups or time (use bar-chart with stack "percent"); a compact card (use segmented-bar).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/donut-chart
```
The file lands at `components/insightcharts/atoms/donut-chart.tsx`. `@insightcharts/donut-chart-demo` adds a ready-made example next to it.
### Usage
```tsx
import { DonutChart } from "insightcharts";
const data = [
{ tenure: "Own outright", households: 8.8 },
{ tenure: "Own with a mortgage", households: 7.4 },
{ tenure: "Rent privately", households: 4.9 },
{ tenure: "Rent social housing", households: 4.1 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "donut-chart",
"data": [
{ "tenure": "Own outright", "households": 8.8 },
{ "tenure": "Own with a mortgage", "households": 7.4 },
{ "tenure": "Rent privately", "households": 4.9 },
{ "tenure": "Rent social housing", "households": 4.1 }
],
"name": "tenure",
"value": "households",
"format": "{value}m",
"centerLabel": "households",
"highlight": "Rent privately",
"title": "4.9 million households in England rented privately in 2022-23",
"subtitle": "Households by tenure, England, 2022-23",
"source": "English Housing Survey",
"note": "Figures are approximate, for illustration"
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `name` (required) | `string` | | Category field: one slice per distinct value. |
| `value` (required) | `string` | | Value field. |
| `maxSlices` | `number` | `5` | Most slices drawn, including "Other"; smaller categories are folded. |
| `centerLabel` | `string` | | Small label in the hole, e.g. "Total". Turns the centre text on. |
| `centerValue` | `string \| number` | | Big text in the hole. Default: the formatted total when `centerLabel` is set. |
| `otherLabel` | `string` | `"Other"` | Name of the folded slice. |
| `names` | `Record` | | Display names for category keys. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: [poster](https://insightcharts.dev/llms/poster.md)
### More examples
#### Two slices, no centre
```tsx
import { DonutChart } from "insightcharts";
const data = [{ answer: "Remote or hybrid", share: 41 }, { answer: "On site every day", share: 59 }];
```
#### Folded into Other, custom centre
```tsx
import { DonutChart } from "insightcharts";
const data = [
{ source: "Coal", twh: 10434 },
{ source: "Gas", twh: 6634 },
{ source: "Hydro", twh: 4210 },
{ source: "Nuclear", twh: 2686 },
{ source: "Wind", twh: 2304 },
{ source: "Solar", twh: 1631 },
{ source: "Bioenergy", twh: 697 },
{ source: "Other fossil", twh: 786 },
// ... 1 more rows
];
```
#### Stress: long names and a sliver
```tsx
import { DonutChart } from "insightcharts";
const data = [
{ item: "Salaries, pensions and other staff-related expenditure", cost: 612 },
{ item: "Buildings and facilities management", cost: 188 },
{ item: "Information technology and software licences", cost: 94 },
{ item: "Travel", cost: 3 },
];
```
### Links
- Registry item: https://insightcharts.dev/r/donut-chart.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/donut-chart.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Dumbbell chart (`dumbbell-chart`)
> Shows two values per category as dots joined by a line, so the length of the line shows the difference.
- Component: `DumbbellChart` (import from `insightcharts`)
- Spec type: `"dumbbell-chart"`
- Level: atom. Category: comparison.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
Before / after or group A / group B across up to about 20 categories, when the size of the difference is what the reader needs.
### Avoid
A single value per category (use lollipop-chart or bar-chart); when the direction of change matters more than the two levels (use arrow-chart); more than two points in time (use slope-chart or line-chart).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/dumbbell-chart
```
The file lands at `components/insightcharts/atoms/dumbbell-chart.tsx`. `@insightcharts/dumbbell-chart-demo` adds a ready-made example next to it.
### Usage
```tsx
import { DumbbellChart } from "insightcharts";
const data = [
{ country: "South Korea", y2000: 76, y2023: 83.6 },
{ country: "Turkey", y2000: 71.1, y2023: 77.3 },
{ country: "Poland", y2000: 73.8, y2023: 78.6 },
{ country: "Japan", y2000: 81.2, y2023: 84.1 },
{ country: "Germany", y2000: 78.2, y2023: 81.2 },
{ country: "United States", y2000: 76.7, y2023: 78.4 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "dumbbell-chart",
"data": [
{ "country": "South Korea", "y2000": 76, "y2023": 83.6 },
{ "country": "Turkey", "y2000": 71.1, "y2023": 77.3 },
{ "country": "Poland", "y2000": 73.8, "y2023": 78.6 },
{ "country": "Japan", "y2000": 81.2, "y2023": 84.1 },
{ "country": "Germany", "y2000": 78.2, "y2023": 81.2 },
{ "country": "United States", "y2000": 76.7, "y2023": 78.4 }
],
"category": "country",
"from": "y2000",
"to": "y2023",
"names": { "y2000": "2000", "y2023": "2023" },
"gapLabel": "Gain",
"format": "decimal:1",
"height": 280,
"title": "Life expectancy in South Korea rose from 76.0 years in 2000 to 83.6 in 2023",
"subtitle": "Life expectancy at birth, years, 2000 and 2023",
"source": "OECD Health Statistics (rounded)",
"note": "Figures are approximate, for illustration"
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `category` (required) | `string` | | Category field: one row of the chart each. |
| `from` (required) | `string` | | Field with the first value, or (long data) the `series` value that marks it. |
| `to` (required) | `string` | | Field with the second value, or (long data) the `series` value that marks it. |
| `series` | `string` | | Long data: the field whose values are `from` and `to`. |
| `value` | `string` | `"value"` | Long data: the measure field. |
| `kind` | `"change" \| "groups"` | `"change"` | `change` draws a tint then the accent (before / after); `groups` uses two hues (two peers). |
| `sort` | `"gap" \| "from" \| "to" \| "none"` | `"gap"` | Row order. |
| `order` | `"desc" \| "asc"` | `"desc"` | Sort direction. |
| `labels` | `boolean` | | Value labels beside both dots. Default: on for up to 14 rows; otherwise a value axis is drawn. |
| `gapLabel` | `boolean \| string` | `false` | Print the gap in a right-hand column; a string sets the column header. |
| `zero` | `boolean` | `false` | Start the value axis at zero. |
| `names` | `Record` | | Display names for the `from` and `to` keys. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: [executive-brief](https://insightcharts.dev/llms/executive-brief.md)
### More examples
#### Two groups, highlighted row
```tsx
import { DumbbellChart } from "insightcharts";
const data = [
{ country: "South Korea", men: 100, women: 68.8 },
{ country: "Japan", men: 100, women: 78.7 },
{ country: "United States", men: 100, women: 83 },
{ country: "United Kingdom", men: 100, women: 85.5 },
{ country: "Germany", men: 100, women: 85.6 },
{ country: "France", men: 100, women: 88.4 },
{ country: "Sweden", men: 100, women: 92.7 },
{ country: "Belgium", men: 100, women: 98.8 },
];
```
#### Long format
```tsx
import { DumbbellChart } from "insightcharts";
const data = [
{ city: "Lisbon", period: "2015", value: 860 },
{ city: "Lisbon", period: "2024", value: 1720 },
{ city: "Dublin", period: "2015", value: 1390 },
{ city: "Dublin", period: "2024", value: 2380 },
{ city: "Berlin", period: "2015", value: 780 },
{ city: "Berlin", period: "2024", value: 1310 },
{ city: "Madrid", period: "2015", value: 790 },
{ city: "Madrid", period: "2024", value: 1260 },
// ... 2 more rows
];
```
#### Stress: 22 rows, long names, axis instead of labels
```tsx
import { DumbbellChart } from "insightcharts";
const data = [
{ sector: "Professional, scientific and technical services", before: 41, after: 63 },
{ sector: "Information and communication", before: 48, after: 66 },
{ sector: "Financial and insurance activities", before: 39, after: 58 },
{ sector: "Public administration and defence", before: 22, after: 44 },
{ sector: "Education", before: 18, after: 33 },
{ sector: "Real estate activities", before: 21, after: 35 },
{ sector: "Administrative and support services", before: 14, after: 26 },
{ sector: "Arts, entertainment and recreation", before: 11, after: 21 },
// ... 14 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/dumbbell-chart.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/dumbbell-chart.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Fan flow (`fan-flow`)
> One solid block that splits into curved ribbons, each as thick as its value, ending at an aligned column of names, values and shares.
- Component: `FanFlow` (import from `insightcharts`)
- Spec type: `"fan-flow"`
- Level: atom. Category: poster.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
One total divided among 4 to about 14 destinations (or collected from that many origins, with `direction: "in"`) when the picture of the split is the point: where a budget goes, what a day's water is used for, which sources feed one total.
### Avoid
Flows with more than one stage or several sources (use sankey-chart); precise comparison of the parts (use bar-chart); 2 to 4 parts (use segmented-bar or donut-chart); values that can be negative (use bar-chart).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/fan-flow
```
The file lands at `components/insightcharts/atoms/fan-flow.tsx`. `@insightcharts/fan-flow-demo` adds a ready-made example next to it.
### Usage
```tsx
import { FanFlow } from "insightcharts";
const data = [
{ use: "Shower", litres: 128 },
{ use: "Toilet", litres: 97 },
{ use: "Washing machine", litres: 62 },
{ use: "Kitchen tap", litres: 45 },
{ use: "Dishwasher", litres: 24 },
{ use: "Garden", litres: 21 },
{ use: "Bathroom tap", litres: 19 },
{ use: "Leaks", litres: 16 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "fan-flow",
"data": [
{ "use": "Shower", "litres": 128 },
{ "use": "Toilet", "litres": 97 },
{ "use": "Washing machine", "litres": 62 },
{ "use": "Kitchen tap", "litres": 45 },
{ "use": "Dishwasher", "litres": 24 },
{ "use": "Garden", "litres": 21 },
{ "use": "Bathroom tap", "litres": 19 },
{ "use": "Leaks", "litres": 16 }
],
"name": "use",
"value": "litres",
"trunk": "Litres a day",
"tagged": ["Shower", "Washing machine", "Dishwasher"],
"tagLabel": "Uses heated water",
"title": "Showers used 128 of a household's 412 litres of water a day",
"subtitle": "Daily water use of one four-person household by use, litres",
"note": "Illustrative figures."
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `name` (required) | `string` | | Destination field: one ribbon per distinct value. |
| `value` (required) | `string` | | Value field: ribbon thickness. Rows sharing a name are summed; non-positive values are dropped. |
| `trunk` | `string` | | Label printed in the single block, with the total. |
| `direction` | `"out" \| "in"` | `"out"` | `out`: one block fans out to many labels on the right. `in`: many labels on the left flow into one block. |
| `maxItems` | `number` | `12` | Ribbons to keep; the rest are summed into the remainder ribbon. |
| `otherLabel` | `string` | `"Other"` | Name of the remainder ribbon, drawn in the context colour. A row with this name is treated as the remainder. |
| `tagged` | `string[]` | | Destinations marked with a dotted outline around the name. |
| `tagLabel` | `string` | | What the dotted outline means, printed in the key. Needed for `tagged` to be drawn. |
| `share` | `boolean` | `true` | Print each ribbon's share of the total. Dropped on narrow screens. |
| `colorBy` | `"series" \| "none"` | | `series`: one colour per destination; past the size of the theme's palette the extra colours are derived from the theme's own. `none`: every ribbon in the accent. Default: series while the destinations fit the palette (at most 8), none beyond. |
| `sort` | `"value" \| "none"` | `"value"` | Largest first, or the data order. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: [flow-poster](https://insightcharts.dev/llms/flow-poster.md)
### More examples
#### Many into one, with highlight
```tsx
import { FanFlow } from "insightcharts";
const data = [
{ district: "Riverside", riders: 2600 },
{ district: "Old Town", riders: 2150 },
{ district: "Hillcrest", riders: 1700 },
{ district: "Northgate", riders: 1250 },
{ district: "Mill Quarter", riders: 900 },
{ district: "Eastfield", riders: 520 },
{ district: "Lakeside", riders: 280 },
];
```
#### One colour
```tsx
import { FanFlow } from "insightcharts";
const data = [
{ item: "Rent", amount: 1180 },
{ item: "Food", amount: 520 },
{ item: "Transport", amount: 240 },
{ item: "Energy", amount: 190 },
{ item: "Savings", amount: 150 },
{ item: "Other", amount: 120 },
];
```
#### Ten destinations, one colour each
```tsx
import { FanFlow } from "insightcharts";
const data = [
{ service: "Schools", spend: 212 },
{ service: "Social care", spend: 164 },
{ service: "Transport", spend: 121 },
{ service: "Housing", spend: 96 },
{ service: "Waste", spend: 71 },
{ service: "Police", spend: 64 },
{ service: "Parks", spend: 42 },
{ service: "Fire service", spend: 38 },
// ... 2 more rows
];
```
#### Stress: 18 destinations, long names, very uneven values
```tsx
import { FanFlow } from "insightcharts";
const data = [
{ line: "Salaries and employer contributions for teaching staff", pounds: 48200000 },
{ line: "Building maintenance, heating and electricity", pounds: 9100000 },
{ line: "Special educational needs support", pounds: 7400000 },
{ line: "Catering", pounds: 3900000 },
{ line: "Administration and office staff", pounds: 3600000 },
{ line: "Books, software licences and classroom equipment", pounds: 2100000 },
{ line: "School trips", pounds: 640000 },
{ line: "Sports hall hire", pounds: 410000 },
// ... 10 more rows
];
```
### Links
- Registry item: https://insightcharts.dev/r/fan-flow.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/fan-flow.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Funnel chart (`funnel-chart`)
> Shows how many remain at each step of a sequence, with conversion and drop-off between steps.
- Component: `FunnelChart` (import from `insightcharts`)
- Spec type: `"funnel-chart"`
- Level: atom. Category: flow.
- An ECharts chart built with `defineChart`: accepts every common chart prop (data, transform, title, highlight, annotations, ...).
### When to use
A strict sequence where each stage is a subset of the one before: sign-up flows, hiring pipelines, sales stages, applications to admissions. 3 to 7 stages.
### Avoid
Stages that are not sequential subsets, or categories with no order (use bar-chart); flows that split to several destinations (use sankey-chart); conversion tracked over time (use line-chart).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/funnel-chart
```
The file lands at `components/insightcharts/atoms/funnel-chart.tsx`. `@insightcharts/funnel-chart-demo` adds a ready-made example next to it.
### Usage
```tsx
import { FunnelChart } from "insightcharts";
const data = [
{ step: "Visited the pricing page", users: 48200 },
{ step: "Started a free trial", users: 9640 },
{ step: "Connected a data source", users: 5310 },
{ step: "Invited a teammate", users: 2230 },
{ step: "Became a paying customer", users: 1480 },
];
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "funnel-chart",
"data": [
{ "step": "Visited the pricing page", "users": 48200 },
{ "step": "Started a free trial", "users": 9640 },
{ "step": "Connected a data source", "users": 5310 },
{ "step": "Invited a teammate", "users": 2230 },
{ "step": "Became a paying customer", "users": 1480 }
],
"stage": "step",
"value": "users",
"format": "integer",
"title": "20% of pricing-page visitors started a free trial",
"subtitle": "Users reaching each step of the sign-up funnel, last 90 days",
"source": "Product analytics",
"note": "Illustrative data"
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `stage` (required) | `string` | | Stage field. Rows are read in data order; the first row is the top of the funnel. |
| `value` (required) | `string` | | Count field. |
| `dropoff` | `boolean` | `true` | State the drop-off between consecutive stages. |
| `dropoffLabel` | `string` | `"dropped off"` | Word for what is lost between stages. |
### Common props
Also accepts every prop in the "Common chart props" section of this file.
### Composition
- Composes: nothing (it is a primitive)
- Used by: no molecule or template yet
### More examples
#### Three stages, highlight
```tsx
import { FunnelChart } from "insightcharts";
const data = [
{ stage: "Applied", people: 12400 },
{ stage: "Interviewed", people: 1860 },
{ stage: "Offered a place", people: 930 },
];
```
#### Stress: seven stages, long names, big numbers
```tsx
import { FunnelChart } from "insightcharts";
const data = [
{ stage: "Impressions served across all paid channels", n: 18400000 },
{ stage: "Clicked through to the landing page", n: 2210000 },
{ stage: "Viewed at least one product", n: 1480000 },
{ stage: "Added an item to the basket", n: 412000 },
{ stage: "Reached checkout", n: 236000 },
{ stage: "Entered payment details", n: 171000 },
{ stage: "Completed purchase", n: 148000 },
];
```
### Links
- Registry item: https://insightcharts.dev/r/funnel-chart.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/funnel-chart.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Glyph table (`glyph-table`)
> A table with one row per entity in which every number is drawn as a small form chosen per column (counted dots, squares or icons, a ring, a sized circle, a bar) with the value printed beside it.
- Component: `GlyphTable` (import from `insightcharts`)
- Spec type: `"glyph-table"`
- Level: atom. Category: poster.
- An HTML component: it takes only the props listed below, plus `theme`, `mode`, `className` and `style` where its source declares them. It does not take the common chart props.
### When to use
Comparing 3 to about 12 entities across 2 to 5 measures with different units, when the table should read as a graphic: profiles of products, cities, teams or species. Pair it with curve-rank by passing both the same `order`.
### Avoid
Looking up many exact values, sorting, or more than about 12 rows (use data-table); one measure across entities (use bar-chart or pictogram-chart); a trend over time (use line-chart or small-multiples).
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/glyph-table
```
The file lands at `components/insightcharts/atoms/glyph-table.tsx`. `@insightcharts/glyph-table-demo` adds a ready-made example next to it.
### Usage
```tsx
import { GlyphTable } from "insightcharts";
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "glyph-table",
"caption": "Filter coffee sold 310 cups a day at £2.40 a cup",
"subtitle": "Six drinks at one café: price, cups sold per day, share ordered with oat milk, seconds to prepare and caffeine",
"note": "Illustrative figures.",
"name": "drink",
"nameLabel": "Drink",
"columns": [
{
"field": "price",
"label": "Price per cup",
"glyph": "bar",
"format": { "style": "currency", "currency": "GBP" },
"max": 5
},
{ "field": "cups", "label": "Cups sold a day", "glyph": "dots", "unit": 10 },
{ "field": "oat", "label": "With oat milk", "glyph": "ring", "format": "{value}%" },
{ "field": "seconds", "label": "Seconds to prepare", "glyph": "blocks", "unit": 10 },
{ "field": "caffeine", "label": "Caffeine, mg", "glyph": "circle" }
],
"rows": [
{
"drink": "Filter coffee",
"price": 2.4,
"cups": 310,
"oat": 8,
"seconds": 15,
"caffeine": 140
},
{ "drink": "Espresso", "price": 2.6, "cups": 145, "oat": null, "seconds": 30, "caffeine": 65 },
{ "drink": "Flat white", "price": 3.6, "cups": 260, "oat": 34, "seconds": 95, "caffeine": 130 },
{ "drink": "Iced latte", "price": 4.2, "cups": 180, "oat": 41, "seconds": 70, "caffeine": 130 },
{ "drink": "Matcha latte", "price": 4.8, "cups": 75, "oat": 57, "seconds": 110, "caffeine": 70 },
{ "drink": "Hot chocolate", "price": 3.9, "cups": 40, "oat": 22, "seconds": 85, "caffeine": 10 }
]
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `rows` (required) | `Row[]` | | One object per entity. |
| `name` | `string` | | Field holding the entity name. Defaults to the first key of the first row. |
| `nameLabel` | `string` | | Heading of the name column. |
| `columns` (required) | `{ field, label?, glyph?: "dots" \| "blocks" \| "ring" \| "circle" \| "bar" \| "icons" \| "text", unit?, format?, max?, icon? }[]` | | Glyph columns in order. `dots`, `blocks` and `icons` draw one mark per `unit` (default 1, raised to a round number when the largest value would not fit, and stated in the heading). `ring` fills to `max` (default 100, or 1 for fractions). `circle` is sized by area and `bar` by length against `max` (default: the column's largest value). `text` prints the value alone. |
| `order` | `string[]` | | Entity names in display order; also assigns colours. Pass the same list to curve-rank to match its curve colours. |
| `highlight` | `string \| string[]` | | Entities drawn in colour; every other row turns grey. |
| `colorBy` | `"series" \| "none"` | | `series`: one colour per entity; past the size of the theme's palette the extra colours are derived from the theme's own (curve-rank derives the same ones). `none`: every row in the accent. Default: series while the rows fit the palette (at most 8), none beyond. |
| `caption` | `string` | | The table's title, as a real `
`. |
| `subtitle` | `string` | | Measures, units and period, under the caption. |
| `source` | `string` | | Shown under the table as "Source: ...". |
| `note` | `string` | | Method notes or caveats, shown under the table. |
| `select` | `boolean` | | Let readers click a row (or press Enter on it) to select it; the other rows fade. |
| `onSelect` | `(values: string[]) => void` | | Called with the selected entity names whenever the selection changes. |
| `selected` | `string[]` | | Controlled selection: the entity names currently selected. When given, the table shows exactly this list and a click only calls `onSelect`. Omit it and the table keeps its own selection. |
### Composition
- Composes: nothing (it is a primitive)
- Used by: [matrix-poster](https://insightcharts.dev/llms/matrix-poster.md), [profile-poster](https://insightcharts.dev/llms/profile-poster.md)
### More examples
#### Highlighted row, icons, selectable
```tsx
import { GlyphTable } from "insightcharts";
```
#### Ten entities, one colour each
```tsx
import { GlyphTable } from "insightcharts";
```
#### Stress: 10 rows, long names, very uneven values, missing data
```tsx
import { GlyphTable } from "insightcharts";
```
### Links
- Registry item: https://insightcharts.dev/r/glyph-table.json
- Source: https://github.com/insightcharts/insightcharts/blob/main/packages/insightcharts/src/atoms/glyph-table.tsx
- Spec schema: https://insightcharts.dev/schema/insight-spec.json
- Choosing a form: https://insightcharts.dev/llms/guides/choosing-a-form.md
- All components: https://insightcharts.dev/llms.txt
---
## Headline (`headline`)
> The opening block of a piece or section: kicker, title, one-sentence deck and a byline/date line.
- Component: `Headline` (import from `insightcharts`)
- Spec type: `"headline"`
- Level: atom. Category: text.
- An HTML component: it takes only the props listed below, plus `theme`, `mode`, `className` and `style` where its source declares them. It does not take the common chart props.
### When to use
At the top of a data story (`page`), at the start of each chapter (`section`), or as the heading of a tile (`card`). Write the title as one plain fact with its number. Write the deck as a second plain sentence.
### Avoid
Titling a single chart: charts carry their own `title` and `subtitle`. For numbered chapters use `step-marker`.
### Install
From npm (the component ships in the package):
```bash
npm install insightcharts echarts
```
Or copy the source into your project with the shadcn CLI, so you can edit it (the engine still comes from the `insightcharts` package):
```bash
npx shadcn@latest registry add @insightcharts=https://insightcharts.dev/r/{name}.json # once per project
npx shadcn@latest add @insightcharts/headline
```
The file lands at `components/insightcharts/atoms/headline.tsx`. `@insightcharts/headline-demo` adds a ready-made example next to it.
### Usage
```tsx
import { Headline } from "insightcharts";
```
### JSON spec
The same thing as data, rendered with ``:
```json
{
"type": "headline",
"size": "page",
"kicker": "Energy transition",
"title": "Solar generated more electricity than coal in Europe in 2024",
"deck": "Solar was the continent's second-largest source of electricity that year.",
"byline": "By Ana Ruiz and Tom Okafor",
"date": "2025-03-14"
}
```
### Props
| Prop | Type | Default | Description |
|---|---|---|---|
| `title` (required) | `string` | | One fact with its number, as a plain sentence. Wraps balanced. |
| `kicker` | `string` | | Small label above the title: section, series or topic. Sentence case. |
| `deck` | `string` | | One-sentence standfirst. |
| `byline` | `string` | | Author line, shown as written, e.g. "By Ana Ruiz". |
| `date` | `string` | | ISO dates are formatted for the locale and wrapped in `