Area Chart
Displays an area chart with gradient fill and a smooth line.
Preview not available
Installation
pnpm dlx shadcn@latest add https://ui.vllnt.com/r/area-chart.jsonbash
Code
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
function getPathCoordinates(data: Datum[], width: number, height: number, strokeWidth: number) {
if (data.length === 0) return { area: '', line: '' }
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)
const points = 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 }
})
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},${height} ${line} L${points.at(-1)?.x ?? 0},${height}Z`
return { area, line }
}
export const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>(
(
{
className,
color = 'currentColor',
data,
gradientId = 'area-chart-gradient',
height = DEFAULT_HEIGHT,
strokeWidth = DEFAULT_STROKE_WIDTH,
width = DEFAULT_WIDTH,
...props
},
reference,
) => {
const { area, line } = React.useMemo(
() => getPathCoordinates(data, width, height, strokeWidth),
[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'
typescript