Generator, Co, Async 를 이용한 비동기 순차 실행시키기
D3.js 를 사용하여 데이터 시각화하기 #6 Click, Drag 이벤트 사용하기
2020-07-29
Explanation
한동안 일이 많아서 정신이 없었는데, 이제 좀 여유가 생겨서 다시 포스팅을 하려고 했으나,
어김없이 찾아 온 귀차니즘으로 한참을 고민하다…
의식을 부여잡고.
오늘은 D3를 사용한 차트에 Click과 Drag이벤트를 추가하는 것에 대해 적어보려 합니다.
막대 차트는 예전에 만들었던 것에서 스타일을 좀 수정하고 툴팁 부분만 빼봤어요.
https://falsy.me/d3-js-를-사용하여-데이터-시각화하기-4-custom-bar-charts/
이제 여기에 클릭 이벤트를 추가하면 아래와 같아요.
저는 막대를 클릭하면 클릭한 막대에만 색이 변하게 해봤답니다.
간단하게 달라진 부분만 살펴보면,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
... const handleClickRect = (d, i) => { svg.selectAll('rect').each(function(d, j) { if(j === i) d3.select(this).style('fill', pointColor); else d3.select(this).style('fill', baseColor); }); }; ... svg.append('g') .selectAll('rect').data(data).enter().append('rect') .attr('x', d => x(d.name)) .attr('y', d => y(d.value)) .attr('height', d => y(0) - y(d.value)) .attr("rx", 15) .attr('width', x.bandwidth()) .attr('fill', baseColor) .attr('data-x', d => d.name) .attr('data-y', d => d.value) .on("click", handleClickRect); svg.node(); handleClickRect(null, data.length - 1); |
음.. 이벤트를 추가하는건 간단해요. .on()으로 콜백 함수를 등록할 수 있답니다.
그리고 콜백 함수에는 첫번째 인자로 .data()로 추가한 data값이, 그리고 두번째 인자로는 선택된 rect의 index값이 전달 된답니다.
그래서 해당 콜백 함수에서는 svg에 등록되었는 rect를 each로 순회해서 rect가 인자로 받은 index(선택 한 rect의 index)가 순회한 rect의 index가 같을때 해당 rect에 포인트 컬러를 채우고 나머지 rect에는 기본 컬러로 채워주도록 했답니다.
역시 예전에 만들었던 간단한 라인 차트로 시작하려 했다가 저번 글에 사용한 라인 차트로 시작합니다.
참고로 저번에 적은 글은,
https://falsy.me/d3-js-를-사용하여-데이터-시각화하기-5-area-charts-그라데이션
더 옛날에 라인 차트에 대한 글은,
https://falsy.me/d3-js-를-사용하여-데이터-시각화하기-1-line-charts/
저는 여기에 툴팁을 넣고 드래그 이벤트를 넣어서 드래그에 따라서 움직이는 툴팁을 만들어 볼거에요.
조금 복잡할 수 있어요.
우선 결과물을 먼저 보여드리면 아래와 같습니다.
악… ‘scaleTime’으로는 처음 해보는데, x축 첫번째가 왜 안나올까요…
시간이 너무 늦어서, 나머지는 내일 다시 추가하는 것으로…
x축에 첫번째가 안나와서 한참 해맸는데, .nice()를 붙여주면 되네요!
참고. https://www.d3indepth.com/scales/
1 2 3 4 5 6 7 8 |
... const x = d3.scaleTime() .domain(d3.extent(data, d => d.date)) .range([margin.left + 30, width - margin.right]) .nice(); ... |
간단하게 설명을 좀 적어보면,
우선 라인 차트에 경우에는 클릭 이벤트로 툴팁을 표시하기에는 선택 영역이 너무 좁아서 조금 UX적으로 좋지 않기 때문에, 저는 투명한 막대 차트를 뒤에 깔고 거기에 클릭 이벤트를 추가하고 클릭한 데이터의 index 값으로 라인 차트의 해당 index의 y값에 툴팁을 이동시켜 주었습니다.
약간의 이해를 돕기 위해 뒤에 있는 투명 막대 차트를 살짝 표시해보면 아래와 같습니다.
해당하는 부분의 코드를 살펴보면,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
... const maxValue = d3.max(data.map(d => d.value)); // d3의 max 메서드를 사용해서 현재 데이터에사 가장 큰 벨류값을 찾고 const cursorData = data.map((d) => { return { date: d.date, value: maxValue }; }); // 투명 막대차트에 사용될 데이터를 만들어줍니다. value 값은 항상 최대치로 해서 꽉찬 막대그래프를 만들어 줍니다. ... const cursorX = d3.scaleBand() .domain(cursorData.map(d => d.date)) .range([margin.left + 30, width - margin.right]) .padding(0); // 투명한 막대 차트에 사용될 X 값을 만들어주고 padding 값은 0으로해서 막대의 넢이를 해당 영역에 넓게? 꽉체게? 만들어줍니다. const xWidth = cursorX.bandwidth(); // 투명한 막대의 너비값을 미리 구해놓고 svg.append('g') .selectAll('rect').data(cursorData).enter().append('rect') .attr("transform", () => `translate(-${xWidth/2}, 0)`) .attr('x', (d) => x(d.date)) .attr('y', (d) => y(d.value)) .attr('height', (d) => y(0) - y(d.value)) .attr('width', xWidth) .attr('fill', 'rgba(100,100,100,0.3)') .on('click', handleClickShowTooltip); // 이렇게 투명한 막대 차트를 추가해 줍니다. 지금의 fill 값은 약간 보이게 하려고 rgba()를 썼는데, 완전히 투명하게 하기 위해서는 'transparent' 값을 사용해줍니다. // 그리고 클릭 이벤트를 추가해줍니다. ... const showTooltip = (i) => { const targetData = data[i]; const posX = Math.floor(x(targetData.date)) - Math.floor((tooltip.offsetWidth)/2); const posY = Math.floor(y(targetData.value)) - 35; tooltip.innerText = targetData.value; tooltip.style.cssText = ` left: ${posX}px; top: ${posY}px; opacity: 1; `; }; const handleClickShowTooltip = (d, i) => { showTooltip(i); }; // 클릭 이벤트 콜백 함수는 위와 같이 정의했어요. ... |
Drag 이벤트는 차트 영역 전체를 기준으로 동작해야 하니까 이벤트를 svg에 걸어줍니다.
그리고 d3에서 제공하는 drag() 라는 메서드를 사용했습니다.
1 2 3 4 5 |
... svg.call(d3.drag().on("drag", handleDragShowTooltip)); ... |
d3.drag() 참고링크.
https://github.com/d3/d3-drag#api-reference
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
... const showTooltip = (i) => { const targetData = data[i]; const posX = Math.floor(x(targetData.date)) - Math.floor((tooltip.offsetWidth)/2); const posY = Math.floor(y(targetData.value)) - 35; tooltip.innerText = targetData.value; tooltip.style.cssText = ` left: ${posX}px; top: ${posY}px; opacity: 1; `; }; const handleDragShowTooltip = (d, i) => { const targetIdx = Math.floor(d3.event.x/xWidth) - 1; const periodIdx = targetIdx < 0 ? 0 : targetIdx > maxIdx ? maxIdx : targetIdx; showTooltip(periodIdx); }; ... |
그리고 위와 같이 이벤트 콜백 함수를 정의해 주었습니다.
한가지 특이한? 보통 DOM에 연결된 콜백 함수는 event라는 이벤트 객체를 사용할 수 있는데요, D3에서 등록한 이벤트는 ‘d3.event’라는 이벤트 객체를 사용할 수 있답니다.
다 적었는데, 뒤가 너무 허전한 거 같아서…
결과물을 다시 한번 보면 아래와 같답니다.