Composed chart
A composed chart is a chart that combines different chart types, such as lines, bars and areas in the same chart. This is a great way to visualize different types of data in the same chart.
Composed charts are a little more complex, and you may need to use different axes for different data series. In the example below, we have a composed line chart and bar chart, where the bar chart uses the left y-axis and the line chart uses an invisible right y-axis. You may want to show both y-axes, but for this example we thought it looked better with custom callout values on the line chart.
Composed chart example
Code
Also see <CustomTooltip>
Tooltip helper component
Also see legendFormatter()
Formatting helper for <Legend>
import { useEffect, useRef, useState } from "react";
import {
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
CartesianGrid,
Bar,
ComposedChart,
Line,
LabelList,
Legend,
} from "recharts";
import CustomTooltip from "../CustomToolTip";
import legendFormatter from "../legendFormatter";
const satisfactionData = [
{ respondents: 190, average: 4.4, year: 2021 },
{ respondents: 212, average: 4.3, year: 2022 },
{ respondents: 219, average: 4.2, year: 2023 },
{ respondents: 222, average: 4.3, year: 2024 },
];
function Callout({ x, y, value }: { x?: number; y?: number; value?: number }) {
const textRef = useRef<SVGTextElement>(null);
const [rectDimensions, setRectDimensions] = useState({
width: 0,
height: 0,
});
useEffect(() => {
if (textRef.current) {
const bbox = textRef.current.getBBox();
setRectDimensions({ width: bbox.width + 16, height: bbox.height + 8 });
}
}, [value]);
const { width, height } = rectDimensions;
if (!x || !y || !value) {
return null;
}
return (
<g transform={`translate(${x}, ${y - height / 2})`}>
<rect
x={-width / 2}
y={-height / 2 - 4}
width={width}
height={height}
fill="var(--bfc-base)"
opacity={0.5}
rx={4} // Rounded corners
ry={4} // Rounded corners
/>
<text
ref={textRef}
x={0}
y={-4}
fill="var(--bfc-base-c-1)"
fontSize={14}
fontWeight={600}
textAnchor="middle"
dominantBaseline="middle"
>
{value}
</text>
</g>
);
}
export default function ComposedChartExample() {
return (
<ResponsiveContainer width="100%" height="100%">
<ComposedChart
width={500}
height={400}
data={satisfactionData}
margin={{ left: -20 }}
>
<defs>
<linearGradient id="colorRespondents" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#A175FF" stopOpacity={1} />
<stop offset="95%" stopColor="#614699" stopOpacity={1} />
</linearGradient>
</defs>
<CartesianGrid
strokeDasharray="5 5"
vertical={false}
stroke="var(--bfc-base-dimmed)"
/>
<XAxis
axisLine={false}
tickLine={false}
dataKey="year"
tick={{ fill: "var(--bfc-base-c-2)" }}
dy={8}
/>
<YAxis
yAxisId="respYAxis"
dataKey="respondents"
axisLine={false}
tickLine={false}
tick={{ fill: "var(--bfc-base-c-2)" }}
/>
<YAxis hide yAxisId="avgYAxis" dataKey="average" domain={[1, 6]} />
<Tooltip content={<CustomTooltip />} />
<Legend align="right" formatter={legendFormatter} />
<Bar
yAxisId="respYAxis"
name="Respondents"
dataKey="respondents"
fill="url(#colorRespondents)"
radius={8}
/>
<Line
yAxisId="avgYAxis"
name="Average score"
dataKey="average"
dot={false}
type="monotone"
stroke="var(--bfc-base-c)"
strokeWidth={2}
>
<LabelList content={<Callout />} position="top" />
</Line>
</ComposedChart>
</ResponsiveContainer>
);
}
import { useEffect, useRef, useState } from "react";
import {
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
CartesianGrid,
Bar,
ComposedChart,
Line,
LabelList,
Legend,
} from "recharts";
import CustomTooltip from "../CustomToolTip";
import legendFormatter from "../legendFormatter";
const satisfactionData = [
{ respondents: 190, average: 4.4, year: 2021 },
{ respondents: 212, average: 4.3, year: 2022 },
{ respondents: 219, average: 4.2, year: 2023 },
{ respondents: 222, average: 4.3, year: 2024 },
];
function Callout({ x, y, value }: { x?: number; y?: number; value?: number }) {
const textRef = useRef<SVGTextElement>(null);
const [rectDimensions, setRectDimensions] = useState({
width: 0,
height: 0,
});
useEffect(() => {
if (textRef.current) {
const bbox = textRef.current.getBBox();
setRectDimensions({ width: bbox.width + 16, height: bbox.height + 8 });
}
}, [value]);
const { width, height } = rectDimensions;
if (!x || !y || !value) {
return null;
}
return (
<g transform={`translate(${x}, ${y - height / 2})`}>
<rect
x={-width / 2}
y={-height / 2 - 4}
width={width}
height={height}
fill="var(--bfc-base)"
opacity={0.5}
rx={4} // Rounded corners
ry={4} // Rounded corners
/>
<text
ref={textRef}
x={0}
y={-4}
fill="var(--bfc-base-c-1)"
fontSize={14}
fontWeight={600}
textAnchor="middle"
dominantBaseline="middle"
>
{value}
</text>
</g>
);
}
export default function ComposedChartExample() {
return (
<ResponsiveContainer width="100%" height="100%">
<ComposedChart
width={500}
height={400}
data={satisfactionData}
margin={{ left: -20 }}
>
<defs>
<linearGradient id="colorRespondents" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#A175FF" stopOpacity={1} />
<stop offset="95%" stopColor="#614699" stopOpacity={1} />
</linearGradient>
</defs>
<CartesianGrid
strokeDasharray="5 5"
vertical={false}
stroke="var(--bfc-base-dimmed)"
/>
<XAxis
axisLine={false}
tickLine={false}
dataKey="year"
tick={{ fill: "var(--bfc-base-c-2)" }}
dy={8}
/>
<YAxis
yAxisId="respYAxis"
dataKey="respondents"
axisLine={false}
tickLine={false}
tick={{ fill: "var(--bfc-base-c-2)" }}
/>
<YAxis hide yAxisId="avgYAxis" dataKey="average" domain={[1, 6]} />
<Tooltip content={<CustomTooltip />} />
<Legend align="right" formatter={legendFormatter} />
<Bar
yAxisId="respYAxis"
name="Respondents"
dataKey="respondents"
fill="url(#colorRespondents)"
radius={8}
/>
<Line
yAxisId="avgYAxis"
name="Average score"
dataKey="average"
dot={false}
type="monotone"
stroke="var(--bfc-base-c)"
strokeWidth={2}
>
<LabelList content={<Callout />} position="top" />
</Line>
</ComposedChart>
</ResponsiveContainer>
);
}