목표
위 이미지와 같이,
여러 dataset을 하나의 line chart에서 보여주고,
label에 mouse hover했을 때 해당 dataset만 강조하려고 한다.
이미지는 이해를 돕기 위한 예시이다. (출처)
위 예시는 amchart 라이브러리를 사용하여 구현되었으며,
나는 chart.js로 위 기능을 구현하고자 한다.
1차 Solution
- chart option > legend에 onHover, onLeave 이벤트 핸들러 추가
function handleHoverLineLegend(...) {
// hover된 legend 연결 dataset 색상을 강조색으로 변경
// 그 외 dataset 색상을 gray로 변경
logic();
// 변경된 색상을 기반으로 chart update
chart.update();
}
function handleLeaveLineLegend(...) {
// 모든 dataset의 색상을 기본값으로 초기화
logic();
// 초기화된 색상을 기반으로 chart update
chart.update();
}
const chartOption = {
plugins: {
legend: {
onHover: handleHoverLineLegend,
onLeave: handleLeaveLineLegend,
},
},
}
1차 Result
아래 이미지와 같이 매우 잘 동작하는 것을 확인할 수 있다. 깔끔!
문제 상황
chart와 legend 사이의 간격을 조절하고 싶어졌다.
chart js 기본 legend를 사용하면, legend와 chart 사이 간격을 조절할 수 없다.
legend가 canvas 내부에 같이 그려지기 때문이다.
chart option > legend > padding 을 부여하여 gap을 띄울 수 있는데,
legend item 기준 상하좌우 모두로 padding이 먹어버려
legend item간 간격도 벌어지고 legend 위로도 여백이 생겨 보기 좋지 않다.
(paddingLeft 등으로 상하좌우 구분하여 먹일 수 없다 ㅜ)
나만 이런 생각을 한 것은 당연히 아닐테니,, reference를 찾아보았다.
Chart.js 공식 github에 다음과 같은 issue가 있었다. (링크)
아쉽지만 결론은, chart.js의 내장 legend 말고 HTML Legend를 사용하라! 였다.
2차 Solution : HTML Legend
chart.js 공식 문서를 바탕으로, HTML legend를 구현해 보았다.
기존 onHover, onLeave 이벤트 핸들러도 부착했다.
const getOrCreateLegendList = (chart, id) => {
const legendContainer = document.getElementById(id);
let listContainer = legendContainer.querySelector("ul");
if (!listContainer) {
listContainer = document.createElement("ul");
/* set listContainer style */
legendContainer.appendChild(listContainer);
}
return listContainer;
};
export const htmlLegendPlugin = {
id: "htmlLegend",
afterUpdate(chart, args, options) {
const ul = getOrCreateLegendList(chart, options.containerID);
// Remove old legend items
while (ul.firstChild) ul.firstChild.remove();
// Reuse the built-in legendItems generator
const items = chart.options.plugins.legend.labels.generateLabels(chart);
items.forEach((item) => {
const li = document.createElement("li");
/* set li style */
// toggle show each line chart when click legend
li.onclick = () => {...};
// find dataset & highlight
const highlightLine = () => {
someLogic();
chart.update();
};
// reset color for all datasets
const resetLine = () => {
someLogic();
chart.update();
};
li.addEventListener("mouseenter", highlightLine);
li.addEventListener("mouseleave", resetLine);
ul.appendChild(li);
});
},
};
문제 상황 2
기본 기능들은 잘 동작한다.
다만 마우스를 빠르게 이동하여 legend를 스쳐지나갈 때,
hover는 catch하고 leave는 catch하지 못하여
마우스가 chart 바깥에 있음에도 dataset 하나를 계속 강조하고 있게 되는 현상이 발생했다.
원인
정상적인 이벤트 발생 순서는 아래와 같다.
- hover, leave 이벤트 핸들러 부착
- legend mouse hover
- highlight 로직 실행
- highlight 완료 후 chart update 실행
- 이벤트 핸들러 초기화 및 차트 색상 업데이트
- 초기화된 요소에 hover, leave 이벤트 핸들러 재부착
- legend mouse leave
- reset 로직 실행
- reset 완료 후 chart update 실행
- 이벤트 핸들러 초기화 및 차트 색상 업데이트
마우스를 매우 빠르게 이동시켰을 경우,
5번 과정 완료 후 6번 과정이 수행되기 전에 마우스가 이미 요소 바깥에 위치하게 되어
leave 이벤트 핸들러가 이를 catch하지 못하는 것이다.
highlight, reset 로직 내에서 chart.update()를 수행하기 때문에 발생하는 필연적인 결과였다.
(색상 변경 후 chart.update() 를 반드시 수행해야만 차트에 반영된다.)
이를 해결하기 위해 이것저것 시도해 보았으나, 실패였다.
chart.js 의 plugin을 직접 구현한 것이기 때문에,
Chart Plugin Life Cycle을 제대로 이해하고 적절한 시기에 이벤트 핸들러를 지정해주면 될 것 같다고는 생각되나,
그 정확한 시점을 찾을 수 없었다.
일단 이 정도로만 마무리하고, 더 고민해본 다음 해결해 봐야겠다..
Reference
https://github.com/chartjs/Chart.js/issues/6544
https://www.chartjs.org/docs/latest/samples/legend/html.html
'Deep Dive' 카테고리의 다른 글
Webpack 빌드 파일은 어떻게 캐시되어 제공되는가 (with Nginx) (0) | 2023.11.08 |
---|---|
Nginx 에 POST 요청하기 (405 Not Allowed) (0) | 2023.10.22 |
React popup window와 소통하기 - 서드파티 결제 서비스 연동 (1) | 2023.08.27 |
Github 프로필 기술 스택 아이콘 꾸미기 (0) | 2021.08.19 |