[React] ApexCharts 커스텀(Bar)

파송송계란빡 ㅣ 2022. 9. 10. 19:59

💡 원인

→ ApexCharts는 차트라이브러리이다.

→ 원하는 디자인대로 커스텀하기 생각보다 어렵다.

 

🗡 해결

→ 하지만 못할 것은 없다, 나에겐 무적 CSS가 있다.

→ 원하는 위치, 원하는 내용을 넣엔 CSS position: relative; position: absolute;가 있다.

→ 차트의 기본 기능만 사용하고 나머지는 CSS를 이용해서 열심히 커스텀해보자!

 

ApexCharts 공식 홈페이지

ApexCharts.js - Open Source JavaScript Charts for your website

 

ApexCharts.js - Open Source JavaScript Charts for your website

ApexCharts is a a free and open-source modern charting library that helps developers to create beautiful and interactive visualizations for web pages.

apexcharts.com

 

bar차트에 대한 기본 옵션 공식 문서

bar - ApexCharts.js

 

bar – ApexCharts.js

plotOptions: { bar: { horizontal: false, s̶t̶a̶r̶t̶i̶n̶g̶S̶h̶a̶p̶e̶: 'flat', e̶n̶d̶i̶n̶g̶S̶h̶a̶p̶e̶: 'flat', borderRadius: 0, columnWidth: '70%', barHeight: '70%', distributed: false, rangeBarOverlap: true, rangeBarGroupRows: false,

apexcharts.com

 

수많은 codepen 예제 모음

CodePen Search

 

CodePen Search

 

codepen.io

 

 

목표는 디자이너님에게 최대한 맞드는 것이다.

하지만 일반 기능으로는 원하는 디자인으로 만들기 불가능한것 같다.

이것이 디자이너 님께서 만들어준 Top 10 차트이다.

 

 

1차로 기본으로 만들 수 있는건 여기까지였다.

커스텀에 커스텀을 해보자.

 

 

사이즈 변경에 답이 없다.

계속 hight가 동적으로 늘어나고 줄어드는 문제가 있었다.

ApexCharts가 각 Bar에 대해서 %로만 나오기 때문에 높이가 변하면 각 바의 간격이 계속 변경된다.

→ 세로 높이를 고정할수 밖에 없었다.

글도 왼쪽에 나오는게 아니라 내부에 넣고싶다…

 

내부에 넣을 수 있는 기본 옵션같은건 없다.

→ position: relative; position: absolute;를 이용해서 표와 글자가 따로 가는 방법밖에 생각할 수 없었다.

완성… 이정도면…성공적이지 않을까…?

조금 더 만져봐야하지만…얼추 비슷하다.

 

전체코드

import styled from "styled-components";
import ApexCharts from "react-apexcharts";
import { useState, useEffect } from "react";
import axios from "axios";
import BestGoodIcon from "../../../assets/images/main_bset_bl_24.svg";

function setChartOption(threeData) {
  const ChartOptions = {
    options: {
      chart: {
        type: "bar",
        stacked: true, //여러개의 값 한줄로 보여줌
        toolbar: {
          show: false, //상단 툴바 조정(필수)
        },
      },
      plotOptions: {
        bar: {
          horizontal: true,
          startingShape: "rounded",
          endingShape: "rounded",
          barHeight: "20", //바 하나의 너비(필수)
          colors: {
            backgroundBarColors: ["#E6F0FF"],
            backgroundBarOpacity: 1,
            backgroundBarRadius: 9,
          },
        },
      },
      title: {
        text: "ㅤ",
        offsetY: 10,
        offsetX: 10,
        style: {
          fontFamily: "GangwonEduPower",
          fontSize: "18px",
          fontWeight: "400",
          lineHeight: "26px",
          color: "#222222",
        },
      },
      //하단 삼대력 수치 표
      xaxis: {
        categories: threeData.map((item, rank) => ""), //세로축에 영향줌 필수
        labels: {
          formatter: function (val) {
            return val + "KG";
          },
          style: {
            fontFamily: "Pretendard",
            fontStyle: "normal",
            fontWeight: "400",
            fontSize: "12px",
            lineHeight: "20px",
            textAlign: "right",
            color: "#888888",
          },
        },
      },
      //왼쪽 y축
      yaxis: {
        title: {
          text: undefined,
        },
      },
      fill: {
        opacity: 1,
        colors: ["#0066FF", "#7AFFBF", "#00D1FF"],
      },
      legend: {
        //상단 점수 색상별 종류 나열
        position: "top",
        offsetX: -80,
        offsetY: 10,
        fontFamily: "Pretendard",
        fontStyle: "normal",
        fontWeight: "400",
        fontSize: "12px",
        lineHeight: "20px",
        markers: {
          width: 20,
          height: 10,
          fillColors: ["#0066FF", "#7AFFBF", "#00D1FF"],
        },
      },
      dataLabels: {
        enabled: false, //바 내부에 값 숫자 표기
      },

      grid: {
        yaxis: {
          lines: {
            show: false, //각 차트의 구분을 짖는 세로 실선
          },
        },
        xaxis: {
          lines: {
            show: true, //각 차트의 구분을 짖는 가로 실선
          },
        },
      },
    },
  };

  return ChartOptions.options;
}

export default function HomeChart() {
  const [threeData, setThreeData] = useState([]);

  useEffect(() => {
    axios.get(`/api`).then(({ data }) => {
      setThreeData(data.data.users);
    });
  }, []);

  if (!threeData) {
    return <></>;
  }

  return (
    <HomeChartBox>
      <ApexCharts
        options={setChartOption(threeData)}
        series={[
          {
            name: "데드리프트",
            data: threeData.map((item) => item.bigThreePower.dead),
          },
          {
            name: "벤치프레스",
            data: threeData.map((item) => item.bigThreePower.bench),
          },
          {
            name: "스쿼트",
            data: threeData.map((item) => item.bigThreePower.squat),
          },
        ]}
        width="100%"
        height="100%"
        type="bar"
      />
      <TopTitleGroup>
        <img src={BestGoodIcon} alt="3대력 측정 Top 10"></img>
        <TopTitleText>3대력 top 10</TopTitleText>
      </TopTitleGroup>
      <InnerInfo>
        {threeData.map((item, rank) => (
          <InfoGroup key={rank}>
            <TopRankLabel>{rank + 1}</TopRankLabel>
            <TopNickLabel>
              {item.userNickName.includes("_")
                ? item.userNickName.split("_")[1]
                : item.userNickName}
            </TopNickLabel>
            <TopPowerLabel>{`${item.bigThreePower.dead} / ${item.bigThreePower.bench} / ${item.bigThreePower.squat}`}</TopPowerLabel>
          </InfoGroup>
        ))}
      </InnerInfo>
    </HomeChartBox>
  );
}

const HomeChartBox = styled.div`
  position: relative;
  width: 100%;
  min-width: 350px;
  height: 900px;
  margin: 20px auto;
  font-size: ${(props) => props.theme.fontSizeH1};
  box-shadow: 1px 2px 16px rgba(0, 0, 0, 0.16);
  border-radius: 8px;

  @media screen and (max-width: 1440px) {
    width: 900px;
  }
  @media screen and (max-width: 1024px) {
    width: 556px;
  }
  @media screen and (max-width: 600px) {
    width: 400px;
  }
`;

const InnerInfo = styled.div`
  position: absolute;
  width: 250px;
  top: 47px; //숫자가 클수록 바에 붙음, 라벨 위치 조정(차트 높이 변경시 마진으로 텍스트 info 위치 조정)
  left: 37px;
  display: flex;
  flex-direction: column;
`;

const InfoGroup = styled.div`
  display: flex;
  align-items: center;
  margin: 26.5px 0; //라벨 위치 조정(차트 높이 변경시 마진으로 텍스트 info 위치 조정)
`;

const TopRankLabel = styled.span`
  font-size: 18px;
  line-height: 26px;
  text-align: center;
  color: ${(props) => props.theme.reverseFontColor};
  margin-right: 8px;
`;

const TopNickLabel = styled.span`
  font-size: 16px;
  line-height: 24px;
  color: ${(props) => props.theme.reverseFontColor};
  margin-right: 8px;
`;

const TopPowerLabel = styled.span`
  font-size: 14px;
  line-height: 22px;
  color: #ff7a00;
  margin-right: 8px;
`;

const TopTitleGroup = styled.span`
  position: absolute;
  left: 28px;
  top: -4px;
`;
const TopTitleText = styled.span`
  font-family: "GangwonEduPower";
  font-style: normal;
  font-weight: 400;
  font-size: 18px;
  line-height: 26px;
  margin-left: 8px;
  color: ${(props) => props.theme.reverseFontColor};
`;

 

[React] ApexCharts 커스텀(Bar)