[ChartJS / react-chartjs-2] 클릭이벤트 적용해보기 #5

김하정·2024년 1월 2일
3
post-thumbnail

이번에는 차트에 click event를 넣어, 클릭하는 index 총합의 데이터를 하단 영역에 보여지도록 만들어 볼 것이다.

이전 포스팅에 이어 믹스형 차트에서 진행할 것이기에,
처음부터 보고싶은 사람들은 이전 포스팅을 참고하시길 바란다 🙂

먼저, ChartJS 에서 안내하는 이벤트 사용법은 다음과 같다.

event 참고 문서 - ChartJS

문서에 더 자세히 나와있지만, 설명을 해석해보자면 options.event에 사용할 event 를 등록해주고, 관련 이벤트 함수를 적용해주면 된다!

그러나 나의 경우,
react-chartjs-2 를 함께 사용하고 있기 때문에, 해당 문서에서 제안하는 event를 사용해보았다.
click event 참고 문서 - react-chartjs-2

react-chartjs-2 를 참고해서 쓰기가 훨씬 간단하다~ react-chartjs-2에서는 다음과 같이 3가지의 이벤트를 제공해주고 있는데
어떤 차이가 있는지 함께 알아보자!

1) getDatasetAtEvent

 const chartRef = useRef();
  const onClick = (
    event: React.MouseEvent<HTMLCanvasElement, globalThis.MouseEvent>
  ) => {
    const chart = chartRef.current;
    if (!chart) return;
    console.log(getDatasetAtEvent(chart, event));
  };

  return (
    <div>
      <Chart
        ref={chartRef}
        type="bar"
        data={mixData}
        options={mixOptions}
        onClick={onClick}
      />
    </div>
  );

먼저 getDatasetAtEvent 는 클릭한 차트 타입의 모든 element에 대한 정보들을 출력해준다.

예를들어 barElement를 클릭했을 경우, 다음처럼 12개의 데이터가 콘솔로 출력되었다.

2) getElementAtEvent

상단 console.log 에서 getElementAtEvent 로만 바꿔주면, 변경된 출력 내용을 확인해볼 수 있다.

getElementAtEvent는 클릭한 차트타입 한개만 콘솔에 찍어준다.

barElement의 특정 index를 클릭해주면 다음과 같이 콘솔이 출력된다.

3) getElementsAtEvent

getElementsAtEvent 는 클릭한 index에 있는 모든 chart type 에 대한 정보를 콘솔로 출력해준다.

다음과 같이 bar 형, line 형 차트 3가지를 모두 콘솔에 찍어주는 것을 알 수 있다.

상황에 따라 위 3가지 중 한가지를 선택하면 될 것이다!

나의 경우, 3가지의 index를 모두 출력하게 해야하기 때문에 getElementsAtEvent를 활용해 볼 것이다.

그럼, 이제 해당 함수를 이용하여 클릭한 요소들의 값과 정보를 테이블로 보여주게 만드는 코드를 작성해보자.

type TClickType = { label: string; cnt: number };
type TKeys = "total" | "react" | "vue";
//전체,react,vue를 담아 줄 state 만들어주기
const [clickType, setClickType] = useState<Record<TKeys, TClickType>>();


// 클릭하면 setClickType에 getElementAtEvent로부터 받아온 클릭한 index의 data 와 label 담아주기
 const onClick = (
    event: React.MouseEvent<HTMLCanvasElement, globalThis.MouseEvent>
  ) => {
    const chart = chartRef.current;
    if (!chart) return;
    const click_result = getElementAtEvent(chart, event);

    const dataIndex = click_result[0].index;
    const label = labels_dummy[click_result[0].index];

    setClickType({
      total: { label, cnt: total_dummy[dataIndex] },
      react: { label, cnt: react_dummy[dataIndex] },
      vue: { label, cnt: vue_dummy[dataIndex] },
    });
  };


// click event 적용하기
      <Chart
        ref={chartRef}
        type="bar"
        data={mixData}
        options={mixOptions}
        onClick={onClick}
      />
          

다음과 같이 화면 하단에 표시가 된다 ⭐️

전체 코드는 다음과 같다~

import { Chart, getElementAtEvent } from "react-chartjs-2";

import {
  ArcElement,
  BarElement,
  CategoryScale,
  Chart as ChartJS,
  Legend,
  LineElement,
  LinearScale,
  PointElement,
  Tooltip,
  registerables,
} from "chart.js";
import { useMemo, useRef, useState } from "react";
import styled from "styled-components";

ChartJS.register(
  ...registerables,
  LinearScale,
  CategoryScale,
  BarElement,
  PointElement,
  LineElement,
  Legend,
  Tooltip,
  ArcElement
);

type TClickType = { label: string; cnt: number };
type TKeys = "total" | "react" | "vue";

const MixChartExample = () => {
  const [clickType, setClickType] = useState<Record<TKeys, TClickType>>();
  const react_dummy = [10, 10, 10, 10, 20, 20, 20, 20, 30, 30, 30, 30];
  // const react_flex_dummy = [
  //   10, 10, 1000, 10, 20, 200, 20, 20, 330, 30, 1130, 30,
  // ];
  const vue_dummy = [10, 30, 10, 10, 10, 20, 30, 30, 20, 20, 20, 30];
  const total_dummy = react_dummy.map((item, idx) => item + vue_dummy[idx]);
  const labels_dummy = new Array(12).fill(0).map((_, idx) => idx + 1 + "월");

  // 최대,최소값을 구하는
  const min = useMemo(() => {
    return Math.min.apply(null, total_dummy);
  }, [total_dummy]);

  const max = useMemo(() => {
    return Math.max.apply(null, total_dummy);
  }, [total_dummy]);

  const mixOptions = {
    scales: {
      x: {
        ticks: {
          maxTicksLimit: 6,
        },
      },
      y: {
        ticks: {
          color: "#808080",
          stepSize: getStepSize(min, max),
        },
      },
    },
  };
  //chart data
  const mixData = {
    labels: labels_dummy,
    datasets: [
      {
        type: "line" as const,
        label: "React" as string,
        data: react_dummy,
        borderColor: "#DD5353",
        backgroundColor: "#DD5353",
        borderWidth: 2, // 선의 크기
        pointBorderWidth: 1, // point의 크기
        tension: 0.1, // line 차트일 경우 선의 휘어짐 정도
      },
      {
        type: "line" as const,
        label: "Vue" as string,
        data: vue_dummy,
        borderColor: "#5F9DF7",
        backgroundColor: "#5F9DF7",
        borderWidth: 2,
        pointBorderWidth: 1,
        tension: 0.1, // line 차트일 경우 선의 휘어짐 정도
      },
      {
        type: "bar" as const,
        label: "전체" as string,
        backgroundColor: "#ddd",
        data: total_dummy,
        borderColor: "#ddd",
        borderWidth: 2,
      },
    ],
  };
  const chartRef = useRef();
  const onClick = (
    event: React.MouseEvent<HTMLCanvasElement, globalThis.MouseEvent>
  ) => {
    const chart = chartRef.current;
    if (!chart) return;
    const click_result = getElementAtEvent(chart, event);

    const dataIndex = click_result[0].index;
    const label = labels_dummy[click_result[0].index];

    setClickType({
      total: { label, cnt: total_dummy[dataIndex] },
      react: { label, cnt: react_dummy[dataIndex] },
      vue: { label, cnt: vue_dummy[dataIndex] },
    });
  };

  return (
    <div>
      <Chart
        ref={chartRef}
        type="bar"
        data={mixData}
        options={mixOptions}
        onClick={onClick}
      />
      {clickType && (
        <TableStyle>
          <caption>선택한 index의 데이터</caption>
          <tr>
            <th></th>
            <th></th>
          </tr>
          <tr>
            <td rowSpan={3}>{clickType.total.label}</td>
            <td>{clickType.total.cnt}</td>
          </tr>
          <tr>
            <td>{clickType.react.cnt}</td>
          </tr>
          <tr>
            <td>{clickType.vue.cnt}</td>
          </tr>
        </TableStyle>
      )}
    </div>
  );
};
export default MixChartExample;

const TableStyle = styled.table`
  width: 500px;
  caption {
    margin: 10px 0;
    padding: 10px 0;
    background-color: #eceaea;
  }
  th {
    height: 40px;
    background: #ddd;
  }
  td {
    height: 25px;
    border-bottom: 1px solid #ddd;
    text-align: center;
  }
`;

const getStepSize = (min: number, max: number) => {
  const range = max - min; // 최댓값과 최솟값 간의 범위를 계산
  const interval = Math.ceil(range / 3); // 범위를 3등분하여 각 구간의 크기를 계산
  const exponent = Math.floor(Math.log10(interval)); // 각 구간의 크기에 대한 로그 값을 계산
  const factor = Math.pow(10, exponent); // 10의 거듭제곱을 통해 가장 가까운 10의 배수로 구간 크기를 정규화
  return Math.ceil(interval / factor) * factor;
};

끝!

profile
web developer

0개의 댓글