소개
막대 차트의 레이블은 차트 크기가 조정되거나, 다양한 화면 해상도에서 표시되거나, 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를 가지고 있습니다. 그러나 이 너비는 동적으로 조정되지 않기 때문에 열 너비와 차트 크기에 따라 계산해야 합니다.
이를 달성하기 위해 rangechangeended 및 sizechanged 이벤트에 훅을 걸고, 열의 너비를 확인한 후 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()
이러한 조정을 통해 우리의 차트는 이제 완벽하게 적응합니다—레이블은 읽기 쉬우며, 더 이상 보기 흉한 겹침이 없고, 모든 것이 부드럽게 크기가 조정됩니다. 너비를 동적으로 조정하고, 긴 이름을 잘라내고, 이벤트에 훅을 걸어 차트가 어떤 화면에서도 멋지게 보이도록 했습니다. 이제 직접 시도해 보세요!