React에서 ApexCharts로 차트 그리기

2024년 09월 09일

TOC

React

데이터 시각화란 데이터를 그래프, 차트, 지도, 인포그래픽 등 시각적인 형태로 표현하여 복잡한 정보나 패턴을 쉽게 이해할 수 있도록 하는 방법이다. 대량의 데이터를 시각적으로 표현함으로써 숨겨진 경향, 상관관계, 이상치 등을 더 직관적으로 파악할 수 있게 하며, 이를 통해 의사 결정 과정에서 통찰력을 제공한다. 차트는 데이터를 시각적으로 표현하여 정보를 효과적으로 전달하는 데 매우 유용하다. React와 같은 프레임워크에서 다양한 차트를 쉽게 구현할 수 있는 라이브러리 중 대표적으로 ApexCharts가 있다.

ApexCharts.js

apexchart1
apexchart1

ApexCharts를 적용하여 개발한 프로젝트 - 과거 기상 데이터 차트 분석 웹 서비스

ApexCharts는 다양한 유형의 차트를 지원하는 JavaScript 차트 라이브러리이다. 데이터 시각화라고 하면 대표적인 것으로 D3.js가 있지만 사실 난이도가 굉장히 높아 진압장벽이 느껴진다. 그에 비해 React에서 사용할 수 있는 차트 라이브러리 중에서 ApexCharts는 다양한 종류의 차트를 간편하게 설정할 수 있다.

라이브러리를 둘러보다 보면 커스텀 측면에서 자유도가 낮은 경우도 있고 데이터를 직관적으로 풀기 어렵게 설계된 경우도 많다. 하지만 ApexCharts는 전체적인 코드 설정이 비교적 간편하고, 특히 옵션 설정에서 많은 기능을 지원하기 때문에 다양한 커스터마이징이 가능하다. 위에 나온 프로젝트는 ApexCharts를 통해 선택한 날씨 데이터를 시각화하여 볼 수 있게 제작되었다. 데이터 시각화에 메인으로 사용되었던 라인, 원형 차트를 비롯하여, 사용하고 싶었으나 사용 데이터에 부합하지 않아 폐기되었던 레이더 차트까지 간단한 예제를 통해 다루어 보았다.

React에서 ApexCharts 사용하기

React에서 ApexCharts를 사용하려면, 먼저 필요한 라이브러리를 설치해야 한다.

$ npm install react-apexcharts

설치 후, 차트를 구현할 컴포넌트를 만들어 차트를 렌더링하면 된다. 다음은 ApexCharts의 라인 차트(Line Chart), 원형 차트(Pie Chart), 레이더 차트(Radar Chart) 예제이다.

라인 차트(Line Chart)

라인 차트는 데이터를 선형으로 연결하여 데이터의 흐름을 파악하는 데 유용하다. 아래는 두 개의 데이터 시리즈를 보여주는 간단한 라인 차트이다.

import Chart from "react-apexcharts"

// 차트에 표시될 데이터 설정
const chartData = [
  {
    name: "data A",
    data: [
      { x: 1, y: 0.6 },
      { x: 2, y: 7.9 },
      ...{ x: 9, y: 21.7 },
      { x: 10, y: 16.5 },
    ],
  },
  {
    name: "data B",
    data: [
      { x: 1, y: 8.1 },
      { x: 2, y: 6.3 },
      ...{ x: 9, y: 25.7 },
      { x: 10, y: 15.2 },
    ],
  },
  {
    name: "data C",
    data: [
      { x: 1, y: 4.8 },
      { x: 2, y: 3.2 },
      ...{ x: 9, y: 12.8 },
      { x: 10, y: 7.6 },
    ],
  },
  {
    name: "data D",
    data: [
      { x: 1, y: 5.7 },
      { x: 2, y: 9.9 },
      ...{ x: 9, y: 22.9 },
      { x: 10, y: 11.4 },
    ],
  },
]

// 차트 옵션 설정
const chartOptions = {
  chart: {
    type: "line", // 차트 유형을 라인 차트로 설정
    toolbar: {
      show: true, // 툴바 표시 여부
      tools: {
        zoom: true, // 확대 툴
        zoomin: true, // 확대 버튼
        zoomout: true, // 축소 버튼
        download: true, // 이미지 다운로드 버튼
        pan: true, // 팬 이동 가능 여부
        reset: true, // 차트 초기화 버튼
        selection: true, // 선택 도구 활성화 여부
      },
    },
  },
  title: {
    text: "Line Chart", // 차트 타이틀 텍스트
    align: "center", // 타이틀 위치
    margin: 5, // 타이틀과 차트 간의 여백
    offsetY: 0, // Y축 기준으로 타이틀 위치 조정
    style: {
      fontSize: "25px", // 타이틀 텍스트 크기
      color: "#000", // 타이틀 텍스트 색상
    },
  },
  dataLabels: {
    enabled: false, // 데이터 라벨 표시 여부
    offsetX: -3, // 라벨의 X축 위치를 조정
    offsetY: 0, // 라벨의 Y축 위치를 조정
  },
  stroke: {
    curve: "smooth", // 곡선형 라인 적용
  },
  xaxis: {
    tickAmount: 10, // X축에 표시될 틱의 수
    labels: {
      show: true, // 라벨 표시 여부
      rotate: 0, // 라벨 회전 각도
    },
  },
  legend: {
    show: true, // 범례 표시 여부
    position: "bottom", // 범례 위치
  },
  tooltip: {
    enabled: true, // 툴팁 활성화 여부
    shared: true, // 여러 시리즈의 데이터 값을 동시에 툴팁에 표시
  },
  colors: [
    // 데이터 컬러 리스트
    "#008FFB", // ApexChart에서 기본값으로 사용하는 색상을 일단 적용해 놓았음
    "#00E396",
    "#FEB019",
    "#FF4560",
    "#775DD0",
    "#3F51B5",
    "#4CAF50",
    "#F9CE1D",
    "#33B2DF",
    "#D4526E",
  ],
}

export default function LineChart() {
  return (
    <Chart
      type="line" // 차트 유형 지정
      options={chartOptions} // 차트 옵션 적용
      series={chartData} // 차트 데이터 적용
      width={"100%"} // 차트 너비 설정
      height={400} // 차트 높이 설정
    />
  )
}
line-1

이 예제에서는 x, y 좌표로 이루어진 데이터를 시각화하며, X축은 1부터 10까지의 숫자를, Y축은 각 데이터 포인트의 값을 표시했다. 옵션 설정 부분의 경우 라이브러리를 사용하면서 찾아본 최대한 많은 옵션을 포함해 놓았지만, 위 차트 옵션 이외에도 많은 설정 옵션들이 존재한다.

line-2

ApexCharts 지원 툴 중 하나인 줌 기능이다. 구역을 확대해서 데이터를 확인할 수 있다. 불필요할 경우 옵션에서 설정을 끄면 된다.

  dataLabels: {
    enabled: true, // 데이터 라벨 표시 ON
    offsetX: -3,
    offsetY: 0,
  },
line-2

데이터 라벨을 표시하면 위와 같이 나타나게 된다. 데이터 라벨이 차트 공간 끝에 걸려 잘리는 경우가 있으니 offset을 조절하거나 div를 따로 설정하여 처리해야 한다.

원형 차트(Pie Chart)

원형 차트는 비율을 나타내는 데 자주 사용된다. ApexCharts에서는 데이터를 섹터로 나누어 비율을 표시하고, 라벨과 중앙에 총합을 표시할 수 있다.

import Chart from "react-apexcharts"

// 차트에 표시될 데이터 설정
const chartData = [20, 19, 22, 36, 35, 0, 18, 22, 0, 30]

// 차트 옵션 설정
const chartOptions = {
  chart: {
    width: "100%",
    height: "100%",
    type: "pie",
  },
  labels: [
    // 원형 차트의 라벨 표시
    "Alpha",
    "Beta",
    "Gamma",
    "Delta",
    "Epsilon",
    "Zeta",
    "Eta",
    "Theta",
    "Iota",
    "Kappa",
  ],
  title: {
    text: "Pie Chart", // 차트 타이틀 텍스트
    align: "center", // 타이틀의 정렬 (center로 설정)
    margin: 5, // 타이틀과 차트 간 여백
    offsetX: -35, // X축 기준으로 타이틀 위치 조정
    style: {
      fontSize: "25px", // 타이틀 텍스트 크기
      color: "#000", // 타이틀 색상
    },
  },
  grid: {
    padding: {
      top: 5, // 차트 상단 여백
      bottom: 5, // 차트 하단 여백
      left: 5, // 차트 왼쪽 여백
      right: 5, // 차트 오른쪽 여백
    },
  },
  tooltip: {
    enabled: false, // 툴팁 비활성화
  },
  legend: {
    show: true, // 범례 표시 여부
    width: 60, // 범례 너비 설정
    offsetY: 10, // 범례의 수직 위치 조정
    itemMargin: {
      horizontal: 2, // 범례 아이템 간의 가로 여백
      vertical: 2, // 범례 아이템 간의 세로 여백
    },
  },
  colors: [
    "#FF5722", // 각 데이터의 색상 설정
    "#FF9800",
    "#FFC107",
    "#4CAF50",
    "#8BC34A",
    "#80DEEA",
    "#33CCFF",
    "#0099FF",
    "#0066CC",
    "#004080",
  ],
}

export default function PieChart() {
  return (
    <Chart
      type="pie"
      options={chartOptions}
      series={chartData}
      width={500}
      height={300}
    />
  )
}
pie-1

위와 같이 각 데이터 항목의 비율을 나타내는 차트를 그릴 수 있다. 마찬가지로 옵션을 통해 원형 차트의 다양한 부분을 커스터마이징할 수 있다.

const chartOptions = {
  chart: {
    width: "100%",
    height: "100%",
    type: "donut", // 타입 도넛으로 변경
  },
  labels: [
    ...
  ],
  title: {
    ...
  },
  plotOptions: {
    pie: {
      donut: {
        size: "55%", // 도넛 차트의 크기
        labels: {
          show: true, // 도넛 중앙에 라벨 표시
          total: {
            show: true, // 도넛 중앙에 총합 표시
            label: "All", // 총합에 대한 라벨 텍스트
            fontFamily: "Noto Sans KR, sans-serif", // 폰트 패밀리 설정
            fontWeight: "600", // 폰트 두께 설정
            fontSize: "20px", // 폰트 크기 설정
            formatter(w) {
              // 총합을 계산하여 표시하는 함수
              return (
                w.globals.seriesTotals.reduce((a, b) => a + b, 0) + " data"
              );
            },
          },
          value: {
            fontFamily: "Noto Sans KR, sans-serif", // 데이터 값에 대한 폰트 설정
            fontWeight: "600",
            fontSize: "20px",
            formatter(val) {
              // 각 데이터 값을 표시하는 함수
              return val + " data";
            },
          },
        },
      },
      dataLabels: {
        offset: 0, // 데이터 라벨의 위치 조정
      },
    },
  },
  grid: {
    ...
  },
  ...
};

...
    <Chart
      type="donut"
      options={chartOptions}
      series={chartData}
      width={500}
      height={300}
    />
...
donut-1

타입을 donut으로 변경하고 이에 맞는 옵션을 추가하면 중앙이 뚫려 있고 총합을 표시하도록 설정된 도넛 차트로 변형시킬 수 있다.

레이더 차트(Radar Chart)

레이더 차트는 다각형의 축을 따라 여러 데이터를 표현하는 차트로, 여러 카테고리 간의 비교를 시각적으로 보여준다.

import Chart from "react-apexcharts"

// 차트에 표시될 데이터 설정
const chartData = [
  {
    name: "Series 1", // 첫 번째 시리즈 이름
    data: [80, 50, 30, 40, 100, 20], // 각 축에 해당하는 값
  },
  {
    name: "Series 2", // 두 번째 시리즈 이름
    data: [20, 30, 40, 80, 20, 80],
  },
  {
    name: "Series 3", // 세 번째 시리즈 이름
    data: [44, 76, 78, 13, 43, 10],
  },
]

// 차트 옵션 설정
const chartOptions = {
  chart: {
    type: "radar", // 레이더 차트로 설정
    toolbar: {
      show: true, // 차트 상단 툴바 표시
    },
  },
  title: {
    text: "Radar Chart", // 차트 타이틀 텍스트
    align: "center", // 타이틀 위치 조정
    offsetX: -10, // X축 기준으로 타이틀 위치 조정
    style: {
      fontSize: "25px", // 타이틀 폰트 크기
      color: "#000", // 타이틀 색상
    },
  },
  xaxis: {
    // 레이더 차트의 각 축에 해당하는 라벨
    categories: [
      "Category A",
      "Category B",
      "Category C",
      "Category D",
      "Category E",
      "Category F",
    ],
  },
  plotOptions: {
    radar: {
      size: 100, // 레이더 차트의 크기
      polygons: {
        strokeColors: "#e9e9e9", // 레이더 차트 다각형의 색상
        fill: {
          colors: ["#f8f8f8", "#fff"], // 다각형 안의 채우기 색상
        },
      },
    },
  },
  stroke: {
    width: 2, // 라인의 두께 설정
  },
  markers: {
    size: 4, // 데이터 포인트의 크기
  },
  fill: {
    opacity: 0.2, // 채우기 색상의 불투명도 설정
  },
  tooltip: {
    enabled: true, // 툴팁 활성화
  },
  legend: {
    show: true, // 범례 표시
    position: "bottom", // 범례 위치
    horizontalAlign: "center", // 범례의 정렬
  },
  colors: ["#FF4560", "#00E396", "#008FFB"], // 각 데이터의 색상 설정
}

export default function RadarChart() {
  return (
    <Chart
      type="radar"
      options={chartOptions}
      series={chartData}
      width={500}
      height={350}
    />
  )
}
radar-1

이 예제에서는 각 시리즈가 여러 카테고리에 대한 값들을 다각형의 축을 따라 나타낸다. 레이더 차트는 분포도나 성능 비교를 시각화하는 데 유용하다.

사용하며 맞이했던 문제

화면 크기 변화 시 width 문제

Chart 컴포넌트에 width를 적용할 때 "100%", "100vw" 등의 옵션이 적용되지 않는 케이스가 발견되었다. 부모 요소의 적용 여부와 관계없이 고정적인 width 값을 입력해야 적용이 되었다. 이는 반응형 디자인을 고려할 때 문제가 발생한다.

width-problem

따라서 화면 크기 변화를 수동적으로 감지하여 차트의 가로 길이를 업데이트 하는 방식을 적용해 보았다.

  // 라이브러리 문제 때문에 가로 길이 수동적으로 설정
  const paperRef = useRef(null);
  const [chartWidth, setChartWidth] = useState("");
  // 컴포넌트가 렌더링될 때와 화면 크기 변화가 감지될 때 차트의 가로 길이를 업데이트
  useEffect(() => {
    const updateChartWidth = () => {
      if (paperRef.current) {
        setChartWidth(paperRef.current.offsetWidth - 15);
      }
    };
    const observer = new ResizeObserver(() => {
      updateChartWidth();
    });
    const paperElement = paperRef.current;
    if (paperElement) {
      // 요소의 크기 변화를 감시
      observer.observe(paperElement);
    }
    updateChartWidth();
    return () => {
      if (paperElement) {
        // 컴포넌트가 언마운트될 때 감시 해제
        observer.unobserve(paperElement);
      }
    };
  }, []);

  ...

    return (
    <div ref={paperRef}>
        ...
        <Chart
          type="line"
          options={chartOptions}
          series={chartData}
          width={chartWidth}
          height={300}
        />
        ...
    </div>
  );

적용하려는 프로젝트의 구조나 사용 환경에 따라 다를 수 있고, 또 다른 해결 방법이 존재할 수 있다. 이 방식은 위에서 소개된 프로젝트의 방식과 유사한 구조를 생각중이라면 나쁘지 않은 해결 방법이 될 수 있다.

toString Error

error

이 문제는 Javascript React 환경에서 테스트를 하던 중 발견되었다. 차트를 생성하고 난 후 웹페이지에서 클릭을 해보면 불규칙적으로 에러가 발생한다. 비교적 간단한 차트를 구성할 경우에는 발생하지 않은 경우도 있다.

원인은 아직까지 파악되지 않았다. Typescript 환경에서 개발을 할 때는 발생하지 않는 문제이기 때문에, 만약 프로젝트에 큰 데이터와 복잡한 옵션이 필요한 차트를 사용할 것이라면 Typescript를 사용하는 것을 권장한다. (추후 원인 파악 시 글 수정 예정)

// 옵션 타입 선언 (위에서 설치했던 것은 react-apexcharts로, apexcharts도 설치 필요)
import { ApexOptions } from "apexcharts";

const chartOptions: ApexOptions = {
  ...

Reference

ApexCharts.js

Written by

yhuj79

🌱 Junior Developer