Rounded Bar corners
Recharts by default renders square bars. There is more than one way to round Bar's corners.
Round all corners
Pass radius={10} prop to a Bar to round all corners equally. This creates a uniform rounded rectangle. Large radius makes the bars look more like pills.
import {
Bar,
BarChart,
BarProps,
BarShapeProps,
CartesianGrid,
emptyTheme,
RechartsThemeProvider,
Rectangle,
Tooltip,
XAxis,
YAxis,
} from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';
// #region Data and helper functions
type TimelineDataType = {
name: string;
type: string;
outcome: 'success' | 'error' | 'pending';
firstCycle: [number, number];
secondCycle: [number, number];
};
const data: Array<TimelineDataType> = [
{
name: 'TEST 1',
type: 'TR',
outcome: 'success',
firstCycle: [0, 3],
secondCycle: [4.11, 14.11],
},
{
name: 'TEST 2',
type: 'MT',
outcome: 'error',
firstCycle: [0, 1.5],
secondCycle: [9.11, 12.11],
},
{
name: 'TEST 3',
type: 'MT',
outcome: 'success',
firstCycle: [3, 5.37],
secondCycle: [8.74, 14.48],
},
{
name: 'TEST 4',
type: 'MT',
outcome: 'error',
firstCycle: [5.37, 7.87],
secondCycle: [9.61, 16.98],
},
{
name: 'TEST 5',
type: 'MT',
outcome: 'success',
firstCycle: [4.87, 8.24],
secondCycle: [10.74, 17.35],
},
{
name: 'TEST 6',
type: 'MT',
outcome: 'success',
firstCycle: [3.24, 5.74],
secondCycle: [8.61, 17.85],
},
{
name: 'TEST 7',
type: 'MT',
outcome: 'success',
firstCycle: [2.74, 9.11],
secondCycle: [9.74, 18.22],
},
{
name: 'TEST 8',
type: 'MT',
outcome: 'pending',
firstCycle: [9.11, 10.61],
secondCycle: [12.11, 19.72],
},
];
const getBarColor = (outcome: TimelineDataType['outcome']) => {
switch (outcome) {
case 'success':
return 'blue';
case 'error':
return 'red';
default:
return 'grey';
}
};
const CustomFillRectangle = (props: BarShapeProps) => {
// @ts-expect-error props.outcome is injected from the data array which Recharts doesn't know about
const { outcome, isActive } = props;
const barColor = getBarColor(outcome);
return (
<Rectangle {...props} stroke={isActive ? 'orange' : barColor} strokeWidth={isActive ? 3 : 1} fill={barColor} />
);
};
const MyBar = (props: BarProps) => {
return <Bar {...props} stackId="a" radius={25} activeBar shape={CustomFillRectangle} />;
};
// #endregion
export default function TimelineExample({ defaultIndex }: { defaultIndex?: number }) {
return (
<RechartsThemeProvider value={emptyTheme}>
<BarChart
layout="vertical"
style={{ width: '100%', maxWidth: '700px', maxHeight: '70vh', aspectRatio: 1.618 }}
responsive
data={data}
margin={{ bottom: 20 }}
>
<CartesianGrid strokeDasharray="2 2" />
<Tooltip shared={false} defaultIndex={defaultIndex} />
<XAxis type="number" height={50} label={{ value: 'Time (s)', position: 'insideBottomRight' }} />
<YAxis
type="category"
dataKey="name"
width="auto"
label={{
value: 'Test run',
angle: -90,
position: 'insideTopLeft',
textAnchor: 'end',
}}
/>
<MyBar dataKey="firstCycle" />
<MyBar dataKey="secondCycle" />
<RechartsDevtools />
</BarChart>
</RechartsThemeProvider>
);
}Different radius for different corners
radius prop also accepts an array of four numbers. This way you can define specific radius for each corner.
import {
Bar,
BarChart,
Legend,
LegendPayload,
Tooltip,
XAxis,
YAxis,
RenderableText,
TooltipValueType,
} from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';
// #region Data and helper functions
/**
* Data source: https://www.populationpyramid.net/world/2024/
* CSV columns: Age,M,F
*/
const rawData = `
100+,110838,476160
95-99,1141691,3389124
90-94,6038458,13078242
85-89,18342182,31348041
80-84,37166893,53013079
75-79,65570812,83217973
70-74,103998992,124048996
65-69,138182244,154357035
60-64,170525048,180992721
55-59,206686596,212285997
50-54,231342779,232097236
45-49,240153677,236696232
40-44,270991534,263180352
35-39,301744799,289424003
30-34,310384416,294303405
25-29,308889349,291429439
20-24,318912554,300510028
15-19,335882343,315258559
10-14,353666705,331681954
5-9,351991008,332121131
0-4,331889289,315450649
`
.trim()
.split('\n')
.map(line => {
const [age, m, f] = line.split(',');
return { age, male: Number(m), female: Number(f) };
});
const totalPopulation: number = rawData.reduce((sum, entry) => sum + entry.male + entry.female, 0);
const percentageData = rawData.map(entry => {
return {
age: entry.age,
male: (entry.male / totalPopulation) * -100, // Negative for left side
female: (entry.female / totalPopulation) * 100,
};
});
function formatPercent(val: RenderableText | TooltipValueType): string {
return `${Math.abs(Number(val)).toFixed(1)}%`;
}
function itemSorter(item: LegendPayload): number {
// Make legend order match the chart bar order
return item.value === 'Male' ? 0 : 1;
}
// #endregion
export default function PopulationPyramidExample({ defaultIndex }: { defaultIndex?: number }) {
return (
<BarChart
data={percentageData}
layout="vertical"
style={{ width: '100%', maxWidth: '700px', maxHeight: '70vh', aspectRatio: 1 }}
responsive
stackOffset="sign"
barCategoryGap={1}
>
<XAxis
type="number"
domain={[-10, 10]}
tickFormatter={formatPercent}
height={50}
label={{
value: '% of total population',
position: 'insideBottom',
}}
/>
<YAxis
width="auto"
type="category"
dataKey="age"
name="Age group"
label={{
value: 'Age group',
angle: -90,
position: 'insideLeft',
offset: 10,
}}
/>
<Bar
stackId="age"
name="Female"
dataKey="female"
fill="#ed7485"
stroke="none"
radius={[0, 5, 5, 0]}
label={{ position: 'right', formatter: formatPercent }}
/>
<Bar
stackId="age"
name="Male"
dataKey="male"
fill="#6ea1c7"
stroke="none"
radius={[0, 5, 5, 0]}
label={{ position: 'right', formatter: formatPercent }}
/>
<Tooltip formatter={formatPercent} defaultIndex={defaultIndex} />
<Legend itemSorter={itemSorter} verticalAlign="top" align="right" />
<RechartsDevtools />
</BarChart>
);
}Round a bar stack
In a stacked bar chart, you will run into some complications:
- Some data points may be smaller than the radius
- Some data points may be omitted
To fix these, you may want to use BarStack component. This allows you to set radius of the whole stack.
import { BarChart, XAxis, YAxis, Tooltip, Bar, BarStack, TooltipIndex, lightTheme } from 'recharts';
import { RechartsDevtools } from '@recharts/devtools';
// #region Sample data
const rangedStackedBarData = [
{ name: 'A', value1: [100, 200], value2: [200, 250], value3: [250, 300] },
{ name: 'B', value1: [120, 180], value2: [130, 230], value3: [170, 270] },
{ name: 'C', value1: [90, 160], value2: [210, 310], value3: [340, 440] },
{ name: 'D', value1: [80, 140], value2: [140, 200], value3: [200, 220] },
];
// #endregion
const RangedStackedBarChart = ({
isAnimationActive = true,
defaultIndex,
}: {
isAnimationActive?: boolean;
defaultIndex?: TooltipIndex;
}) => (
<BarChart
style={{ width: '100%', maxWidth: '700px', maxHeight: '70vh', aspectRatio: 1.618 }}
responsive
data={rangedStackedBarData}
id="recharts-ranged-stacked-bar-chart"
margin={{
top: 20,
right: 20,
bottom: 20,
left: 20,
}}
>
<XAxis dataKey="name" />
<YAxis width="auto" />
<Tooltip defaultIndex={defaultIndex} />
<BarStack radius={25}>
<Bar
dataKey="value1"
{...lightTheme.graphicalItems[0]}
maxBarSize={50}
isAnimationActive={isAnimationActive}
activeBar={{ fillOpacity: 1 }}
/>
<Bar
dataKey="value2"
{...lightTheme.graphicalItems[1]}
maxBarSize={50}
isAnimationActive={isAnimationActive}
activeBar={{ fillOpacity: 1 }}
/>
<Bar
dataKey="value3"
{...lightTheme.graphicalItems[2]}
maxBarSize={50}
isAnimationActive={isAnimationActive}
activeBar={{ fillOpacity: 1 }}
/>
</BarStack>
<RechartsDevtools />
</BarChart>
);
export default RangedStackedBarChart;