LineChart
linechart-1779378817321.tsx
'use client'
import { useState, useRef } from 'react'
const DATASETS = {
Users: [120, 185, 140, 210, 190, 280, 260, 340, 310, 420, 390, 510],
Revenue: [3200, 4100, 3800, 5200, 4800, 6100, 5700, 7400, 6900, 8800, 8200, 10500],
}
const MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']
export default function LineChart() {
const [activeSet, setActiveSet] = useState<keyof typeof DATASETS>('Users')
const [hx, setHx] = useState<number | null>(null)
const svgRef = useRef<SVGSVGElement>(null)
const W = 520, H = 220, PL = 48, PR = 16, PT = 16, PB = 36
const data = DATASETS[activeSet]
const max = Math.max(...data) * 1.1
const min = Math.min(...data) * 0.9
const cW = W - PL - PR, cH = H - PT - PB
const px = (i: number) => PL + (i / (data.length - 1)) * cW
const py = (v: number) => PT + cH - ((v - min) / (max - min)) * cH
const points = data.map((v, i) => [px(i), py(v)] as [number, number])
const line = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p[0]},${p[1]}`).join(' ')
const area = line + ` L${px(data.length - 1)},${PT + cH} L${px(0)},${PT + cH} Z`
const hovIdx = hx !== null ? Math.round((hx - PL) / cW * (data.length - 1)) : null
const clampedIdx = hovIdx !== null ? Math.max(0, Math.min(data.length - 1, hovIdx)) : null
return (
<div style={{ background: '#0D0D0D', padding: 24, borderRadius: 16, border: '1px solid rgba(255,255,255,0.06)' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
<div>
<div style={{ color: '#F5F5F0', fontSize: 15, fontWeight: 600 }}>Growth Metrics</div>
<div style={{ color: 'rgba(255,255,255,0.4)', fontSize: 12 }}>Last 12 months</div>
</div>
<div style={{ display: 'flex', gap: 6 }}>
{(Object.keys(DATASETS) as (keyof typeof DATASETS)[]).map(k => (
<button key={k} onClick={() => setActiveSet(k)} style={{
background: activeSet === k ? '#C9A84C' : 'rgba(255,255,255,0.06)',
color: activeSet === k ? '#0A0A0A' : 'rgba(255,255,255,0.5)',
border: 'none', borderRadius: 6, padding: '5px 12px', fontSize: 12, fontWeight: 600, cursor: 'pointer',
}}>{k}</button>
))}
</div>
</div>
<svg ref={svgRef} width="100%" viewBox={`0 0 ${W} ${H}`} style={{ overflow: 'visible', cursor: 'crosshair' }}
onMouseMove={e => {
const rect = svgRef.current?.getBoundingClientRect()
if (rect) setHx((e.clientX - rect.left) / rect.width * W)
}}
onMouseLeave={() => setHx(null)}>
<defs>
<linearGradient id="areaGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stopColor="#C9A84C" stopOpacity="0.2" />
<stop offset="100%" stopColor="#C9A84C" stopOpacity="0" />
</linearGradient>
</defs>
{[0, 0.25, 0.5, 0.75, 1].map(p => {
const val = min + (max - min) * p
const y = PT + cH * (1 - p)
return <line key={p} x1={PL} y1={y} x2={W - PR} y2={y} stroke="rgba(255,255,255,0.06)" strokeWidth={1} />
})}
<path d={area} fill="url(#areaGrad)" />
<path d={line} fill="none" stroke="#C9A84C" strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round" />
{clampedIdx !== null && (
<>
<line x1={points[clampedIdx][0]} y1={PT} x2={points[clampedIdx][0]} y2={PT + cH} stroke="rgba(255,255,255,0.15)" strokeWidth={1} strokeDasharray="4 4" />
<circle cx={points[clampedIdx][0]} cy={points[clampedIdx][1]} r={5} fill="#C9A84C" stroke="#0A0A0A" strokeWidth={2} />
<rect x={points[clampedIdx][0] - 40} y={points[clampedIdx][1] - 36} width={80} height={26} fill="#1a1a1a" stroke="rgba(255,255,255,0.12)" rx={6} />
<text x={points[clampedIdx][0]} y={points[clampedIdx][1] - 17} textAnchor="middle" fontSize={11} fill="#F5F5F0" fontWeight="600">
{activeSet === 'Revenue' ? `$${(data[clampedIdx]/1000).toFixed(1)}k` : data[clampedIdx]}
</text>
</>
)}
{MONTHS.map((m, i) => (
<text key={m} x={px(i)} y={H - 6} textAnchor="middle" fontSize={10} fill="rgba(255,255,255,0.3)">{m}</text>
))}
</svg>
</div>
)
}Component info
CategoryCharts
Frameworkreact
TierFREE
Views0
Copies0
About
SVG line chart with animated path, area fill, dot markers, and interactive crosshair