Bar Chart
Compare categorical values using proportional bar lengths with vertical or horizontal orientation.
Source
LLM docs
Package
npm install @plocks/chartsExamples
8
Properties
59
Playground
Basics
Basic BarChart usage with a title.
Loading demo…
import { BarChart } from '@plocks/charts';
import { QUARTERLY_REVENUE } from './data';
export function Demo() {
return (
<BarChart
title="Quarterly revenue"
subtitle="North America"
h={260}
data={QUARTERLY_REVENUE}
barSpacing={0.25}
barBorderRadius={6}
valueFormatter={(value) => `$${(value / 1000).toFixed(0)}k`}
xAxis={{ show: true }}
yAxis={{
show: true,
labelFormatter: (value) => `$${(value / 1000).toFixed(0)}k`,
}}
grid={{ show: true, style: 'dotted' }}
tooltip={{
show: true,
formatter: (point) => `${point.category}: $${point.value.toLocaleString()}`,
}}
enableCrosshair
liveTooltip
/>
);
}Diverging from zero
Key settings
colorScale={{ type: 'diverging', midpoint: 0, colors: [low, high] }}colors each bar by its value. The scale's domain always includes zero, where bars start.Both arms are symmetric around the midpoint, so a $40k loss and a $40k gain carry the same intensity.
Loading demo…
import { BarChart } from '@plocks/charts';
import { NET_CASH_FLOW } from './data';
export function Demo() {
return (
<BarChart
title="Monthly net cash flow"
subtitle="Diverging from zero: red months lost money, blue months made it"
h={340}
data={NET_CASH_FLOW}
barSpacing={0.24}
legend={{ show: false }}
colorScale={{ type: 'diverging', midpoint: 0, colors: ['#e34948', '#2a78d6'] }}
yAxis={{
show: true,
title: 'Net cash flow (USD thousands)',
labelFormatter: (value) => `$${value}k`,
}}
xAxis={{ show: true }}
grid={{ show: true }}
valueFormatter={(value) => `${value < 0 ? '-' : '+'}$${Math.abs(value)}k`}
/>
);
}Gradient bars
Key settings
barColortakes a gradient as well as a color:{ angle, stops, extent }.extent: 'plot'lays one gradient across the whole plot, so each bar shows the slice behind it. The default,extent: 'mark', gives every bar the full gradient.
Loading demo…
import { BarChart } from '@plocks/charts';
import { HOURLY_REQUESTS } from './data';
export function Demo() {
return (
<BarChart
title="API requests by hour"
subtitle="One gradient spans the plot, so busier hours reach the deeper end"
h={320}
data={HOURLY_REQUESTS}
barSpacing={0.28}
legend={{ show: false }}
barBorderRadius={6}
barColor={{
angle: 90,
extent: 'plot',
stops: [
{ offset: 0, color: '#12805a' },
{ offset: 1, color: '#8fdcbf' },
],
}}
yAxis={{
show: true,
title: 'Requests (millions)',
labelFormatter: (value) => `${value}M`,
}}
xAxis={{ show: true }}
grid={{ show: true }}
valueFormatter={(value) => `${value}M requests`}
/>
);
}Coloring with a function
Key settings
colorScalealso accepts a function,({ datum, series, seriesIndex, categoryIndex }) => color. Here each region is green when it beat its goal and red when it missed. Returnundefinedto fall back tobarColor.Green and red are status colors, so the value labels state every variance: color is never the only cue.
Loading demo…
import { BarChart } from '@plocks/charts';
import { REGIONAL_REVENUE } from './data';
// Status colors: the value labels spell out each variance, so color is never the only cue.
const BEAT_PLAN = '#0ca30c';
const MISSED_PLAN = '#d03b3b';
const formatMillions = (value: number) => `$${value.toFixed(2)}M`;
const formatVariance = (value: number, datum: (typeof REGIONAL_REVENUE)[number]) => {
const goal = datum.data?.goal ?? 0;
const diff = value - goal;
const direction = diff >= 0 ? '+' : '-';
return `${direction}$${Math.abs(diff).toFixed(2)}M vs goal`;
};
export function Demo() {
return (
<BarChart
title="Quarterly Revenue by Region"
subtitle="Q3 actuals with variance to plan"
h={420}
data={REGIONAL_REVENUE}
barSpacing={0.32}
colorScale={({ datum }) => (datum.value >= (datum.data?.goal ?? 0) ? BEAT_PLAN : MISSED_PLAN)}
legend={{ show: false }}
valueFormatter={(value, datum) => {
const goal = datum.data?.goal;
return goal != null
? `${formatMillions(value)} actual (goal ${formatMillions(goal)})`
: formatMillions(value);
}}
valueLabel={{
formatter: (value, datum) => formatVariance(value, datum as (typeof REGIONAL_REVENUE)[number]),
color: '#1f2937',
fontSize: 12,
fontWeight: '600',
offset: 12,
}}
yAxis={{
show: true,
title: 'Revenue (USD millions)',
titleFontSize: 12,
labelFormatter: (value) => `$${value.toFixed(1)}M`,
}}
xAxis={{ show: true }}
grid={{ show: true }}
tooltip={{
formatter: (datum) => {
const goal = datum.data?.goal ?? 0;
const diff = datum.value - goal;
const pct = goal ? (diff / goal) * 100 : 0;
const direction = diff >= 0 ? '+' : '-';
const varianceValue = `${direction}${formatMillions(Math.abs(diff))}`;
const variancePct = `${direction}${Math.abs(pct).toFixed(1)}%`;
return [
`${datum.category}`,
`Actual: ${formatMillions(datum.value)}`,
`Goal: ${formatMillions(goal)}`,
`Variance: ${varianceValue} (${variancePct})`,
datum.data?.focus ? `Focus: ${datum.data.focus}` : undefined,
]
.filter(Boolean)
.join('\n');
},
}}
/>
);
}Feature Adoption By Tier
Loading demo…
import { BarChart } from '@plocks/charts';
import { FEATURE_ADOPTION } from './data';
const formatAccounts = (value: number) => `${value.toLocaleString()} accounts`;
export function Demo() {
return (
<BarChart
title="Feature adoption by customer tier"
subtitle="Accounts live on the experimentation canvas within 45 days"
h={400}
data={FEATURE_ADOPTION}
barSpacing={0.26}
barBorderRadius={12}
legend={{ show: false }}
valueFormatter={(value, datum) => {
const segmentTotal = datum.data?.accounts ?? value;
const rate = datum.data?.adoptionRate ?? value / segmentTotal;
const percentage = `${Math.round((rate ?? 0) * 100)}% adoption`;
return `${formatAccounts(value)} (${percentage})`;
}}
valueLabel={{
position: 'inside',
color: '#ffffff',
fontSize: 13,
fontWeight: '600',
formatter: (value) => value.toLocaleString(),
}}
yAxis={{
show: true,
title: 'Activated accounts',
titleFontSize: 12,
labelFormatter: (value) => value.toLocaleString(),
}}
xAxis={{ show: true }}
grid={{ show: true }}
tooltip={{
formatter: (datum) => {
const accounts = datum.data?.accounts ?? datum.value;
const adoptionRate = datum.data?.adoptionRate ?? datum.value / accounts;
return [
datum.category,
`Activated: ${formatAccounts(datum.value)}`,
`Account base: ${accounts.toLocaleString()}`,
`Adoption rate: ${(adoptionRate * 100).toFixed(1)}%`,
].join('\n');
},
}}
/>
);
}Marketing Spend Multi Touch
Loading demo…
import { BarChart } from '@plocks/charts';
import { CAMPAIGN_SPEND, TOTAL_SPEND } from './data';
const formatSpend = (value: number) => `$${value.toLocaleString()}k`;
export function Demo() {
return (
<BarChart
title="Marketing spend by channel"
subtitle="Multi-touch journey campaign mix"
h={420}
data={CAMPAIGN_SPEND}
barSpacing={0.28}
legend={{ show: false }}
valueFormatter={(value) => `${formatSpend(value)} invested`}
valueLabel={{
color: '#1f2937',
fontSize: 12,
offset: 12,
formatter: (value) => {
const share = TOTAL_SPEND ? (value / TOTAL_SPEND) * 100 : 0;
return `${share.toFixed(1)}% of spend`;
},
}}
yAxis={{
show: true,
title: 'Investment (USD thousands)',
titleFontSize: 12,
labelFormatter: (value) => `$${value.toFixed(0)}k`,
}}
xAxis={{ show: true }}
grid={{ show: true }}
tooltip={{
formatter: (datum) => {
const share = TOTAL_SPEND ? (datum.value / TOTAL_SPEND) * 100 : 0;
return [
datum.category,
`Spend: ${formatSpend(datum.value)}`,
`Share: ${share.toFixed(1)}% of program`,
datum.data?.objective ? `Objective: ${datum.data.objective}` : undefined,
]
.filter(Boolean)
.join('\n');
},
}}
/>
);
}New Hire Recruiting Cycles
Loading demo…
import { BarChart } from '@plocks/charts';
import { RECRUITING_PROGRESS } from './data';
const formatDelta = (value: number, datum: (typeof RECRUITING_PROGRESS)[number]) => {
const previous = datum.data?.previous ?? 0;
const delta = value - previous;
if (delta === 0) return 'No change vs last cycle';
const sign = delta > 0 ? '+' : '-';
return `${sign}${Math.abs(delta)} vs last cycle`;
};
export function Demo() {
return (
<BarChart
title="New hires secured this recruiting cycle"
subtitle="Compared with winter intake"
h={440}
orientation="horizontal"
data={RECRUITING_PROGRESS}
barSpacing={0.25}
legend={{ show: false }}
valueFormatter={(value) => `${value} hires`}
valueLabel={{
formatter: (value, datum) => formatDelta(value, datum as (typeof RECRUITING_PROGRESS)[number]),
color: '#1f2937',
fontSize: 12,
offset: 10,
}}
xAxis={{
title: 'Hires confirmed',
labelFormatter: (value) => `${Math.round(value)}`,
}}
yAxis={{ show: true }}
grid={{ show: true }}
tooltip={{
formatter: (datum) => {
const previous = datum.data?.previous ?? 0;
const delta = datum.value - previous;
const direction = delta >= 0 ? '+' : '-';
return [
`${datum.category}`,
`This cycle: ${datum.value} hires`,
`Last cycle: ${previous} hires`,
`Delta: ${direction}${Math.abs(delta)}`,
datum.data?.priority ? `Focus: ${datum.data.priority}` : undefined,
datum.data?.openRoles != null ? `Open roles: ${datum.data.openRoles}` : undefined,
]
.filter(Boolean)
.join('\n');
},
}}
/>
);
}Sla Compliance By Team
Loading demo…
import { BarChart } from '@plocks/charts';
import { SLA_COMPLIANCE } from './data';
const formatPercent = (value: number) => `${value.toFixed(1)}%`;
export function Demo() {
return (
<BarChart
title="SLA compliance by response team"
subtitle="Rolling 12-month attainment"
h={420}
orientation="horizontal"
data={SLA_COMPLIANCE}
barSpacing={0.3}
legend={{ show: false }}
valueFormatter={(value) => `${formatPercent(value)} SLA met`}
valueLabel={{
color: '#111827',
fontSize: 12,
offset: 12,
formatter: (value) => formatPercent(value),
}}
xAxis={{
title: 'Tickets meeting SLA target',
labelFormatter: (value) => `${Math.round(value)}%`,
}}
yAxis={{ show: true }}
grid={{ show: true }}
tooltip={{
formatter: (datum) => {
const last = datum.data?.lastQuarter ?? datum.value;
const delta = datum.value - last;
const direction = delta >= 0 ? '+' : '-';
return [
datum.category,
`Current: ${formatPercent(datum.value)}`,
`Last quarter: ${formatPercent(last)}`,
`Change: ${direction}${Math.abs(delta).toFixed(1)} pts`,
].join('\n');
},
}}
/>
);
}Related charts
Further reading
React Native design for iOS, Android, and Web.
Quick Links
Resources