Diverging bar chart
Draws bars either side of a centre line: positive against negative values, or stacked agree / disagree responses around a neutral midpoint.
- Use when
- 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 when
- 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).
Preview
Austin's population grew 21.3% between 2010 and 2020
Population change, 2010 to 2020
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 },
];
export function Example() {
return (
<DivergingBarChart
data={data}
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."
/>
);
}{
"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."
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Composition
Used in
No molecule or template lists diverging bar chart in its composes. It still fits any slot that takes a component.
Installation
$ pnpm dlx shadcn@latest add @insightcharts/diverging-bar-chartThe 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 { DivergingBarChart } from "insightcharts";Examples
Likert scale around a neutral midpoint
73% of staff agree their manager gives useful feedback
Share of employees responding to each statement
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 },
];
export function Example() {
return (
<DivergingBarChart
data={data}
x="statement"
y={["sd", "d", "n", "a", "sa"]}
names={{
sd: "Strongly disagree",
d: "Disagree",
n: "Neither",
a: "Agree",
sa: "Strongly agree",
}}
format={"{value}%"}
height={300}
title="73% of staff agree their manager gives useful feedback"
subtitle="Share of employees responding to each statement"
note="Illustrative figures."
/>
);
}{
"type": "diverging-bar-chart",
"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
}
],
"x": "statement",
"y": ["sd", "d", "n", "a", "sa"],
"names": {
"sd": "Strongly disagree",
"d": "Disagree",
"n": "Neither",
"a": "Agree",
"sa": "Strongly agree"
},
"format": "{value}%",
"height": 300,
"title": "73% of staff agree their manager gives useful feedback",
"subtitle": "Share of employees responding to each statement",
"note": "Illustrative figures."
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Two opposed groups, long format
Women aged 75 and over make up 4.6% of the population
Share of total population by age group and sex
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 },
{ age: "15 to 29", sex: "Men", share: 9.3 },
{ age: "15 to 29", sex: "Women", share: 8.8 },
{ age: "0 to 14", sex: "Men", share: 8.7 },
{ age: "0 to 14", sex: "Women", share: 8.3 },
];
export function Example() {
return (
<DivergingBarChart
data={data}
x="age"
y="share"
series="sex"
sort="none"
format={"{value}%"}
height={280}
title="Women aged 75 and over make up 4.6% of the population"
subtitle="Share of total population by age group and sex"
note="Illustrative figures."
/>
);
}{
"type": "diverging-bar-chart",
"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 },
{ "age": "15 to 29", "sex": "Men", "share": 9.3 },
{ "age": "15 to 29", "sex": "Women", "share": 8.8 },
{ "age": "0 to 14", "sex": "Men", "share": 8.7 },
{ "age": "0 to 14", "sex": "Women", "share": 8.3 }
],
"x": "age",
"y": "share",
"series": "sex",
"sort": "none",
"format": "{value}%",
"height": 280,
"title": "Women aged 75 and over make up 4.6% of the population",
"subtitle": "Share of total population by age group and sex",
"note": "Illustrative figures."
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Vertical over time, good and bad
Payroll employment fell by 71,000 in August 2024
Monthly change in payroll employment, thousands
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 },
{ month: "2024-09", jobs: 44 },
{ month: "2024-10", jobs: -28 },
{ month: "2024-11", jobs: 161 },
{ month: "2024-12", jobs: 203 },
];
export function Example() {
return (
<DivergingBarChart
data={data}
x="month"
y="jobs"
colors="sentiment"
height={280}
title="Payroll employment fell by 71,000 in August 2024"
subtitle="Monthly change in payroll employment, thousands"
note="Illustrative figures."
/>
);
}{
"type": "diverging-bar-chart",
"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 },
{ "month": "2024-09", "jobs": 44 },
{ "month": "2024-10", "jobs": -28 },
{ "month": "2024-11", "jobs": 161 },
{ "month": "2024-12", "jobs": 203 }
],
"x": "month",
"y": "jobs",
"colors": "sentiment",
"height": 280,
"title": "Payroll employment fell by 71,000 in August 2024",
"subtitle": "Monthly change in payroll employment, thousands",
"note": "Illustrative figures."
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Stress: 14 long statements, one highlighted, normalised
42% of respondents support capping rents by law
Share of respondents, normalised to 100% per statement
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,
},
{
statement: "Working from home should be a legal right",
oppose: 296,
unsure: 146,
support: 418,
},
{
statement: "Short-haul flights should be banned where a train exists",
oppose: 243,
unsure: 163,
support: 489,
},
{
statement: "The voting age should be lowered to 16",
oppose: 190,
unsure: 100,
support: 230,
},
{
statement: "Social media should be barred for children under 15",
oppose: 137,
unsure: 117,
support: 301,
},
{ statement: "Rents should be capped by law", oppose: 384, unsure: 134, support: 372 },
{
statement: "A four-day working week should become standard",
oppose: 331,
unsure: 151,
support: 443,
},
];
export function Example() {
return (
<DivergingBarChart
data={data}
x="statement"
y={["oppose", "unsure", "support"]}
names={{ oppose: "Oppose", unsure: "Not sure", support: "Support" }}
percent
highlight="Rents should be capped by law"
title="42% of respondents support capping rents by law"
subtitle="Share of respondents, normalised to 100% per statement"
note="Synthetic data for layout testing."
/>
);
}{
"type": "diverging-bar-chart",
"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
},
{
"statement": "Working from home should be a legal right",
"oppose": 296,
"unsure": 146,
"support": 418
},
{
"statement": "Short-haul flights should be banned where a train exists",
"oppose": 243,
"unsure": 163,
"support": 489
},
{
"statement": "The voting age should be lowered to 16",
"oppose": 190,
"unsure": 100,
"support": 230
},
{
"statement": "Social media should be barred for children under 15",
"oppose": 137,
"unsure": 117,
"support": 301
},
{
"statement": "Rents should be capped by law",
"oppose": 384,
"unsure": 134,
"support": 372
},
{
"statement": "A four-day working week should become standard",
"oppose": 331,
"unsure": 151,
"support": 443
}
],
"x": "statement",
"y": ["oppose", "unsure", "support"],
"names": { "oppose": "Oppose", "unsure": "Not sure", "support": "Support" },
"percent": true,
"highlight": "Rents should be capped by law",
"title": "42% of respondents support capping rents by law",
"subtitle": "Share of respondents, normalised to 100% per statement",
"note": "Synthetic data for layout testing."
}Render with import { Insight } from "insightcharts" and <Insight spec={spec} />. Nested specs resolve by name; nothing else to import.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| xrequired | string | Category field. | |
| yrequired | 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<string, string> | Display names for series keys. |
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>. |