[Next.js] Chart.js - 도넛 차트 두께 변경
본문 바로가기
카테고리 없음

[Next.js] Chart.js - 도넛 차트 두께 변경

by 은돌1113 2022. 4. 11.
728x90

아래 게시물의 도넛 차트 코드를 바탕으로 수정😂😂😂

 

[Next.js] Chart.js

Chart.js | Open source HTML5 Charts for your website New in 2.0 New chart axis types Plot complex, sparse datasets on date time, logarithmic or even entirely custom scales with ease. www.chartjs.or..

eundol1113.tistory.com


도넛 차트는 만들었는 데 두께가 너무 두껍다고 판단되어 아래 블로그를 참고하여 수정하였다!

 

React에서 Chartjs 도넛 그래프 적용, 타입스크립트 에러 처리

React에서 Chartjs 적용, 오류 해결과 타입 지정에 대한 정리글 입니다.chartjs와 react chartjs를 같이 설치합니다.yarn add react-chartjs-2 chart.jsChartjs에서 공식문서에서 원하는 도형 선택, 차트 데이터와 옵

velog.io

 

아래 코드를 expData에 넣어주면 끝난다.

cutout: "75%",    // 도넛 안쪽 원의 크기 설정

 

내가 원하는 모습으로 만들어 졌다.


전체 코드

더보기
// 공식문서 https://www.chartjs.org/docs/latest/samples/legend/html.html

import { useEffect, useState } from "react";
import styled from "styled-components";

import "chart.js/auto";
// 출처 https://stackoverflow.com/questions/70098392/react-chartjs-2-with-chartjs-3-error-arc-is-not-a-registered-element
import { Doughnut } from "react-chartjs-2";
// 출처 https://dipsiiiiiiiiii.com/2019/11/03/1%EC%B0%A8-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-react%EC%97%90%EC%84%9C-chart-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0/

// Chart > ToolTip 커스텀
const getOrCreateTooltip = (chart) => {
  let tooltipEl = chart.canvas.parentNode.querySelector("div");

  if (!tooltipEl) {
    tooltipEl = document.createElement("div");
    tooltipEl.style.background = "#FFFFFF 0% 0% no-repeat padding-box";
    tooltipEl.style.borderRadius = "10px";
    tooltipEl.style.color = "#000000";
    tooltipEl.style.opacity = 1;
    tooltipEl.style.pointerEvents = "none";
    tooltipEl.style.position = "absolute";
    tooltipEl.style.transform = "translate(-50%, 0)";
    tooltipEl.style.transition = "all .1s ease";

    const table = document.createElement("table");
    table.style.margin = "0px";

    tooltipEl.appendChild(table);
    chart.canvas.parentNode.appendChild(tooltipEl);
  }

  return tooltipEl;
};

const externalTooltipHandler = (context) => {
  // Tooltip Element
  const { chart, tooltip } = context;
  const tooltipEl = getOrCreateTooltip(chart);

  // Hide if no tooltip
  if (tooltip.opacity === 0) {
    tooltipEl.style.opacity = 0;
    return;
  }

  // Set Text
  if (tooltip.body) {
    const titleLines = tooltip.title || [];
    const bodyLines = tooltip.body.map((b) => b.lines);

    const tableHead = document.createElement("thead");

    titleLines.forEach((title) => {
      const tr = document.createElement("tr");
      tr.style.borderWidth = 0;

      const th = document.createElement("th");
      th.style.borderWidth = 0;
      const text = document.createTextNode(title);

      th.appendChild(text);
      tr.appendChild(th);
      tableHead.appendChild(tr);
    });

    const tableBody = document.createElement("tbody");
    bodyLines.forEach((body, i) => {
      const colors = tooltip.labelColors[i];

      const span = document.createElement("span");
      span.style.background = colors.backgroundColor;
      span.style.borderColor = colors.borderColor;
      span.style.borderWidth = "2px";
      span.style.marginRight = "10px";
      span.style.height = "10px";
      span.style.width = "10px";
      span.style.display = "inline-block";

      const tr = document.createElement("tr");
      tr.style.backgroundColor = "inherit";
      tr.style.borderWidth = 0;

      const td = document.createElement("td");
      td.style.display = "block";
      td.style.width = "100px";
      td.style.textAlign = "center";

      // tooltip text 커스텀
      const text = document.createTextNode(`${body[0].split(":")[0]}`);
      const text2 = document.createTextNode(`${body[0].split(":")[1]}원`);

      td.appendChild(span);
      td.appendChild(text);
      td.appendChild(text2);
      tr.appendChild(td);
      tableBody.appendChild(tr);
    });

    const tableRoot = tooltipEl.querySelector("table");

    // Remove old children
    while (tableRoot.firstChild) {
      tableRoot.firstChild.remove();
    }

    // Add new children
    tableRoot.appendChild(tableHead);
    tableRoot.appendChild(tableBody);
  }

  const { offsetLeft: positionX, offsetTop: positionY } = chart.canvas;

  // Display, position, and set styles for font
  tooltipEl.style.background = "#FFFFFF 0% 0% no-repeat padding-box";
  tooltipEl.style.boxShadow = "0px 0px 10px #ADADAD40";
  tooltipEl.style.borderRadius = "10px";
  tooltipEl.style.font = "normal normal normal 14px/16px Pretendard";
  tooltipEl.style.letterSpacing = "-0.35px";
  tooltipEl.style.color = "#000000";
  tooltipEl.style.opacity = 1;
  tooltipEl.style.left = positionX + tooltip.caretX + "px";
  tooltipEl.style.top = positionY + tooltip.caretY + "px";
  tooltipEl.style.font = tooltip.options.bodyFont.string;
  tooltipEl.style.padding =
    tooltip.options.padding + "px " + tooltip.options.padding + "px";
};

const Chart = (props) => {
  const { select_state } = props;

  // Chart 데이터
  const expData = {
    labels:
      select_state === "건물 선택" // 건물 선택 안된 상태에서 홈 진입 시 미달성액만 있어야 함
        ? ["미달성액"]
        : ["미달성액", "개인룸", "비상주", "자유석", "지정석"],
    datasets: [
      {
        labels:
          select_state === "건물 선택" // 건물 선택 안된 상태에서 홈 진입 시 미달성액만 있어야 함
            ? ["미달성액"]
            : ["미달성액", "개인룸", "비상주", "자유석", "지정석"],
        data:
          select_state === "건물 선택" // 건물 선택 안된 상태에서 홈 진입 시 미달성액에 목표 금액 넣어야 함
            ? [30000000]
            : [5290500, 14103000, 4282000, 3288000, 3036500],
        borderWidth: 0, // 차트별 border CSS
        hoverBorderWidth: 0,
        backgroundColor: [
          "#E1E1E1",
          "#038CFF",
          "#90F9A0",
          "#21F2A5",
          "#00DCEC",
        ],
        fill: false,
        // 출처 https://velog.io/@tunakim/React%EC%97%90%EC%84%9C-Chartjs-%EB%8F%84%EB%84%9B-%EA%B7%B8%EB%9E%98%ED%94%84-%EC%A0%81%EC%9A%A9-%ED%83%80%EC%9E%85%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8-%EC%97%90%EB%9F%AC-%EC%B2%98%EB%A6%AC
        cutout: "75%", // 도넛 안쪽 원의 크기 설정
      },
    ],
  };

  const [goal, setGoal] = useState();

  useEffect(() => {
    // 건물 선택 할 때마다 목표 달성률 다시 계산
    achievementRate();
  }, [select_state]);

  /* 
    매출 현황 > 목표 달성률
    => 달성률 - 현재 매출 / 매출 목표 * 100 -> 소수점으로 나올 경우 Math.round 써서 반올림
  */
  const achievementRate = () => {
    const labels = expData.datasets[0].labels; // 라벨
    const data = expData.datasets[0].data; // 라벨 값

    let 현재매출 = 0; // 현재 매출
    let 목표매출 = 30000000; // 목표 매출 (DB에서 받아온 값)

    // 현재매출 구하기
    labels.forEach((label, i) => {
      if (label !== "미달성액") {
        현재매출 += data[i];
      }
    });

    // 달성률 구하기
    setGoal(Math.round((현재매출 / 목표매출) * 100));
  };

  return (
    <ChartCSS>
      <Doughnut
        options={{
          interaction: {
            intersect: false,
            mode: "index",
          },
          cutoutPercentage: 100,
          legend: {
            position: "right",
          },
          plugins: {
            // 카테고리 커스텀
            legend: {
              display: false,
            },
            // 제목 커스텀
            title: {
              display: false,
              text: "Chart.js Line Chart - External Tooltips",
            },
            // 툴팁 커스텀
            tooltip: {
              enabled: false,
              position: "nearest",
              external: externalTooltipHandler,
            },
          },
        }}
        data={expData}
        height={120}
      />
      <p className="goal">
        <span>{goal ? goal : 0}%</span>
        <br />
        목표 달성률
      </p>
    </ChartCSS>
  );
};

const ChartCSS = styled.div`
  width: 200px;
  height: 200px;
  margin: 0 auto;
  position: relative;
  cursor: pointer;

  .goal {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    text-align: center;

    font: normal normal normal 14px/16px Pretendard;
    letter-spacing: -0.35px;
    color: #777777;

    span {
      font: normal normal 900 32px/38px Pretendard;
      letter-spacing: 0px;
      color: #101a36;
    }
  }
`;

export default Chart;

 

728x90

댓글