main-logo

강력한 데이터 시각화 라이브러리 amChart

amChart와 위믹스 플랫폼 활용 예시

profile
나만애옹이없어
2024년 01월 15일 · 0 분 소요

들어가며

각종 지표나 데이터를 시각화하여 표시하는 데는 여러 가지 방법이 있지만 가장 효과적으로 전달할 수 있는 방법 중 하나는 그래프 또는 차트를 활용하는 것입니다. amChart는 무료 또는 라이선스 비용을 지불하고 사용할 수 있는 가장 강력한 데이터 시각화 라이브러리입니다.

기본적인 사용방법을 샘플 코드 조각 일부와 함께 살펴보고 위믹스 플랫폼에서 활용한 방법을 간단하게 소개하려고 합니다.

 

시작하기

임포트

차트 유형에 따라서 필요한 모듈이 다를 수 있으니 공식 문서를 참고하세요. 저는 XYChart를 기준으로 설명합니다. 환경은 next.js입니다.

import * as am5 from '@amcharts/amcharts5';
import * as am5xy from '@amcharts/amcharts5/xy';
import am5themes_Animated from '@amcharts/amcharts5/themes/Animated';

 

차트 생성

useEffect(() => {
  let root = am5.Root.new(id); // 루트 요소 인스턴스화
  root.setThemes([am5themes_Animated.new(root)]); // 애니메이션을 위한 테마 주입
  let chart = root.container.children.push( // 차트 인스턴스 주입
    am5xy.XYChart.new(root, {
      paddingRight: 0,
      paddingTop: 8,
      paddingBottom: 0,
      paddingLeft: 3,
    })
  );
});

차트의 종류를 지금 단계에서 설정 가능합니다. 예시 코드의 XYChart는 x축과 y축을 이용하여 선, 막대 등으로 데이터를 표현하는 차트로 가장 많이 사용되는 유형입니다.

기본적으로 데이터의 포맷, 오토 사이즈 등의 전역 설정이 기본값으로 적용되어 있으며 필요하다면 세부적인 조정이 가능합니다. 또한 컴포넌트 마운트 이후에 주입되어야 하기 때문에 useEffect 훅 내부에서 작업되어야 합니다.

인스턴스 생성 시 옵션을 부여하는 방법도 있지만 특정 옵션을 선언 후 set 하는 것도 가능합니다.

chart.zoomOutButton.set('forceHidden', true);

 

id에 차트 인스턴스를 할당하면 차트를 그릴 canvas가 생성됩니다. 컨테이너 역할을 하는 요소는 차트의 실제 크기를 결정할 수 있게 width와 height 등의 스타일을 잡아주면 되죠.

<div className="chart-container">
  <div id={id} className="multi-step-chart"></div>
</div>

 

useEffect 훅 외부에서도 차트에 접근 가능하게 하기 위해서는 빈 Ref를 생성 후 다시 할당해 주는 작업이 필요합니다.

const chartRef = useRef(null);

chartRef.current = chart;

 

Axis

앞서 차트가 XYChart로 생성되었기 때문에 x축과 y축을 설정해 줘야 합니다. Pie형 차트처럼 차트 유형이 완전히 다른 경우라면 선언할 필요가 없기도 하니 구현하려고 하는 차트의 종류를 확인하고 공식 사이트의 문서와 관련 튜토리얼을 확인해 필요한 설정을 진행하면 됩니다.

const xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    tooltipLocation: 0.5,
    startLocation: 0.1,
    baseInterval: { timeUnit: 'day', count: 1 },
    gridIntervals: [
      { timeUnit: 'day', count: 6 },
      { timeUnit: 'month', count: 1 },
      { timeUnit: 'month', count: 2 },
      { timeUnit: 'year', count: 1 },
      { timeUnit: 'year', count: 5 },
      { timeUnit: 'year', count: 10 },
      { timeUnit: 'year', count: 50 }
    ],
    dateFormats: dateFormat, // 날짜 포맷 지정
    renderer: am5xy.AxisRendererX.new(root, {}), // Renderer 설정을 위해 추가
    tooltip: am5.Tooltip.new(root, {
      forceHidden: true
    })
  })
);

보통의 경우, y축에는 데이터가 매핑되고 x축에는 기준값들이 매핑됩니다. 기준값들은 몇 가지 타입을 제공하고 있는데 ValueAxis, DateAxis 등 타입 별로 옵션이 달라서 (예를 들면, CategoryAxis 형식의 경우 카테고리 별로 x축 정보들을 묶어서 보여주는 것이 가능합니다.) 공식 문서를 참고하여 원하는 차트 방향으로 커스터마이징해야 합니다.

 

Renderer

Renderer 설정을 이용하여 그리드 및 레이블의 시각적 디자인을 변경해 줄 수 있습니다. 축마다 따로 설정해 줘야 합니다.

const xRenderer = xAxis.get('renderer');
xRenderer.labels.template.setAll({
  oversizedBehavior: 'fit',
  textAlign: 'center',
  fill: am5.color('#fff'),
  paddingTop: 8,
  fontSize: 12,
  fillOpacity: 0.4,
  location: 0
});

 

Series

실제 데이터 묶음이라 할 수 있는 Series를 추가하는 단계입니다. 이 단계에서 각 축의 선이나 영역, 막대의 디자인을 변경해 줄 수 있습니다. 옵션이 다양하게 제공되고 있어서 꽤 많은 것을 할 수 있습니다.

const series = chart.series.push(
  am5xy.LineSeries.new(root, {
    name, // 축 레이블.
    xAxis, // 앞서 설정한 xAxis가 연결됨
    yAxis, // 앞서 설정한 yAxis가 연결됨
    valueYField: field, // 차트 데이터가 매핑됨.
    valueXField: 'date'
  })
);

 

 

UX를 보완해주는 여러 기법들

차트가 복잡해지면 자칫 너무 많은 차트 데이터로 인하여 오히려 데이터를 보기 어려운 경우가 생길 수 있습니다. 차트에서 제공하는 여러 옵션을 적용해 이런 문제를 완화하여 사용성을 증가시켜 줍니다. 아래는 대표적인 3가지 옵션입니다.

 

Bullet

Bullet은 여러 표식을 추가하여 사용자로 하여금 데이터 포인트의 위치를 잘 알아볼 수 있도록 해주는 방법입니다. 아래 코드 조각은 Series 포인트들에 원형 표식을 추가하고 간단한 마우스 인터랙션을 부여하고 있습니다.

series.bullets.push(() => {
  const circle = am5.Circle.new(root, {
    radius: 4,
    interactive: true,
    fill: am5.color('#121212'),
    stroke: am5.color('#fff'),
    strokeWidth: 2
  });
  circle.states.create('default', {
    opacity: 0
  });
  circle.states.create('hover', {
    opacity: 1
  });
  return am5.Bullet.new(root, {
    sprite: circle
  });
});

 

Cursor

사용자 입력 도구에 반응하여 그리드 위치에 라인을 그려주는 기법으로 사용자의 현재 포커스 위치의 시인성을 증가시켜 줍니다.

const cursor = chart.set(
  'cursor',
  am5xy.XYCursor.new(root, {
    xAxis: xAxis,
    yAxis: yAxis,
    snapToSeriesBy: 'x'
  })
);

 

Tooltip

포인트의 데이터를 말풍선 레이어로 띄워줍니다. Cursor 또는 Bullet과 연계하거나 고정 위치에 계속 띄워 둘 수 있습니다. 아래 코드 조각은 툴팁을 세팅 후 배경 디자인과 html로 이루어진 inner UI를 추가하는 간단한 방법입니다.

const tooltip = stepSeries.set(
  'tooltip',
  am5.Tooltip.new(root, {
    getFillFromSprite: false,
    getStrokeFromSprite: false,
    autoTextColor: false,
    pointerOrientation: 'horizontal'
  })
);
tooltip.get('background').setAll({
  fill: am5.color('#212121'),
  fillOpacity: 1,
  stroke: am5.color('#fff'),
  strokeOpacity: 0.07,
  cornerRadius: 3
  // ...
});
tooltip.label.adapters.add('html', function () {
   // ...  
});

 

위믹스 플랫폼 활용 예시

위믹스 가치 보존 정책인 소각 프로세스에 대한 정보 제공 플랫폼인 Wemix Burn 플랫폼에서 amChart를 활용한 케이스를 코드 조각과 함께 살펴보겠습니다.

 

강조되어야 하는 레이블을 고정으로 노출시키기

amChart의 그리드 레이블은 기본적으로 반응형이며 설정된 노출 빈도 등에 의해서 그 개수나 분포가 자동으로 배치됩니다.

WII 트렌드 차트는 수치가 0이하로 유지되는 디플레이션 상태를 안내해 주어 위믹스의 소각 정책이 정상적으로 운용 중임을 안내해 주는 것이 중요한 차트입니다.

스크린샷 2024-01-16 오전 11.43.27.png

amChart의 기본 설정인 반응형 레이블에 영향을 받지 않고 y축에 특정 그리드 레이블(0에 해당하는 라인)을 고정으로 노출시키기 위해서 Range 옵션을 활용했습니다.

Range는 차트의 고정된 구역 범위를 강조해 줄 때 활용하는 옵션인데 단선으로 위치를 고정하여 노출시키는 방법으로 해결했습니다.

const createRange = (value: number, label: string): void => {
  const rangeDataItem = yAxis.makeDataItem({
    value: value
  });

  const range: am5.DataItem<am5xy.IValueAxisDataItem> = yAxis.createAxisRange(rangeDataItem);

  range.get('label').setAll({
    forceHidden: false,
    text: label,
    fill: am5.color('#fff'),
    fillOpacity: label === '0' ? 1 : 0.4,
    paddingLeft: 16,
    fontSize: 12,
    location: 0.5
  });
  if (label !== '0') return;
  range.get('grid').setAll({
    forceHidden: false,
    stroke: am5.color('#fff'),
    strokeOpacity: 0.2
  });
};
createRange(value, "label");

 

마지막 데이터에만 말풍선 띄우기

스크린샷 2024-01-16 오전 11.43.45.png

공급량 & 소각량 트렌드 차트는 현재 시점의 값이 중요한 포인트여서 데이터의 가장 마지막 값에는 강조 표시를 위해 말풍선 Tooltip을 고정으로 띄워주는 기능이 구현되었습니다.

말풍선을 띄우기 위해서 차트 데이터의 가장 마지막 값에 특정 키를 삽입하고 조건문 처리하면 구현할 수 있습니다.

// datas
{
  date: new Date(2024, 1, 16, 0, 0).getTime(),
  burn: 1000000,
  supply: 1000000,
  bullet: true // 가장 마지막 데이터에만 존재
}

// chart
series.bullets.push(function(root, series, dataItem) {  
  if (dataItem.dataContext.bullet) {    
    // bullet setting
  }
})

 

스택으로 쌓이는 영역형 라인 그래프에서 보고 싶은 영역만 강조 표시하기

위믹스 소각 정책은 매스 번, 배치 번, 오토 번으로 구분할 수 있습니다.

스크린샷 2024-01-16 오전 11.44.24.png

Burn Chart는 각각의 소각 정책으로 인해서 소각된 총 위믹스의 양을 타임라인별로 보여주고 있는데요, 추후 시간과 소각량이 대량 누적되면 한정된 영역 안에서 원하는 데이터만 확인하기 어려울 수 있었습니다.

이를 해결할 방법으로 원하는 범례에 인터랙션을 추가하고 가로형 막대그래프와도 연동하여 보고 싶은 데이터만 강조되는 기능을 구현했습니다.

시리즈 데이터의 인덱스와 부모 컴포넌트로부터 하이라이트가 필요한 시리즈의 인덱스를 내려받아 비교해 주는 방법으로 쉽게 구현이 가능합니다.

const MultiStepLine = ({
  // ...props
  chartHighlightIndex
}: MultiStepLineType) => {

  useEffect(() => {
    const series = chartRef.current.series.getIndex(chartHighlightIndex?.index);
    chartRef.current.series.each((chartSeries) => {
      if (chartHighlightIndex === null || chartHighlightIndex === undefined) {
        chartSeries.fills.template.setAll({
          fillOpacity: 0.2
        });
      } else {
        if (series !== chartSeries) {
          chartSeries.fills.template.setAll({
            fillOpacity: 0.2
          });
        } else {
          chartSeries.fills.template.setAll({
            fillOpacity: 0.5
          });
        }
      }
    });
  }, [chartHighlightIndex]);

  return (
    //...
  )
};

 

 

마치며

위믹스 플랫폼을 개발하면서 적용해 본 여러 차트 기법과 그 중심에 있는 amChart에 대해 간단히 소개해 보는 자리였습니다. 읽어주셔서 감사합니다.