- framer-motion: ^10.18.0
AreaChart is a cartesian chart variant that allows for easy visualization of stacked data.
Basic Example
<AreaChart height={{ base: 150, tablet: 200, desktop: 250 }} inset={{ top: 8, bottom: 8, left: 0, right: 0 }} series={[ { id: 'prices', data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 2, 68, 20, 21, 58], }, ]} type="gradient" showLines showYAxis yAxis={{ showGrid: true, width: 32, }} />
Simple
<AreaChart height={{ base: 150, tablet: 200, desktop: 250 }} inset={0} series={[ { id: 'prices', data: [10, 22, 29, 45, 98, 45, 22, 52, 21, 2, 68, 20, 21, 58], }, ]} type="gradient" showLines />
Stacking
You can use the stacked prop to stack all areas on top of each other. You can also use the stackId prop on a series to create different stack groups. See CartesianChart for more details.
<AreaChart showLines stacked curve="natural" height={{ base: 150, tablet: 200, desktop: 250 }} inset={0} series={[ { id: 'currentRewards', data: [ 100, 150, 200, 280, 380, 500, 650, 820, 1020, 1250, 1510, 1800, 2120, 2470, 2850, 3260, 3700, 4170, ], color: 'var(--color-fg)', }, { id: 'potentialRewards', data: [ 150, 220, 300, 400, 520, 660, 820, 1000, 1200, 1420, 1660, 1920, 2200, 2500, 2820, 3160, 3520, 3900, ], color: 'var(--color-fgPositive)', LineComponent: DottedLine, }, ]} AreaComponent={(props) => <DottedArea {...props} peakOpacity={0.4} baselineOpacity={0.4} />} type="dotted" />
Negative Values
When an area chart contains negative values, the baseline automatically adjusts to zero instead of the bottom of the chart. The area fills from the data line to the zero baseline, properly showing both positive and negative regions.
<AreaChart height={{ base: 150, tablet: 200, desktop: 250 }} inset={0} series={[ { id: 'pageViews', data: [24, 13, -98, 39, 48, 38, 43], }, ]} AreaComponent={(props) => <SolidArea {...props} opacity={0.4} />} showLines showYAxis yAxis={{ showGrid: true, }} />
Area Styles
You can have different area styles for each series.
<AreaChart height={{ base: 150, tablet: 200, desktop: 250 }} inset={0} series={[ { id: 'visitors', data: [450, 520, 480, 600, 750, 680, 590], label: 'Weekly Visitors', color: '#fb4d3d', type: 'dotted', }, { id: 'repeatVisitors', data: [250, 200, 150, 140, 100, 80, 50], label: 'Weekly Repeat Visitors', color: '#16a34a', }, { id: 'signups', data: [45, 62, 55, 250, 380, 400, 450], label: 'Weekly Signups', color: '#2563eb', type: 'gradient', }, ]} />
Animations
You can configure chart transitions using the transition prop.
Customized Transitions
You can pass in a custom spring based transition to your AreaChart for a custom transition.
function AnimatedStackedAreas() { const dataCount = 20; const minYValue = 5000; const maxDataOffset = 15000; const minStepOffset = 2500; const maxStepOffset = 10000; const updateInterval = 500; const seriesSpacing = 2000; const myTransition = { type: 'spring', stiffness: 700, damping: 20 }; const seriesConfig = [ { id: 'red', label: 'Red', color: 'rgb(var(--red40))' }, { id: 'orange', label: 'Orange', color: 'rgb(var(--orange40))' }, { id: 'yellow', label: 'Yellow', color: 'rgb(var(--yellow40))' }, { id: 'green', label: 'Green', color: 'rgb(var(--green40))' }, { id: 'blue', label: 'Blue', color: 'rgb(var(--blue40))' }, { id: 'indigo', label: 'Indigo', color: 'rgb(var(--indigo40))' }, { id: 'purple', label: 'Purple', color: 'rgb(var(--purple40))' }, ]; const domainLimit = maxDataOffset + seriesConfig.length * seriesSpacing; function generateNextValue(previousValue) { const range = maxStepOffset - minStepOffset; const offset = Math.random() * range + minStepOffset; let direction; if (previousValue >= maxDataOffset) { direction = -1; } else if (previousValue <= minYValue) { direction = 1; } else { direction = Math.random() < 0.5 ? -1 : 1; } let newValue = previousValue + offset * direction; newValue = Math.max(minYValue, Math.min(maxDataOffset, newValue)); return newValue; } function generateInitialData() { const data = []; let previousValue = minYValue + Math.random() * (maxDataOffset - minYValue); data.push(previousValue); for (let i = 1; i < dataCount; i++) { const newValue = generateNextValue(previousValue); data.push(newValue); previousValue = newValue; } return data; } const MemoizedDottedArea = memo((props) => ( <DottedArea {...props} baselineOpacity={1} peakOpacity={1} /> )); function AnimatedChart() { const [data, setData] = useState(generateInitialData); useEffect(() => { const intervalId = setInterval(() => { setData((currentData) => { const lastValue = currentData[currentData.length - 1] ?? 0; const newValue = generateNextValue(lastValue); return [...currentData.slice(1), newValue]; }); }, updateInterval); return () => clearInterval(intervalId); }, []); const series = seriesConfig.map((config, index) => ({ id: config.id, label: config.label, color: config.color, data: index === 0 ? data : Array(dataCount).fill(seriesSpacing), })); return ( <AreaChart overflow="visible" stacked height={{ base: 200, tablet: 250, desktop: 300 }} series={series} type="dotted" showLines AreaComponent={MemoizedDottedArea} transition={myTransition} inset={0} showYAxis yAxis={{ showGrid: true, width: 0, tickLabelFormatter: () => '', domain: { min: 0, max: domainLimit }, }} /> ); } return <AnimatedChart />; }
Disable Animations
You can also disable animations by setting the animate prop to false.
function AnimatedStackedAreas() { const dataCount = 20; const minYValue = 5000; const maxDataOffset = 15000; const minStepOffset = 2500; const maxStepOffset = 10000; const updateInterval = 500; const seriesSpacing = 2000; const myTransition = { type: 'spring', stiffness: 700, damping: 20 }; const seriesConfig = [ { id: 'red', label: 'Red', color: 'rgb(var(--red40))' }, { id: 'orange', label: 'Orange', color: 'rgb(var(--orange40))' }, { id: 'yellow', label: 'Yellow', color: 'rgb(var(--yellow40))' }, { id: 'green', label: 'Green', color: 'rgb(var(--green40))' }, { id: 'blue', label: 'Blue', color: 'rgb(var(--blue40))' }, { id: 'indigo', label: 'Indigo', color: 'rgb(var(--indigo40))' }, { id: 'purple', label: 'Purple', color: 'rgb(var(--purple40))' }, ]; const domainLimit = maxDataOffset + seriesConfig.length * seriesSpacing; function generateNextValue(previousValue) { const range = maxStepOffset - minStepOffset; const offset = Math.random() * range + minStepOffset; let direction; if (previousValue >= maxDataOffset) { direction = -1; } else if (previousValue <= minYValue) { direction = 1; } else { direction = Math.random() < 0.5 ? -1 : 1; } let newValue = previousValue + offset * direction; newValue = Math.max(minYValue, Math.min(maxDataOffset, newValue)); return newValue; } function generateInitialData() { const data = []; let previousValue = minYValue + Math.random() * (maxDataOffset - minYValue); data.push(previousValue); for (let i = 1; i < dataCount; i++) { const newValue = generateNextValue(previousValue); data.push(newValue); previousValue = newValue; } return data; } const MemoizedDottedArea = memo((props) => ( <DottedArea {...props} baselineOpacity={1} peakOpacity={1} /> )); function AnimatedChart() { const [data, setData] = useState(generateInitialData); useEffect(() => { const intervalId = setInterval(() => { setData((currentData) => { const lastValue = currentData[currentData.length - 1] ?? 0; const newValue = generateNextValue(lastValue); return [...currentData.slice(1), newValue]; }); }, updateInterval); return () => clearInterval(intervalId); }, []); const series = seriesConfig.map((config, index) => ({ id: config.id, label: config.label, color: config.color, // First series gets animated data, others get constant height data: index === 0 ? data : Array(dataCount).fill(seriesSpacing), })); return ( <AreaChart animate={false} overflow="visible" stacked height={{ base: 200, tablet: 250, desktop: 300 }} series={series} type="dotted" showLines AreaComponent={MemoizedDottedArea} inset={0} showYAxis yAxis={{ showGrid: true, width: 0, tickLabelFormatter: () => '', domain: { min: 0, max: domainLimit }, }} /> ); } return <AnimatedChart />; }
Gradients
You can use the gradient prop on series or Area components to enable gradients.
Each stop requires an offset, which is based on the data within the x/y scale and color, with an optional opacity (defaults to 1).
Values in between stops will be interpolated smoothly using srgb color space.
function ContinuousGradient() { const spectrumColors = [ 'blue', 'green', 'orange', 'yellow', 'gray', 'indigo', 'pink', 'purple', 'red', 'teal', 'chartreuse', ]; const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58]; const [currentSpectrumColor, setCurrentSpectrumColor] = useState('pink'); return ( <VStack gap={2}> <HStack gap={1} justifyContent="flex-end" flexWrap="wrap"> {spectrumColors.map((color) => ( <Pressable key={color} onClick={() => setCurrentSpectrumColor(color)} accessibilityLabel={`Select ${color}`} style={{ backgroundColor: `rgb(var(--${color}20))`, border: `2px solid rgb(var(--${color}50))`, outlineColor: `rgb(var(--${color}80))`, outline: currentSpectrumColor === color ? `2px solid rgb(var(--${color}80))` : undefined, }} width={{ base: 16, tablet: 24, desktop: 24 }} height={{ base: 16, tablet: 24, desktop: 24 }} borderRadius={1000} /> ))} </HStack> <AreaChart enableScrubbing height={{ base: 150, tablet: 200, desktop: 250 }} series={[ { id: 'prices', data: data, gradient: { stops: ({ min, max }) => [ // Allows a function which accepts min/max or direct array { offset: min, color: `rgb(var(--${currentSpectrumColor}80))` }, { offset: max, color: `rgb(var(--${currentSpectrumColor}20))` }, ], }, }, ]} showYAxis yAxis={{ showGrid: true, }} > <Scrubber /> </AreaChart> </VStack> ); }
Discrete
You can set multiple stops at the same offset to create a discrete gradient.
function DiscreteGradient() { const spectrumColors = [ 'blue', 'green', 'orange', 'yellow', 'gray', 'indigo', 'pink', 'purple', 'red', 'teal', 'chartreuse', ]; const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58]; const [currentSpectrumColor, setCurrentSpectrumColor] = useState('pink'); return ( <VStack gap={2}> <HStack gap={1} justifyContent="flex-end" flexWrap="wrap"> {spectrumColors.map((color) => ( <Pressable key={color} onClick={() => setCurrentSpectrumColor(color)} accessibilityLabel={`Select ${color}`} style={{ backgroundColor: `rgb(var(--${color}20))`, border: `2px solid rgb(var(--${color}50))`, outlineColor: `rgb(var(--${color}80))`, outline: currentSpectrumColor === color ? `2px solid rgb(var(--${color}80))` : undefined, }} width={{ base: 16, tablet: 24, desktop: 24 }} height={{ base: 16, tablet: 24, desktop: 24 }} borderRadius={1000} /> ))} </HStack> <AreaChart enableScrubbing height={{ base: 150, tablet: 200, desktop: 250 }} series={[ { id: 'prices', data: data, gradient: { stops: ({ min, max }) => [ { offset: min, color: `rgb(var(--${currentSpectrumColor}80))` }, { offset: min + (max - min) / 3, color: `rgb(var(--${currentSpectrumColor}80))` }, { offset: min + (max - min) / 3, color: `rgb(var(--${currentSpectrumColor}50))` }, { offset: min + ((max - min) / 3) * 2, color: `rgb(var(--${currentSpectrumColor}50))`, }, { offset: min + ((max - min) / 3) * 2, color: `rgb(var(--${currentSpectrumColor}20))`, }, { offset: max, color: `rgb(var(--${currentSpectrumColor}20))` }, ], }, }, ]} showLines strokeWidth={4} showYAxis yAxis={{ showGrid: true, }} fillOpacity={0.5} > <Scrubber /> </AreaChart> </VStack> ); }
Axes
By default, gradients will be applied to the y-axis. You can apply a gradient to the x-axis by setting axis to x in the gradient definition.
function XAxisGradient() { const spectrumColors = [ 'blue', 'green', 'orange', 'yellow', 'gray', 'indigo', 'pink', 'purple', 'red', 'teal', 'chartreuse', ]; const data = [10, 22, 29, 45, 98, 45, 22, 52, 21, 4, 68, 20, 21, 58]; const [currentSpectrumColor, setCurrentSpectrumColor] = useState('pink'); return ( <VStack gap={2}> <HStack gap={1} justifyContent="flex-end" flexWrap="wrap"> {spectrumColors.map((color) => ( <Pressable key={color} onClick={() => setCurrentSpectrumColor(color)} accessibilityLabel={`Select ${color}`} style={{ backgroundColor: `rgb(var(--${color}20))`, border: `2px solid rgb(var(--${color}50))`, outlineColor: `rgb(var(--${color}80))`, outline: currentSpectrumColor === color ? `2px solid rgb(var(--${color}80))` : undefined, }} width={{ base: 16, tablet: 24, desktop: 24 }} height={{ base: 16, tablet: 24, desktop: 24 }} borderRadius={1000} /> ))} </HStack> <AreaChart enableScrubbing height={{ base: 150, tablet: 200, desktop: 250 }} series={[ { id: 'prices', data: data, gradient: { axis: 'x', stops: ({ min, max }) => [ { offset: min, color: `rgb(var(--${currentSpectrumColor}80))`, opacity: 0 }, { offset: max, color: `rgb(var(--${currentSpectrumColor}20))`, opacity: 1 }, ], }, }, ]} showYAxis yAxis={{ showGrid: true, }} > <Scrubber /> </AreaChart> </VStack> ); }