본문 바로가기

React

React에서 Highcharts로 데이터 시각화하기

실무 대시보드에서 빠르게 신뢰성 있는 차트를 붙여야 한다면 Highcharts는 훌륭한 선택입니다. React에서 공식 래퍼를 사용하면 설정만으로 라인, 컬럼, 영역, 파이 차트를 쉽게 구현할 수 있습니다. 이 글은 설치부터 동적 업데이트, 반응형, 내보내기, 접근성, SSR(Next.js) 이슈까지 실무 팁 중심으로 정리합니다. 상업적 사용은 라이선스를 확인하시기 바랍니다.

1. 설치

# npm
yarn add highcharts highcharts-react-official
# or
yarn add highcharts highcharts-react-official

2. 가장 빠른 라인 차트 예제

import React, { useMemo } from 'react'
import Highcharts from 'highcharts'
import HighchartsReact from 'highcharts-react-official'

export default function LineChartBasic() {
  const options = useMemo(() => ({
    chart: { type: 'line', height: 300 },
    title: { text: '월간 가입자 추이' },
    xAxis: { categories: ['1월','2월','3월','4월','5월','6월'] },
    yAxis: { title: { text: '인원' } },
    series: [{ name: '가입자', data: [120, 200, 180, 260, 300, 420] }],
    credits: { enabled: false },
    legend: { enabled: true }
  }), [])

  return <HighchartsReact highcharts={Highcharts} options={options} />
}

옵션 객체는 useMemo로 메모이제이션하여 불필요한 재렌더와 차트 재생성을 줄입니다.

3. 옵션 최적화 포인트

실무에서 성능 이슈는 대부분 옵션/데이터 참조가 매 렌더마다 새로 생성되면서 발생합니다. 다음을 지킵니다.

  • options, series 데이터 배열에 useMemo를 사용합니다.
  • 이벤트 핸들러와 formatter는 화살표 함수 대신 function 키워드를 사용해 Highcharts의 this 컨텍스트를 보장합니다.
  • 고빈도 업데이트는 차트 인스턴스 API로 mutate하는 것이 효율적입니다.

4. 동적 데이터 업데이트: 상태 기반

import React, { useEffect, useMemo, useState } from 'react'
import Highcharts from 'highcharts'
import HighchartsReact from 'highcharts-react-official'

export default function LiveLineChart() {
  const [data, setData] = useState([5, 7, 3, 6, 9, 4])

  useEffect(() => {
    const id = setInterval(() => {
      setData(d => [...d.slice(1), Math.round(Math.random() * 10)])
    }, 1500)
    return () => clearInterval(id)
  }, [])

  const options = useMemo(() => ({
    chart: { animation: false, height: 280 },
    title: { text: '실시간 지표' },
    xAxis: { categories: Array.from({ length: data.length }, (_, i) => `${i + 1}`) },
    series: [{ type: 'line', name: '값', data }],
    credits: { enabled: false }
  }), [data])

  return <HighchartsReact highcharts={Highcharts} options={options} />
}

5. 동적 데이터 업데이트: 차트 인스턴스 API

고빈도 업데이트라면 차트 인스턴스를 직접 갱신하는 것이 비용이 낮습니다.

import React, { useEffect, useRef } from 'react'
import Highcharts from 'highcharts'
import HighchartsReact from 'highcharts-react-official'

export default function LiveWithInstance() {
  const chartRef = useRef(null)
  const options = {
    chart: { type: 'line', height: 260 },
    title: { text: '고빈도 업데이트' },
    series: [{ name: 'CPU', data: Array.from({ length: 20 }, () => 0) }],
    credits: { enabled: false }
  }

  useEffect(() => {
    const id = setInterval(() => {
      const chart = chartRef.current?.chart
      if (!chart) return
      const series = chart.series[0]
      series.addPoint(Math.round(Math.random() * 100), true, true) // shift=true
    }, 500)
    return () => clearInterval(id)
  }, [])

  return (
    <HighchartsReact
      highcharts={Highcharts}
      options={options}
      allowChartUpdate={false}
      ref={chartRef}
    />
  )
}

6. 컬럼 차트 예제

import React, { useMemo } from 'react'
import Highcharts from 'highcharts'
import HighchartsReact from 'highcharts-react-official'

export default function ColumnChart() {
  const options = useMemo(() => ({
    chart: { type: 'column', height: 300 },
    title: { text: '부서별 매출' },
    xAxis: { categories: ['영업','마케팅','개발','운영'] },
    yAxis: { title: { text: '백만원' } },
    plotOptions: { column: { borderRadius: 4, pointPadding: 0.1 } },
    series: [{ name: '2026', data: [420, 310, 560, 280], colorByPoint: true }],
    credits: { enabled: false }
  }), [])
  return <HighchartsReact highcharts={Highcharts} options={options} />
}

7. 반응형, 툴팁, 데이터 레이블

const options = {
  chart: { type: 'line' },
  title: { text: '모바일 대응' },
  tooltip: {
    shared: true,
    formatter: function () {
      if (this.points) {
        const header = `<b>${this.x}</b>`;
        const lines = this.points.map(p => `${p.series.name}: ${p.y}`)
        return [header, ...lines].join('<br/>')
      }
      return `${this.x}: ${this.y}`
    }
  },
  dataLabels: { enabled: false },
  responsive: {
    rules: [{
      condition: { maxWidth: 480 },
      chartOptions: {
        legend: { enabled: false },
        yAxis: { title: { text: '' } },
        chart: { height: 220 }
      }
    }]
  },
  credits: { enabled: false }
}

8. 내보내기와 접근성 활성화

import Highcharts from 'highcharts'
import exporting from 'highcharts/modules/exporting'
import accessibility from 'highcharts/modules/accessibility'

exporting(Highcharts)
accessibility(Highcharts)

const options = {
  exporting: { enabled: true }, // PNG, JPG, PDF, SVG
  accessibility: { enabled: true },
  credits: { enabled: false }
}

9. 다크 테마 적용

import Highcharts from 'highcharts'
import darkTheme from 'highcharts/themes/dark-unica'

darkTheme(Highcharts) // 앱 초기 구동 시 1회 적용

// 런타임 테마 토글은 setOptions로 색상을 재적용하는 방식이 안전합니다.
Highcharts.setOptions({
  chart: { backgroundColor: 'transparent' },
  title: { style: { color: '#fff' } },
  xAxis: { labels: { style: { color: '#ddd' } } },
  yAxis: { labels: { style: { color: '#ddd' } } }
})

10. Next.js에서 SSR 이슈 해결

Highcharts는 window에 의존하므로 SSR에서 에러가 납니다. 클라이언트 전용으로 동적 로드합니다.

'use client'
import React from 'react'
import dynamic from 'next/dynamic'
import Highcharts from 'highcharts'

const HighchartsReact = dynamic(() => import('highcharts-react-official'), { ssr: false })

export default function ChartInNext() {
  const options = { title: { text: 'Next.js + Highcharts' }, series: [{ data: [1,2,3] }], credits: { enabled: false } }
  return <HighchartsReact highcharts={Highcharts} options={options} />
}

11. 대용량 데이터 성능: Boost 모듈

import Highcharts from 'highcharts'
import boost from 'highcharts/modules/boost'

boost(Highcharts)

const options = {
  series: [{ type: 'line', data: bigArray, boostThreshold: 5000 }],
  plotOptions: { series: { turboThreshold: 0 } },
  credits: { enabled: false }
}

12. API로 서버 데이터 연결

import React, { useEffect, useMemo, useState } from 'react'
import Highcharts from 'highcharts'
import HighchartsReact from 'highcharts-react-official'

export default function FetchChart() {
  const [seriesData, setSeriesData] = useState([])

  useEffect(() => {
    let alive = true
    fetch('/api/metrics')
      .then(r => r.json())
      .then(json => { if (alive) setSeriesData(json.points) })
      .catch(console.error)
    return () => { alive = false }
  }, [])

  const options = useMemo(() => ({
    title: { text: 'API 지표' },
    series: [{ name: 'metric', data: seriesData }],
    credits: { enabled: false }
  }), [seriesData])

  return <HighchartsReact highcharts={Highcharts} options={options} />
}

13. 체크리스트: 실무 팁

  • 옵션/데이터는 useMemo로 고정합니다.
  • 고빈도 업데이트는 allowChartUpdate=false + chart API 사용을 고려합니다.
  • 툴팁 formatter, 데이터 라벨 등은 function() { ... }로 작성합니다.
  • Boost 모듈로 5k+ 포인트를 처리합니다.
  • Next.js는 dynamic import로 SSR을 비활성화합니다.
  • 차트 컨테이너가 display:none이면 크기 계산 실패하니, 탭 전환 시 reflow 처리를 합니다.
  • credits는 비활성화하여 UI를 정돈합니다.

14. 자주 겪는 오류

  • ReferenceError: window is not defined → SSR 비활성화 필요.
  • 차트가 계속 리렌더 → options/series 참조가 매번 새로 생성됨. useMemo로 해결.
  • 툴팁에서 this가 undefined → formatter 화살표 함수 사용 지양.
  • 빈 컨테이너 높이 0 → 부모 요소에 명시적 height 또는 차트 height 설정.

Highcharts는 옵션만 알면 바로 실전에 투입할 수 있습니다. 위 스니펫을 베이스로 라인업을 만들고, 모듈(Exporting, Accessibility, Boost)을 필요할 때 켠다는 전략이 가장 빠릅니다.