안녕하세요

[Nivo Chart] Custom tooltip만들기 + 임의의 Data 전달하기 본문

카테고리 없음

[Nivo Chart] Custom tooltip만들기 + 임의의 Data 전달하기

sakuraop 2023. 8. 20. 18:15

기본 Nivo tooltip의 둘팁은 아래와 같이 정보를 그렇게 많이 포함하고 있지 않다.

custom tooltip example을 보아도, 인터넷에 검색을 해보아도 다음과 같은 상태의 tooltip을 약간 더 이쁘게 꾸며줄 뿐이다.

 

하지만 내가 필요로 하는 결과는 해당 일자에 플레이한 방송 정보에 어떤 게임을 하였는지도 포함시키고 싶었다.

 

Custop tooltip 완성 결과 

tooltip에는 별도의 데이터를 전달할 수 있는 방법이나 props가 존재하지 않는데, 

약간의 꼼수(?)를 써서 어떠한 데이터라도 전달할 수 있는 방법을 찾아냈다.
indexValue(위 이미지의 방송시간 (h) )에 JSON.stringify를 이용하여 객체나 배열의 데이터도 싸그리 보내는 방법이다.


1. Nivo Custom tooltip 만드는 방법

      <ResponsiveBar
        tooltip={CustomTooltip} // custom tooltip

일단 기본적인 사용 방법은 공식 레퍼런스를 참고하자.

ResponsiveBar 의 속성 중 tootip을 이용하여 custom tooltip 만들 수 있다. https://nivo.rocks/bar/

 

HTML element를 반환해야 하고, 해당 element는 위와 같은 props를 전달받는다.
이 중에서 id, value, index ,data 등등은 모두 bar를 결정하는데 중요한 요소이지만,

indexValue는 bar에 key를 가리키는 역할을 할 뿐이지 그렇게 중요하지 않다.


2. element 설계하기

 
const CustomTooltip = ({ ...props }) => {
  let { color, indexValue, value, id } = props;

  return (
    <div className="tooltip-container">
        <span>{value}</span>
    </div>
  );
};

export default CustomTooltip;
 
 

기본적으로는 이와 같이 전달받은 프롭스를 이용하여 컴포넌트에 스타일을 적용한 뒤 반환하면 된다..

 

props를 출력해보면 위의 데이터 이외에도 마우스를 bar에 얹었을 때 나타나게 될

x, y 좌표와 같은 속성들도 함께 확인할 수 있다.


3. Custom tooltip에 데이터 전달하기

const BarChart = ({ barData, title }) => {
  let [data, keys, playedGames] = barData;
  data.category = JSON.stringify([data.category, playedGames]);

  return (
      <ResponsiveBar
        data={[data]} // chart에 사용될 데이터
        indexBy="category" // keys들을 그룹화하는 index key (분류하는 값)
        tooltip={CustomTooltip} // custom tooltip

전달할 데이터의 indexBy는 분류 기준을 가리킨다.

 

[
  {
    "country": "AL",
    "hot dog": 193,
    "hot dogColor": "hsl(50, 70%, 50%)",
    "burger": 134,
    "burgerColor": "hsl(13, 70%, 50%)",
  },
  {
    "country": "AM",
    "hot dog": 15,
    "hot dogColor": "hsl(114, 70%, 50%)",
    "burger": 144,
    "burgerColor": "hsl(17, 70%, 50%)",
  }
]

sample data에서 찾아볼 수 있는 country에 해당하는데, https://nivo.rocks/bar/

 

 

이 indexBy로 읽어들일 속성에는 string type만이 들어갈 수 있으므로 JSON.stringify로 data를 전달하면 된다.

  data.category = JSON.stringify([data.category, playedGames]);

 

그렇게 하면 아래와 같이 props의 indexValue 에 데이터를 string으로 전달할 수 있다.


4. 이제 전달한 데이터를 이용하면 된다.

 
const CustomTooltip = ({ ...props }) => {
  let { color, indexValue, value, id } = props;

  const [originIndexValue, playedGames] = JSON.parse(indexValue);
  const currentPlayedGames = playedGames && getPlayedGamesOfCurrentTooltip(props, id, playedGames);

  return (
    <div className="tooltip-container" style={{ color }}>
      <div className="name" style={{ borderBottom: `1px solid ${color}` }}>
        {id}
      </div>
      <div className="properties">
        <div className="keys">{originIndexValue}</div>
        <div className="value">
          <span>{value}</span>
          <span className="unit"></span>
        </div>
      </div>
      {currentPlayedGames && <PlayedGameList playedGames={currentPlayedGames} />}
    </div>
  );
};
 

5. FIlter 기능을 구현했다면 bar의 index가 아니어야 한다.

const getPlayedGamesOfCurrentTooltip = (props, id, playedGames) => {
  const data = Object.entries(props.data);
  data.pop();
  const sortedKeys = data.sort((a, b) => b[1] - a[1]).map((item) => item[0]);
  const currentIndex = sortedKeys.indexOf(id);
  const currentPlayedGames = playedGames[currentIndex];
  return currentPlayedGames;
};

현재 bar에 해당하는 tooltip에 해당하는 tooltip을 그리기 위해서 현재 bar의 index를 이용하여 데이터를 탐색했다.

 

그런데 아래와 같이 filter 기능을 구현했더니 기존에 존재하였던 bar의 data가 tooltip에 표시되었다.

bar 데이터가 filter로 인해서 변환되었지만,

 

indexValue에 전달된 data는 이미 fetch를 한 직후에 stringify로 변환시켜 보냈으므로,
현재 보여주어야 하는 데이터는 필터링된 데이터를 기반으로 하고,

tooltip의 데이터는 필터링 이전의 데이터를 기반으로 하게 되어 데이터의 불변성에 어긋난 셈이다.

꼼수이기 때문에 어쩔 수 없긴 하다.

 


6. 그렇다면 find를 이용할 수 있게 충분한 데이터 구조를 정리하자

  const transferredData = data.category;
  const parsedData = JSON.parse(transferredData);

  const barIndex = parsedData.shift();
  const [streaming, keys] = parsedData;

  const tooltipData = streaming?.map((item, index) => {
    return {
      playedGames: item.played_games,
      title: item.title,
      key: keys[index],
    };
  });

  const currentData = tooltipData?.find((item) => item.key === id);

전달 받은 데이터는 기존에 palyed_games 데이터만을 포함하였기 때문에 find를 쓸 수 없으니까 발생한 문제였다.

 

이번에는 그렇게 할 필요가 없도록 object로 만들 때 find를 쓸 구석을 만들어 주면 된다.

 

keys를 playedGames와 함께 보내어

현재 bar가 지니고 있는 key를 통해 탐색을 할 수 있도록 한다.

 

    <div className="tooltip-container" style={{ color }}>
      <div className="head" style={{ borderBottom: `1px solid ${color}` }}>
        {id}
      </div>
      <div className="body">
        {currentData && <p className="streaming-title">{currentData.title}</p>}
        <div className="properties">
          <div className="keys">{barIndex}</div>
          <div className="value">
            <span>{value}</span>
            <span className="unit"></span>
          </div>
        </div>
      </div>
      {currentData && (
        <div className="game-list-box">
          <PlayedGameList playedGames={currentData?.playedGames} isTooltip />
        </div>
      )}
    </div>

그리고 적절하게 css를 가미하여 반환하면 된다.

그러면 filter 된 이후의 데이터에 대해서 bar와 동일한 data를 문제 없이 보여줄 수 있게 된다.