본문 바로가기

개발/React

SVG를 이용한 라인 그래프(Bar Chart) 만들기

SVG를 이용한 라인 그래프 만들기

계산 방식

  1. 데이터들 중 최솟값과 최댓값을 찾는다.
  2. 최댓값 - 최솟값을 하여 변화량(delta)을 구한다.
  3. y값을 계산하기 위해 데이터값에 최솟값을 빼주고 (svg 높이 / delta) 값을 곱해준다.
    • ex) max=10, min=-10, svgHeight=100, data=0일 경우
    • y = (0 - (-10)) * (100 / 20) = 50
  4. x값은 index * 컬럼 너비 + 컬럼 너비 / 2로 설정하여 각 컬럼의 중간에 기준점을 표시하도록 하였다.
  5. 데이터가 최솟값이거나 최댓값일 경우 svg로 표시할 경우 잘리는 경우가 발생할 수 있어 y에 offset을 더해주었다.
    • y: (value - minValue) * (svgHeight / delta) + offset

코드

export default function LineGraph(props) {
  const offset = 10;
  let {
    data: { columns },
    axisMax,
    axisMin,
    height,
    graphColor,
    columnWidth
  } = props;

  let arr = columns.reduce((prev, next) => prev.concat(next)); // 2차원 목록 columns를 1차원으로 변환
  let maxValue = axisMax ? Math.max(axisMax, ...arr) : Math.max(...arr); // axisMax와 데이터들 중 가장 큰 값을 찾음
  let minValue = axisMin ? Math.min(axisMin, ...arr) : Math.min(...arr); // axisMin과 데이터들 중 가장 작은 값을 찾음
  let maxLength = columns.reduce(
    (prev, next) => (prev > next.length ? prev : next.length),
    -1
  ); // columns의 데이터들 중 가장 긴 길이를 찾음
  let svgWidth = columnWidth * maxLength; // 컬럼의 기본 길이에 maxLength를 곱하여 svg의 너비를 계산
  let svgHeight = height ? height : 100;
  let delta = maxValue - minValue;

  // x값, y값 계산
  let polylines = columns.map((column) =>
    column.map((value, index) => {
      const point = {
        x: index * columnWidth + columnWidth / 2,
        y: (value - minValue) * (svgHeight / delta) + offset
      };
      return point;
    })
  );

  return (
    <div className="line_graph_area">
      <svg
        width={svgWidth}
        height={svgHeight + offset * 2}
        viewBox={`0 0 ${svgWidth} ${svgHeight + offset * 2}`}
        style={{ transform: "scaleY(-1)" }}
      >
        {polylines.map((polyline, index) => {
          const points = polyline.reduce((acc, point) => {
            acc += `${point.x}, ${point.y} `;
            return acc;
          }, "");
          return (
            <polyline
              stroke={graphColor[index]}
              strokeWidth="2"
              fill="none"
              points={points}
            />
          );
        })}
        {polylines.map((polyline, index) =>
          polyline.map((dot, dotIndex) => (
            <circle
              key={dotIndex}
              r="2.5"
              cx={dot.x}
              cy={dot.y}
              stroke={graphColor[index]}
              stroke-width="2"
              fill={"#fff"}
            ></circle>
          ))
        )}
      </svg>
    </div>
  );
}

사용 예

import LineGraph from "./LineGraph";
import "./styles.css";

export default function App() {
  return (
    <>
      <LineGraph
        data={{
          columns: [
            [5, 8, 8, 5, 8, 8],
            [2, 8, 8, 9, 9, 9, 9],
            [8.5, 8.5, 8.5],
            [1, 1, 1],
            [3, 2, 1],
            [1, 1, 3]
          ]
        }}
        graphColor={[
          "#74bfff",
          "#ff7d7d",
          "#badc58",
          "#e056fd",
          "#fab1a0",
          "#ffeaa7"
        ]}
        height={250}
        columnWidth={100}
      />
      <LineGraph
        data={{
          columns: [[5, 8, 8, 5, 8, 8]]
        }}
        graphColor={[
          "#74bfff",
          "#ff7d7d",
          "#badc58",
          "#e056fd",
          "#fab1a0",
          "#ffeaa7"
        ]}
        height={250}
        columnWidth={100}
      />
    </>
  );
}

결과 화면

'개발 > React' 카테고리의 다른 글

[정리] Thinking in React  (0) 2019.08.20