소개

막대 차트의 레이블은 차트 크기가 조정되거나, 다양한 화면 해상도에서 표시되거나, amCharts 줌 컨트롤을 사용하여 확대/축소할 때 가독성 문제에 직면하는 경우가 많습니다. 기본적으로 amCharts 4의 레이블은 고정 너비를 가지고 있어, 작은 화면에서는 겹침, 잘림 또는 읽기 어려운 텍스트가 발생할 수 있습니다.

레이블을 진정으로 반응형으로 만들기 위해서는 다음과 같은 작업이 필요합니다:

  • 차트 크기와 열 너비에 따라 레이블의 최대 너비를 동적으로 계산합니다.
  • 긴 레이블을 잘라냅니다 (필요한 경우 오버플로우를 방지하기 위해).
  • 이벤트 훅을 사용하여 레이블이 적절하게 조정되도록 합니다:
    • 차트 크기가 조정됩니다.
    • 줌/팬이 발생합니다.
    • 데이터가 동적으로 업데이트됩니다.

이 글에서는 레이블 너비를 동적으로 조정하는 방법필요할 때 레이블을 잘라내는 방법을 amCharts 4 이벤트를 사용하여 탐구할 것입니다.

열 차트에서 겹치는 레이블과 반응성 부족의 예

문제가 있는 구현 코드:


am4core.ready(function () {
    // 차트 인스턴스 생성
    let chart = am4core.create("chartdiv", am4charts.XYChart);
    chart.hiddenState.properties.opacity = 0; // 초기 페이드 인
    chart.logo.disabled = true;
    chart.responsive.enabled = true; // 반응형 동작 활성화

    // 차트 데이터
    chart.data = [
      { company: "Apple Inc.", visits: 23725 },
      { company: "Microsoft Corporation", visits: 1882 },
      { company: "Alphabet Inc. (Google)", visits: 1809 },
      { company: "Amazon.com, Inc.", visits: 1322 },
      { company: "Tesla, Inc.", visits: 1122 },
      { company: "Meta Platforms, Inc. (Facebook)", visits: 1114 },
      { company: "Berkshire Hathaway Inc.", visits: 711 },
      { company: "PepsiCo, Inc.", visits: 443 },
      { company: "Johnson & Johnson Services, Inc.", visits: 350 },
      { company: "McDonald's Corporation", visits: 298 },
      { company: "NVIDIA Corporation", visits: 150 },
      { company: "Netflix, Inc.", visits: 100 }
    ];

    // 카테고리 축 생성 (X축)
    let categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
    categoryAxis.dataFields.category = "company";
    categoryAxis.renderer.grid.template.location = 0;
    categoryAxis.renderer.minGridDistance = 40;
    categoryAxis.fontSize = 11;
    categoryAxis.renderer.labels.template.wrap = true;
    categoryAxis.renderer.labels.template.maxWidth = 80;
    categoryAxis.renderer.labels.template.padding(10, 0, 0, 0); // 모든 면을 0으로 설정

    // 값 축 생성 (Y축)
    let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
    valueAxis.min = 0;
    valueAxis.max = 24000;
    valueAxis.strictMinMax = true;
    valueAxis.renderer.minGridDistance = 30;

    // 열 시리즈 생성
    let series = chart.series.push(new am4charts.ColumnSeries());
    series.dataFields.categoryX = "company";
    series.dataFields.valueY = "visits";
    series.columns.template.tooltipText = "{valueY.value}";
    series.columns.template.tooltipY = 0;
    series.columns.template.strokeOpacity = 0;

    // 각 열에 다른 색상 적용
    series.columns.template.adapter.add("fill", function (fill, target) {
      return chart.colors.getIndex(target.dataItem.index);
    });

    // 스크롤바 추가
    chart.scrollbarX = new am4core.Scrollbar();
    chart.scrollbarX.marginTop = 30;
    chart.scrollbarX.parent = chart.bottomAxesContainer;

    // 큰 값에 대한 축 분할 추가
    let axisBreak = valueAxis.axisBreaks.create();
    axisBreak.startValue = 2100;
    axisBreak.endValue = 22900;

    // 축 분할 크기 조정
    let d = (axisBreak.endValue - axisBreak.startValue) / (valueAxis.max - valueAxis.min);
    axisBreak.breakSize = 0.05 * (1 - d) / d; // 가시성을 높이기 위해 분할 크기 증가

    // 상호작용 개선을 위한 호버 효과 조정
    let hoverState = axisBreak.states.create("hover");
    hoverState.properties.breakSize = 1; // 호버 시 약간 큰 분할
    hoverState.properties.opacity = 0.1;
    hoverState.transitionDuration = 1500;

    axisBreak.defaultState.transitionDuration = 1000;

}); // end am4core.ready()

레이블에 대한 반응형 최대 너비 설정

amCharts 4에서는 열 차트xAxis 레이블이 고정된 maxWidth를 가지고 있습니다. 그러나 이 너비는 동적으로 조정되지 않기 때문에 열 너비와 차트 크기에 따라 계산해야 합니다.

이를 달성하기 위해 rangechangeendedsizechanged 이벤트에 훅을 걸고, 열의 너비를 확인한 후 maxWidth를 설정합니다.

구현

// 가장 넓은 열에 따라 레이블 최대 너비를 업데이트하는 함수
const updateLabelMaxWidth = () => {
  let maxColumnWidth = 0;

  // 각 열을 반복하며 최대 너비 찾기
  series.columns.each(function (column) {
    if (column && column.pixelWidth > maxColumnWidth) {
      maxColumnWidth = column.pixelWidth;
    }
  });

  // 레이블에 최대 너비 적용
  categoryAxis.renderer.labels.template.maxWidth = maxColumnWidth;
  console.log("업데이트된 최대 레이블 너비: " + maxColumnWidth);

  // 레이블 재처리 강제
  categoryAxis.invalidateLabels();
}

// 줌/팬 시 레이블 너비 업데이트 트리거
categoryAxis.events.on("rangechangeended", function () {
  updateLabelMaxWidth();
});

// 화면 크기 조정 시 레이블 너비 업데이트 트리거
chart.events.on("sizechanged", function () {
  updateLabelMaxWidth();
});

설명

함수 updateLabelMaxWidth는 차트에서 가장 넓은 열에 따라 x축 레이블 너비를 동적으로 조정합니다. 모든 열을 반복하며 최대 너비를 찾아 레이블에 적용하여 겹침을 방지합니다.

이벤트 훅:

  • rangechangeended → 줌/팬 시 레이블 너비를 업데이트합니다.
  • sizechanged → 차트 크기 조정 시 레이블을 조정합니다.

이렇게 하면 레이블이 사용 가능한 공간에 따라 동적으로 크기가 조정됩니다.

더 나은 가독성을 위한 긴 레이블 잘라내기

동적 최대 너비가 설정되어도 일부 레이블이 여전히 너무 길어서 혼잡을 유발할 수 있습니다. 이를 처리하기 위해 레이블을 잘라냅니다.

구현

categoryAxis.renderer.labels.template.adapter.add("textOutput", function(text, target) {
    let maxLabelLength = 10; // 잘라내기 전 최대 문자 수 설정

    if (text.length > maxLabelLength) {
        return text.substring(0, maxLabelLength) + "..."; // 잘라내고 생략 부호 추가
    }
    return text; // 제한 내에 있을 경우 원본 텍스트 반환
});

설명

  • 이 함수는 각 레이블의 길이를 확인합니다.
  • 레이블이 너무 길면, 텍스트를 첫 번째 단어로 잘라내고 ".."를 추가합니다.
  • 최대 문자 수 제한 내에 있는 레이블은 변경되지 않습니다.

이렇게 하면 긴 레이블이 겹치지 않으면서도 충분한 정보를 제공하고, 크기가 조정되거나 확대/축소할 때도 반응성을 유지합니다.

동적 레이블 조정을 적용한 후 최종 결과

해결책을 적용한 후 전체 구현 코드


am4core.ready(function () {
  // 차트 인스턴스 생성
  let chart = am4core.create("chartdiv", am4charts.XYChart);
  chart.hiddenState.properties.opacity = 0; // 초기 페이드 인
  chart.logo.disabled = true;
  chart.responsive.enabled = true; // 반응형 동작 활성화

  // 차트 데이터
  chart.data = [
    { company: "Apple Inc.", visits: 23725 },
    { company: "Microsoft Corporation", visits: 1882 },
    { company: "Alphabet Inc. (Google)", visits: 1809 },
    { company: "Amazon.com, Inc.", visits: 1322 },
    { company: "Tesla, Inc.", visits: 1122 },
    { company: "Meta Platforms, Inc. (Facebook)", visits: 1114 },
    { company: "Berkshire Hathaway Inc.", visits: 711 },
    { company: "PepsiCo, Inc.", visits: 443 },
    { company: "Johnson & Johnson Services, Inc.", visits: 350 },
    { company: "McDonald's Corporation", visits: 298 },
    { company: "NVIDIA Corporation", visits: 150 },
    { company: "Netflix, Inc.", visits: 100 }
  ];

  // 카테고리 축 생성 (X축)
  let categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
  categoryAxis.dataFields.category = "company";
  categoryAxis.renderer.grid.template.location = 0;
  categoryAxis.renderer.minGridDistance = 40;
  categoryAxis.fontSize = 11;
  categoryAxis.renderer.labels.template.wrap = true;
  categoryAxis.renderer.labels.template.maxWidth = 80;
  categoryAxis.renderer.labels.template.padding(10, 0, 0, 0); // 모든 면을 0으로 설정

  // 값 축 생성 (Y축)
  let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
  valueAxis.min = 0;
  valueAxis.max = 24000;
  valueAxis.strictMinMax = true;
  valueAxis.renderer.minGridDistance = 30;

  // 가장 넓은 열에 따라 레이블 최대 너비를 업데이트하는 함수
  const updateLabelMaxWidth = () => {
    let maxColumnWidth = 0;

    // 각 열을 반복하며 최대 너비 찾기
    series.columns.each(function (column) {
      if (column && column.pixelWidth > maxColumnWidth) {
        maxColumnWidth = column.pixelWidth;
      }
    });

    // 레이블에 최대 너비 적용
    categoryAxis.renderer.labels.template.maxWidth = maxColumnWidth;
    console.log("업데이트된 최대 레이블 너비: " + maxColumnWidth);

    // 레이블 재처리 강제
    categoryAxis.invalidateLabels();
  }

  // 줌/팬 시 레이블 너비 업데이트 트리거
  categoryAxis.events.on("rangechangeended", function () {
    updateLabelMaxWidth();
  });

  // 화면 크기 조정 시 레이블 너비 업데이트 트리거
  chart.events.on("sizechanged", function () {
    updateLabelMaxWidth();
  });

  // 필요한 경우 첫 번째 단어 끝에서 레이블 잘라내기
  categoryAxis.renderer.labels.template.adapter.add("textOutput", function (text, target) {
    let labelMaxWidth = target.maxWidth;

    if (labelMaxWidth < 60 && text) { // 레이블 너비가 60픽셀 미만인지 확인
      let words = text.split(" "); // 텍스트를 단어로 나누기
      return words.length > 1 ? words[0] + ".." : text; // 첫 번째 단어만 유지하고 ".." 추가
    }

    return text;
  });

  // 열 시리즈 생성
  let series = chart.series.push(new am4charts.ColumnSeries());
  series.dataFields.categoryX = "company";
  series.dataFields.valueY = "visits";
  series.columns.template.tooltipText = "{valueY.value}";
  series.columns.template.tooltipY = 0;
  series.columns.template.strokeOpacity = 0;

  // 각 열에 다른 색상 적용
  series.columns.template.adapter.add("fill", function (fill, target) {
    return chart.colors.getIndex(target.dataItem.index);
  });

  // 스크롤바 추가
  chart.scrollbarX = new am4core.Scrollbar();
  chart.scrollbarX.marginTop = 30;
  chart.scrollbarX.parent = chart.bottomAxesContainer;

  // 큰 값에 대한 축 분할 추가
  let axisBreak = valueAxis.axisBreaks.create();
  axisBreak.startValue = 2100;
  axisBreak.endValue = 22900;

  // 축 분할 크기 조정
  let d = (axisBreak.endValue - axisBreak.startValue) / (valueAxis.max - valueAxis.min);
  axisBreak.breakSize = 0.05 * (1 - d) / d; // 가시성을 높이기 위해 분할 크기 증가

  // 상호작용 개선을 위한 호버 효과 조정
  let hoverState = axisBreak.states.create("hover");
  hoverState.properties.breakSize = 1; // 호버 시 약간 큰 분할
  hoverState.properties.opacity = 0.1;
  hoverState.transitionDuration = 1500;

  axisBreak.defaultState.transitionDuration = 1000;

}); // end am4core.ready()

이러한 조정을 통해 우리의 차트는 이제 완벽하게 적응합니다—레이블은 읽기 쉬우며, 더 이상 보기 흉한 겹침이 없고, 모든 것이 부드럽게 크기가 조정됩니다. 너비를 동적으로 조정하고, 긴 이름을 잘라내고, 이벤트에 훅을 걸어 차트가 어떤 화면에서도 멋지게 보이도록 했습니다. 이제 직접 시도해 보세요!