← Components/Charts

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

More from Charts

'use client'
import { useState } from 'react'

const DATA = [
  { label: 'Jan', value: 4200 }, { label: 'Feb', value: 5800 }, { label: 'Mar', value: 3900 },
  { label: 'Apr', value: 7200 }, { label: 'May', value: 6100 }, { label: 'Jun', value: 8400 }
BarChart
Charts