Skip to main content
/
/
/
/
Composed chart

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

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> ); }