Area Chart
Composable area chart with gradients, animations, and interactive tooltips
Installation
Dependencies
Examples
Gradient
"use client"
import AreaChart, { Area, Grid, XAxis, YAxis, ChartTooltip } from "@/components/vritti/area-chart"
const data = [
{ date: new Date(2024, 0, 1), revenue: 4200 },
{ date: new Date(2024, 1, 1), revenue: 5800 },
{ date: new Date(2024, 2, 1), revenue: 5100 },
{ date: new Date(2024, 3, 1), revenue: 7400 },
{ date: new Date(2024, 4, 1), revenue: 6200 },
{ date: new Date(2024, 5, 1), revenue: 8900 },
{ date: new Date(2024, 6, 1), revenue: 7800 },
{ date: new Date(2024, 7, 1), revenue: 9500 },
{ date: new Date(2024, 8, 1), revenue: 8600 },
{ date: new Date(2024, 9, 1), revenue: 11200 },
{ date: new Date(2024, 10, 1), revenue: 10400 },
{ date: new Date(2024, 11, 1), revenue: 12800 },
]
export function AreaChartGradientDemo() {
return (
<div className="w-full p-4">
<AreaChart data={data}>
<Grid horizontal />
<Area
dataKey="revenue"
fill="hsl(217, 91%, 60%)"
stroke="hsl(217, 91%, 60%)"
fillOpacity={0.6}
gradientToOpacity={0.05}
strokeWidth={2}
/>
<XAxis />
<YAxis />
<ChartTooltip />
</AreaChart>
</div>
)
}
Legend
Income
Expenses
"use client"
import AreaChart, { Area, Grid, XAxis, YAxis, ChartTooltip, Legend, LegendItem, LegendMarker, LegendLabel } from "@/components/vritti/area-chart"
const data = [
{ date: new Date(2024, 0, 1), income: 4800, expenses: 3200 },
{ date: new Date(2024, 1, 1), income: 5200, expenses: 3500 },
{ date: new Date(2024, 2, 1), income: 4900, expenses: 3800 },
{ date: new Date(2024, 3, 1), income: 6100, expenses: 3400 },
{ date: new Date(2024, 4, 1), income: 5700, expenses: 4100 },
{ date: new Date(2024, 5, 1), income: 6800, expenses: 3900 },
{ date: new Date(2024, 6, 1), income: 7200, expenses: 4300 },
{ date: new Date(2024, 7, 1), income: 6500, expenses: 4000 },
]
const legendItems = [
{ label: "Income", value: 0, color: "hsl(150, 60%, 50%)" },
{ label: "Expenses", value: 0, color: "hsl(0, 75%, 60%)" },
]
export function AreaChartLegendDemo() {
return (
<div className="w-full p-4">
<AreaChart data={data}>
<Grid horizontal />
<Area
dataKey="income"
fill="hsl(150, 60%, 50%)"
stroke="hsl(150, 60%, 50%)"
fillOpacity={0.3}
gradientToOpacity={0}
/>
<Area
dataKey="expenses"
fill="hsl(0, 75%, 60%)"
stroke="hsl(0, 75%, 60%)"
fillOpacity={0.3}
gradientToOpacity={0}
/>
<XAxis />
<YAxis />
<ChartTooltip />
</AreaChart>
<Legend className="flex-row justify-center gap-6" items={legendItems}>
<LegendItem className="flex items-center gap-2">
<LegendMarker />
<LegendLabel />
</LegendItem>
</Legend>
</div>
)
}
Multi
Desktop
Mobile
"use client"
import AreaChart, { Area, Grid, XAxis, YAxis, ChartTooltip, Legend, LegendItem, LegendMarker, LegendLabel } from "@/components/vritti/area-chart"
const data = [
{ date: new Date(2024, 0, 1), desktop: 186, mobile: 80 },
{ date: new Date(2024, 1, 1), desktop: 305, mobile: 200 },
{ date: new Date(2024, 2, 1), desktop: 237, mobile: 120 },
{ date: new Date(2024, 3, 1), desktop: 73, mobile: 190 },
{ date: new Date(2024, 4, 1), desktop: 209, mobile: 130 },
{ date: new Date(2024, 5, 1), desktop: 214, mobile: 140 },
]
const legendItems = [
{ label: "Desktop", value: 0, color: "hsl(217, 91%, 60%)" },
{ label: "Mobile", value: 0, color: "hsl(280, 87%, 65%)" },
]
export function AreaChartMultiDemo() {
return (
<div className="w-full p-4">
<AreaChart data={data}>
<Grid horizontal />
<Area dataKey="desktop" fill="hsl(217, 91%, 60%)" stroke="hsl(217, 91%, 60%)" />
<Area dataKey="mobile" fill="hsl(280, 87%, 65%)" stroke="hsl(280, 87%, 65%)" />
<XAxis />
<YAxis />
<ChartTooltip />
</AreaChart>
<Legend className="flex-row justify-center gap-6" items={legendItems}>
<LegendItem className="flex items-center gap-2">
<LegendMarker />
<LegendLabel />
</LegendItem>
</Legend>
</div>
)
}
Range
"use client"
import AreaChart, { Area, Grid, XAxis, YAxis, ChartTooltip } from "@/components/vritti/area-chart"
const data = [
{ date: new Date(2024, 0, 1), avg: 42, max: 58, min: 28 },
{ date: new Date(2024, 1, 1), avg: 48, max: 65, min: 34 },
{ date: new Date(2024, 2, 1), avg: 55, max: 72, min: 40 },
{ date: new Date(2024, 3, 1), avg: 62, max: 78, min: 48 },
{ date: new Date(2024, 4, 1), avg: 70, max: 88, min: 55 },
{ date: new Date(2024, 5, 1), avg: 78, max: 95, min: 64 },
{ date: new Date(2024, 6, 1), avg: 82, max: 98, min: 68 },
{ date: new Date(2024, 7, 1), avg: 80, max: 96, min: 66 },
{ date: new Date(2024, 8, 1), avg: 72, max: 86, min: 58 },
{ date: new Date(2024, 9, 1), avg: 60, max: 74, min: 46 },
{ date: new Date(2024, 10, 1), avg: 50, max: 64, min: 36 },
{ date: new Date(2024, 11, 1), avg: 44, max: 58, min: 30 },
]
export function AreaChartRangeDemo() {
return (
<div className="w-full p-4">
<AreaChart data={data}>
<Grid horizontal />
<Area
dataKey="max"
fill="hsl(217, 91%, 60%)"
stroke="hsl(217, 91%, 60%)"
fillOpacity={0.15}
gradientToOpacity={0.15}
strokeWidth={1}
showHighlight={false}
/>
<Area
dataKey="min"
fill="var(--chart-background, hsl(0, 0%, 100%))"
stroke="hsl(217, 91%, 60%)"
fillOpacity={1}
gradientToOpacity={1}
strokeWidth={1}
showHighlight={false}
/>
<Area
dataKey="avg"
fill="transparent"
stroke="hsl(217, 91%, 60%)"
fillOpacity={0}
strokeWidth={2}
/>
<XAxis />
<YAxis />
<ChartTooltip
rows={(point) => [
{ color: "hsl(217, 91%, 60%)", label: "Max", value: (point.max as number) ?? 0 },
{ color: "hsl(217, 71%, 50%)", label: "Avg", value: (point.avg as number) ?? 0 },
{ color: "hsl(217, 91%, 75%)", label: "Min", value: (point.min as number) ?? 0 },
]}
/>
</AreaChart>
</div>
)
}
Segment
2,400users
+1,200 (+100.0%)Feb 22 – Mar 23
"use client"
import { useCallback, useEffect, useState } from "react"
import AreaChart, {
Area,
ChartTooltip,
Grid,
SegmentBackground,
SegmentLineFrom,
SegmentLineTo,
XAxis,
useChart,
} from "@/components/vritti/area-chart"
const chartData = [
{ date: new Date(Date.now() - 29 * 24 * 60 * 60 * 1000), users: 1200 },
{ date: new Date(Date.now() - 27 * 24 * 60 * 60 * 1000), users: 1100 },
{ date: new Date(Date.now() - 25 * 24 * 60 * 60 * 1000), users: 1380 },
{ date: new Date(Date.now() - 23 * 24 * 60 * 60 * 1000), users: 1600 },
{ date: new Date(Date.now() - 21 * 24 * 60 * 60 * 1000), users: 1550 },
{ date: new Date(Date.now() - 19 * 24 * 60 * 60 * 1000), users: 1680 },
{ date: new Date(Date.now() - 17 * 24 * 60 * 60 * 1000), users: 1620 },
{ date: new Date(Date.now() - 15 * 24 * 60 * 60 * 1000), users: 1720 },
{ date: new Date(Date.now() - 13 * 24 * 60 * 60 * 1000), users: 1780 },
{ date: new Date(Date.now() - 11 * 24 * 60 * 60 * 1000), users: 1920 },
{ date: new Date(Date.now() - 9 * 24 * 60 * 60 * 1000), users: 1750 },
{ date: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), users: 2050 },
{ date: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000), users: 2100 },
{ date: new Date(Date.now() - 3 * 24 * 60 * 60 * 1000), users: 2050 },
{ date: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000), users: 2320 },
{ date: new Date(), users: 2400 },
]
interface SegmentStats {
value: number
change: number
changePct: number
startDate: Date
endDate: Date
}
function SegmentBridge({
onSegmentChange,
}: {
onSegmentChange: (stats: SegmentStats | null) => void
}) {
const { selection, data, xAccessor } = useChart()
useEffect(() => {
if (!selection?.active) {
onSegmentChange(null)
return
}
const startIdx = Math.max(0, selection.startIndex)
const endIdx = Math.min(data.length - 1, selection.endIndex)
if (startIdx >= endIdx) {
onSegmentChange(null)
return
}
const startPoint = data[startIdx] as { users?: number }
const endPoint = data[endIdx] as { users?: number }
if (!(startPoint && endPoint)) {
onSegmentChange(null)
return
}
const startVal = startPoint.users
const endVal = endPoint.users
if (typeof startVal !== "number" || typeof endVal !== "number") {
onSegmentChange(null)
return
}
onSegmentChange({
value: endVal,
change: endVal - startVal,
changePct: startVal !== 0 ? ((endVal - startVal) / startVal) * 100 : 0,
startDate: xAccessor(data[startIdx]),
endDate: xAccessor(data[endIdx]),
})
}, [selection, data, xAccessor, onSegmentChange])
return null
}
export function AreaChartSegmentDemo() {
const [stats, setStats] = useState<SegmentStats | null>(null)
const handleSegmentChange = useCallback(
(s: SegmentStats | null) => setStats(s),
[]
)
const firstVal = chartData[0]?.users ?? 0
const lastVal = chartData.at(-1)?.users ?? 0
const displayValue = stats?.value ?? lastVal
const displayChange = stats?.change ?? lastVal - firstVal
const displayPct =
stats?.changePct ??
(firstVal > 0 ? ((lastVal - firstVal) / firstVal) * 100 : 0)
const isPositive = displayChange >= 0
const startLabel = (
stats?.startDate ?? chartData[0]?.date
)?.toLocaleDateString("en-US", { month: "short", day: "numeric" })
const endLabel = (
stats?.endDate ?? chartData.at(-1)?.date
)?.toLocaleDateString("en-US", { month: "short", day: "numeric" })
return (
<div className="w-full p-4">
<div className="mb-4 space-y-1">
<div className="flex items-baseline gap-2">
<span className="font-semibold text-2xl tabular-nums">
{displayValue.toLocaleString()}
</span>
<span className="text-muted-foreground text-sm">users</span>
</div>
<div className="flex items-baseline gap-2 text-sm">
<span className={isPositive ? "text-emerald-500" : "text-red-500"}>
{isPositive ? "+" : ""}
{displayChange.toLocaleString()} ({isPositive ? "+" : ""}
{displayPct.toFixed(1)}%)
</span>
<span className="text-muted-foreground">
{startLabel} – {endLabel}
</span>
</div>
</div>
<AreaChart data={chartData}>
<Grid horizontal />
<Area
dataKey="users"
fill="var(--chart-line-primary)"
stroke="var(--chart-line-primary)"
/>
<SegmentBackground />
<SegmentLineFrom />
<SegmentLineTo />
<XAxis />
<ChartTooltip />
<SegmentBridge onSegmentChange={handleSegmentChange} />
</AreaChart>
</div>
)
}
Sparkline
"use client"
import AreaChart, { Area } from "@/components/vritti/area-chart"
const data = [
{ date: new Date(2024, 0, 1), value: 22 },
{ date: new Date(2024, 1, 1), value: 35 },
{ date: new Date(2024, 2, 1), value: 28 },
{ date: new Date(2024, 3, 1), value: 42 },
{ date: new Date(2024, 4, 1), value: 38 },
{ date: new Date(2024, 5, 1), value: 55 },
{ date: new Date(2024, 6, 1), value: 48 },
{ date: new Date(2024, 7, 1), value: 62 },
{ date: new Date(2024, 8, 1), value: 58 },
{ date: new Date(2024, 9, 1), value: 70 },
{ date: new Date(2024, 10, 1), value: 65 },
{ date: new Date(2024, 11, 1), value: 78 },
]
export function AreaChartSparklineDemo() {
return (
<div className="w-full p-4" style={{ maxWidth: 200 }}>
<AreaChart
data={data}
aspectRatio="3 / 1"
margin={{ top: 4, right: 4, bottom: 4, left: 4 }}
animationDuration={800}
>
<Area
dataKey="value"
fill="hsl(217, 91%, 60%)"
stroke="hsl(217, 91%, 60%)"
fillOpacity={0.3}
gradientToOpacity={0.05}
strokeWidth={1.5}
showHighlight={false}
/>
</AreaChart>
</div>
)
}
Stacked
"use client"
import AreaChart, { Area, Grid, XAxis, YAxis, ChartTooltip } from "@/components/vritti/area-chart"
const data = [
{ date: new Date(2024, 0, 1), organic: 120, direct: 90, referral: 45 },
{ date: new Date(2024, 1, 1), organic: 180, direct: 110, referral: 60 },
{ date: new Date(2024, 2, 1), organic: 150, direct: 130, referral: 75 },
{ date: new Date(2024, 3, 1), organic: 210, direct: 95, referral: 50 },
{ date: new Date(2024, 4, 1), organic: 190, direct: 140, referral: 85 },
{ date: new Date(2024, 5, 1), organic: 240, direct: 120, referral: 70 },
{ date: new Date(2024, 6, 1), organic: 220, direct: 160, referral: 90 },
{ date: new Date(2024, 7, 1), organic: 260, direct: 135, referral: 65 },
]
// Pre-compute stacked values so each area renders on top of the previous
const stackedData = data.map((d) => ({
...d,
referralStacked: d.referral,
directStacked: d.referral + d.direct,
organicStacked: d.referral + d.direct + d.organic,
}))
export function AreaChartStackedDemo() {
return (
<div className="w-full p-4">
<AreaChart data={stackedData}>
<Grid horizontal />
<Area
dataKey="organicStacked"
fill="hsl(217, 91%, 60%)"
stroke="hsl(217, 91%, 60%)"
fillOpacity={0.3}
showHighlight={false}
/>
<Area
dataKey="directStacked"
fill="hsl(280, 87%, 65%)"
stroke="hsl(280, 87%, 65%)"
fillOpacity={0.3}
showHighlight={false}
/>
<Area
dataKey="referralStacked"
fill="hsl(150, 60%, 50%)"
stroke="hsl(150, 60%, 50%)"
fillOpacity={0.3}
showHighlight={false}
/>
<XAxis />
<YAxis />
<ChartTooltip
rows={(point) => [
{ color: "hsl(217, 91%, 60%)", label: "Organic", value: (point.organic as number) ?? 0 },
{ color: "hsl(280, 87%, 65%)", label: "Direct", value: (point.direct as number) ?? 0 },
{ color: "hsl(150, 60%, 50%)", label: "Referral", value: (point.referral as number) ?? 0 },
]}
/>
</AreaChart>
</div>
)
}
Step
"use client"
import AreaChart, { Area, Grid, XAxis, YAxis, ChartTooltip } from "@/components/vritti/area-chart"
import { curveStepAfter } from "@visx/curve"
const data = [
{ date: new Date(2024, 0, 1), servers: 4 },
{ date: new Date(2024, 1, 1), servers: 4 },
{ date: new Date(2024, 2, 1), servers: 6 },
{ date: new Date(2024, 3, 1), servers: 6 },
{ date: new Date(2024, 4, 1), servers: 10 },
{ date: new Date(2024, 5, 1), servers: 10 },
{ date: new Date(2024, 6, 1), servers: 8 },
{ date: new Date(2024, 7, 1), servers: 12 },
{ date: new Date(2024, 8, 1), servers: 12 },
{ date: new Date(2024, 9, 1), servers: 15 },
{ date: new Date(2024, 10, 1), servers: 15 },
{ date: new Date(2024, 11, 1), servers: 18 },
]
export function AreaChartStepDemo() {
return (
<div className="w-full p-4">
<AreaChart data={data}>
<Grid horizontal />
<Area
dataKey="servers"
fill="hsl(280, 87%, 65%)"
stroke="hsl(280, 87%, 65%)"
fillOpacity={0.3}
gradientToOpacity={0.05}
curve={curveStepAfter}
/>
<XAxis />
<YAxis />
<ChartTooltip />
</AreaChart>
</div>
)
}
Tooltip
"use client"
import AreaChart, { Area, ChartTooltip, Grid, XAxis, YAxis } from "@/components/vritti/area-chart"
const data = [
{ date: new Date(2024, 0, 1), revenue: 12000, costs: 7500 },
{ date: new Date(2024, 1, 1), revenue: 15500, costs: 9200 },
{ date: new Date(2024, 2, 1), revenue: 11000, costs: 6800 },
{ date: new Date(2024, 3, 1), revenue: 18500, costs: 11100 },
{ date: new Date(2024, 4, 1), revenue: 16800, costs: 10400 },
{ date: new Date(2024, 5, 1), revenue: 21200, costs: 12800 },
]
export function AreaChartTooltipDemo() {
return (
<div className="w-full p-4">
<AreaChart data={data}>
<Grid horizontal />
<Area
dataKey="revenue"
fill="var(--chart-line-primary)"
stroke="var(--chart-line-primary)"
/>
<Area
dataKey="costs"
fill="var(--chart-line-secondary)"
stroke="var(--chart-line-secondary)"
/>
<XAxis />
<YAxis />
<ChartTooltip
rows={(point) => [
{
color: "var(--chart-line-primary)",
label: "Revenue",
value: `$${(point.revenue as number)?.toLocaleString() ?? 0}`,
},
{
color: "var(--chart-line-secondary)",
label: "Costs",
value: `$${(point.costs as number)?.toLocaleString() ?? 0}`,
},
]}
/>
</AreaChart>
</div>
)
}