Line Chart
Displays a shaded line chart with gradients for trends.
Preview not available
Installation
pnpm dlx shadcn@latest add https://ui.vllnt.com/r/line-chart.jsonbash
Code
/* eslint-disable max-lines-per-function */
import * as React from 'react'
import { cn } from '../../lib/utils'
type Datum = {
label?: string
value: number
}
export type LineChartProps = {
color?: string
data: Datum[]
gradientId?: string
height?: number
strokeWidth?: number
width?: number
} & React.HTMLAttributes<HTMLDivElement>
const DEFAULT_WIDTH = 320
const DEFAULT_HEIGHT = 140
const DEFAULT_STROKE_WIDTH = 2
function buildCoordinates(data: Datum[], width: number, height: number, strokeWidth: number) {
if (data.length === 0) {
return []
}
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 }
})
}
export const LineChart = React.forwardRef<HTMLDivElement, LineChartProps>(
(
{
className,
color = 'currentColor',
data,
gradientId = 'line-chart-gradient',
height = DEFAULT_HEIGHT,
strokeWidth = DEFAULT_STROKE_WIDTH,
width = DEFAULT_WIDTH,
...props
},
reference,
) => {
const canvasData = React.useMemo(
() => buildCoordinates(data, width, height, strokeWidth),
[data, width, height, strokeWidth],
)
if (canvasData.length === 0) {
return null
}
const linePath = canvasData
.map((point, index) => `${index === 0 ? 'M' : 'L'}${point.x},${point.y}`)
.join(' ')
const areaPath =
canvasData.length === 0
? ''
: `M${canvasData[0]?.x ?? 0},${height} ${linePath} L${canvasData.at(-1)?.x ?? 0},${height}Z`
return (
<div
className={cn('rounded-lg border border-border bg-background/40 p-3', className)}
ref={reference}
{...props}
>
<svg
aria-label="Line 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.45" />
<stop offset="100%" stopColor={color} stopOpacity="0" />
</linearGradient>
</defs>
<path
d={areaPath}
fill={`url(#${gradientId})`}
stroke="none"
vectorEffect="non-scaling-stroke"
/>
<path
d={linePath}
fill="none"
stroke={color}
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={strokeWidth}
vectorEffect="non-scaling-stroke"
/>
</svg>
</div>
)
},
)
LineChart.displayName = 'LineChart'
typescript