Area Chart
Renders an area chart for data visualization. Install Area Chart from the VLLNT UI registry with the shadcn CLI.
Renders an area chart for data visualization. Part of the data family in VLLNT UI, it ships as a machine-readable registry entry — copy the source directly into your app with the shadcn CLI and own it, no runtime dependency on a component library.
Installation
Add Area Chart to your project with the shadcn CLI. The source lands in your codebase, ready to adapt:
pnpm dlx shadcn@latest add https://ui.vllnt.ai/r/area-chart.jsonSource
"use client";
import * as React from "react";
import { cn } from "../../lib/utils";
type Datum = {
label?: string;
value: number;
};
export type AreaChartProps = {
color?: string;
data: Datum[];
gradientId?: string;
height?: number;
strokeWidth?: number;
width?: number;
} & React.HTMLAttributes<HTMLDivElement>;
const DEFAULT_WIDTH = 340;
const DEFAULT_HEIGHT = 150;
const DEFAULT_STROKE_WIDTH = 2;
type ChartDimensions = { height: number; strokeWidth: number; width: number };
function computePoints(data: Datum[], dimensions: ChartDimensions) {
const { height, strokeWidth, width } = dimensions;
const values = data.map((point) => point.value);
const minValue = Math.min(...values);
const maxValue = Math.max(...values);
const range = maxValue - minValue || 1;
const safeWidth = Math.max(width - strokeWidth * 2, 0);
const safeHeight = Math.max(height - strokeWidth * 2, 0);
return data.map((point, index) => {
const x =
data.length === 1
? strokeWidth + safeWidth / 2
: strokeWidth + (index / (data.length - 1)) * safeWidth;
const ratio = (point.value - minValue) / range;
const y = safeHeight - ratio * safeHeight + strokeWidth;
return { x, y };
});
}
function getPathCoordinates(data: Datum[], dimensions: ChartDimensions) {
if (data.length === 0) return { area: "", line: "" };
const points = computePoints(data, dimensions);
const line = points
.map((point, index) => `${index === 0 ? "M" : "L"}${point.x},${point.y}`)
.join(" ");
const area =
points.length === 0
? ""
: `M${points[0]?.x ?? 0},${dimensions.height} ${line} L${points.at(-1)?.x ?? 0},${dimensions.height}Z`;
return { area, line };
}
export const AreaChart = ({
className,
color = "currentColor",
data,
gradientId = "area-chart-gradient",
height = DEFAULT_HEIGHT,
ref: reference,
strokeWidth = DEFAULT_STROKE_WIDTH,
width = DEFAULT_WIDTH,
...props
}: AreaChartProps & { ref?: React.Ref<HTMLDivElement> }) => {
const { area, line } = React.useMemo(
() => getPathCoordinates(data, { height, strokeWidth, width }),
[data, width, height, strokeWidth],
);
if (!line) return null;
return (
<div
className={cn(
"rounded-2xl border border-border bg-background/40 p-3",
className,
)}
ref={reference}
{...props}
>
<svg
aria-label="Area chart"
className="h-full w-full"
height={height}
role="img"
viewBox={`0 0 ${width} ${height}`}
width={width}
>
<defs>
<linearGradient id={gradientId} x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stopColor={color} stopOpacity="0.5" />
<stop offset="100%" stopColor={color} stopOpacity="0" />
</linearGradient>
</defs>
<path
d={area}
fill={`url(#${gradientId})`}
stroke="none"
vectorEffect="non-scaling-stroke"
/>
<path
d={line}
fill="none"
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={strokeWidth}
vectorEffect="non-scaling-stroke"
/>
</svg>
</div>
);
};
AreaChart.displayName = "AreaChart";
Stories
Explore every variant and state in the interactive Storybook: