Core concepts
Crossfiltering
InsightData shares one dataset between charts. A click on a selectable chart filters the others.
InsightData loads a dataset once and shares it. Charts inside it that do not pass their own data read the shared rows, and any chart with select filters the others when the reader clicks a mark.
Click a bar to filter the other charts.
By region
By source
Renewable generation by year
TWh, stacked by source. Follows both selections above.
import { BarChart, InsightData, useInsightData } from "insightcharts";
const sum = (groupBy: string | string[]) => ({
type: "aggregate" as const,
groupBy,
measures: { twh: { op: "sum" as const, field: "twh" } },
});
function Reset() {
const store = useInsightData();
const active = Object.values(store?.selections ?? {}).flatMap((s) => s.values ?? []);
return (
<p style={{ display: "flex", gap: 12, alignItems: "center", minHeight: 28, margin: 0, fontSize: 13 }}>
<span style={{ opacity: 0.7 }}>{active.length ? `Filtered to ${active.join(", ")}` : "Click a bar to filter the other charts."}</span>
{active.length ? (
<button type="button" onClick={() => store?.clear()} style={{ textDecoration: "underline", textUnderlineOffset: 3 }}>
Reset
</button>
) : null}
</p>
);
}
export default function Crossfilter() {
return (
<InsightData data="/data/energy.csv">
<div style={{ display: "grid", gap: 20 }}>
<Reset />
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(240px, 1fr))", gap: 24 }}>
<BarChart id="by-region" select x="region" y="twh" transform={sum("region")} format="compact" height={180} title="By region" />
<BarChart id="by-source" select x="source" y="twh" transform={sum("source")} format="compact" height={180} title="By source" />
</div>
<BarChart
x="year"
y="twh"
series="source"
stack
orientation="vertical"
transform={sum(["year", "source"])}
format="compact"
height={240}
title="Renewable generation by year"
subtitle="TWh, stacked by source. Follows both selections above."
note="Illustrative data"
/>
</div>
</InsightData>
);
}
Click a region, then a source. The stacked chart follows both; each selectable chart keeps showing its full set of bars while the other one filters it.
How it works
InsightDataresolvesdata(any accepted shape) and optionally applies atransformto everything.- A chart inside with no
dataprop receives the shared rows, filtered by every active selection except its own. Because of that exception, the bars in a selectable chart do not disappear when you click one of them. - The chart's own
transformruns after the filter. In the demo each chart aggregates the same long-format rows a different way. - Clicking a mark toggles its value in the chart's selection, so several values can be selected at once. Clicking the empty plot area clears that chart's selection. Selected marks stay solid and the rest fade.
The select prop
| Value | Selects on |
|---|---|
true | The chart's own category field: x for a bar chart, series for a line chart. Each chart documents its field. |
"region" | The named field. Use this when the field differs from the one the chart draws. |
Give selectable charts a stable id. It keys the chart's entry in the store, which matters when you read or control selections yourself.
onSelect(values, field) is called whenever a chart's selection changes.
Without InsightData
select also works on a chart that stands alone. The selection is then local: the chart fades unselected marks and reports through onSelect, and you decide what to do with the values.
Search brought in 420 signups
Click a bar to select it. Click the empty plot area to clear.
onSelect: []
import { useState } from "react";
import { BarChart } from "insightcharts";
const data = [
{ channel: "Search", signups: 420 },
{ channel: "Referral", signups: 310 },
{ channel: "Social", signups: 180 },
{ channel: "Email", signups: 95 },
];
export default function SelectLocal() {
const [picked, setPicked] = useState<string[]>([]);
return (
<div style={{ display: "grid", gap: 12 }}>
<BarChart data={data} x="channel" y="signups" select onSelect={(values) => setPicked(values)} orientation="horizontal" height={200} title="Search brought in 420 signups" subtitle="Click a bar to select it. Click the empty plot area to clear." />
<p aria-live="polite" style={{ margin: 0, fontSize: 13, opacity: 0.75 }}>
onSelect: {picked.length ? JSON.stringify(picked) : "[]"}
</p>
</div>
);
}
Reading and controlling the selection
useInsightData() returns the store from the nearest InsightData, or null outside one. The reset control in the first demo uses it.
| Member | Purpose |
|---|---|
rows, all | Every loaded row, unfiltered. |
status, error | "loading", "ready" or "error". |
selections | Active selections keyed by chart id: { field, values?, range? }. |
select(owner, selection) | Set or clear (null) one owner's selection. |
clear() | Clear every selection. |
rowsFor(owner?) | Rows filtered by every selection except owner's. |
A selection is either a list of values or a range of [min, max] on a field, matched inclusively. Charts produce value selections; set a range yourself from a slider or a date picker:
const store = useInsightData();
store?.select("date-range", { field: "year", range: [2022, 2024] });To keep selections in the URL or in your own state, control them:
const [selections, setSelections] = useState({});
<InsightData data="/data/energy.csv" selections={selections} onSelectionsChange={setSelections}>
...
</InsightData>Linked tooltips
Crossfiltering changes the data. To only link hover state, give charts the same group id and their tooltips and axis pointers move together.
<LineChart group="traffic" ... />
<LineChart group="traffic" ... />