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.
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.
- Use when
- 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 when
- 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).
Preview
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 },
];
export function Example() {
return (
<FanFlow
data={data}
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."
/>
);
}{
"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."
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Composition
Used in
1 infographic uses fan flow.
Infographics
1Installation
$ pnpm dlx shadcn@latest add @insightcharts/fan-flowThe CLI copies the source into your project and needs the @insightcharts registry in components.json; see installation. With the package, import it by name:
import { FanFlow } from "insightcharts";Examples
Many into one, with highlight
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 },
];
export function Example() {
return (
<FanFlow
data={data}
name="district"
value="riders"
direction="in"
trunk="Riders arriving at Central Station"
highlight="Riverside"
format="integer"
title="Riverside sent 2,600 of the 9,400 daily riders arriving at Central Station"
subtitle="Riders arriving at Central Station on a weekday by home district"
note="Illustrative figures for an invented city."
/>
);
}{
"type": "fan-flow",
"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 }
],
"name": "district",
"value": "riders",
"direction": "in",
"trunk": "Riders arriving at Central Station",
"highlight": "Riverside",
"format": "integer",
"title": "Riverside sent 2,600 of the 9,400 daily riders arriving at Central Station",
"subtitle": "Riders arriving at Central Station on a weekday by home district",
"note": "Illustrative figures for an invented city."
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
One colour
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 },
];
export function Example() {
return (
<FanFlow
data={data}
name="item"
value="amount"
colorBy="none"
trunk="Monthly take-home pay"
format={"€{value}"}
title="Rent took €1,180 of a €2,400 monthly pay packet"
subtitle="Monthly spending of one renter by category, euros"
note="Illustrative figures."
/>
);
}{
"type": "fan-flow",
"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 }
],
"name": "item",
"value": "amount",
"colorBy": "none",
"trunk": "Monthly take-home pay",
"format": "€{value}",
"title": "Rent took €1,180 of a €2,400 monthly pay packet",
"subtitle": "Monthly spending of one renter by category, euros",
"note": "Illustrative figures."
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Ten destinations, one colour each
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 },
{ service: "Libraries", spend: 23 },
{ service: "Culture", spend: 19 },
];
export function Example() {
return (
<FanFlow
data={data}
name="service"
value="spend"
colorBy="series"
trunk="City budget"
format={"€{value}m"}
title="Schools took €212m of one city's €850m budget"
subtitle="Annual spending of one city council by service, millions of euros"
note="Illustrative figures for an invented city."
/>
);
}{
"type": "fan-flow",
"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 },
{ "service": "Libraries", "spend": 23 },
{ "service": "Culture", "spend": 19 }
],
"name": "service",
"value": "spend",
"colorBy": "series",
"trunk": "City budget",
"format": "€{value}m",
"title": "Schools took €212m of one city's €850m budget",
"subtitle": "Annual spending of one city council by service, millions of euros",
"note": "Illustrative figures for an invented city."
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Stress: 18 destinations, long names, very uneven values
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 },
{ line: "Library", pounds: 380000 },
{ line: "Music lessons", pounds: 300000 },
{ line: "Staff training", pounds: 290000 },
{ line: "Insurance", pounds: 260000 },
{ line: "Cleaning supplies", pounds: 150000 },
{ line: "Printing", pounds: 120000 },
{ line: "Garden club", pounds: 45000 },
{ line: "First-aid supplies", pounds: 30000 },
{ line: "Lost property", pounds: 12000 },
{ line: "Chess club", pounds: 8000 },
];
export function Example() {
return (
<FanFlow
data={data}
name="line"
value="pounds"
maxItems={10}
otherLabel="8 smaller lines"
trunk="Annual budget"
tagged={["Catering", "School trips", "Music lessons"]}
tagLabel="Partly paid by parents"
format="currency:GBP"
title="Teaching salaries took £48.2m of one school's £76.9m budget"
subtitle="Annual spending of one secondary school by budget line, pounds"
note="Illustrative figures."
/>
);
}{
"type": "fan-flow",
"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 },
{ "line": "Library", "pounds": 380000 },
{ "line": "Music lessons", "pounds": 300000 },
{ "line": "Staff training", "pounds": 290000 },
{ "line": "Insurance", "pounds": 260000 },
{ "line": "Cleaning supplies", "pounds": 150000 },
{ "line": "Printing", "pounds": 120000 },
{ "line": "Garden club", "pounds": 45000 },
{ "line": "First-aid supplies", "pounds": 30000 },
{ "line": "Lost property", "pounds": 12000 },
{ "line": "Chess club", "pounds": 8000 }
],
"name": "line",
"value": "pounds",
"maxItems": 10,
"otherLabel": "8 smaller lines",
"trunk": "Annual budget",
"tagged": ["Catering", "School trips", "Music lessons"],
"tagLabel": "Partly paid by parents",
"format": "currency:GBP",
"title": "Teaching salaries took £48.2m of one school's £76.9m budget",
"subtitle": "Annual spending of one secondary school by budget line, pounds",
"note": "Illustrative figures."
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| namerequired | string | Destination field: one ribbon per distinct value. | |
| valuerequired | 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 chart props 29
Every chart built with defineChart accepts these on top of its own props.
| Prop | Type | Description |
|---|---|---|
| data | DataInput | Rows, CSV text, a URL, { url, path }, an async function, or an Arrow-style table. Omit inside <InsightData>. |
| 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 <InsightData> 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 theme, or a partial set of tokens merged over the surrounding theme, for this chart only. |
| 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. Reduced-motion preferences are always respected. |
| lazy | boolean | Mount the chart only when it scrolls near the viewport. Default true. |
| 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<string, ChartEventHandler> | ECharts event handlers, e.g. { click: (params, chart) => {} }. Prefix a name with zr: to bind to the canvas itself. |
| onReady | (chart: EChartsInstance) => void | Called with the ECharts instance once it is created. |
| className | string | Class name for the outer <figure>. |
| style | CSSProperties | Inline style for the outer <figure>. |