언어/Chart.js

[Chart.js] 분산형 차트 4분할로 hover하고 click하기

홍시_코딩기록 2024. 6. 27. 23:52

원래 chart.js 에서 함수를 직접 작성해가면서 기능을 추가했는데

기존 네 영역에도 색을 주고

호버하면 색을 또 주고

클릭하면 또 주고..

코드가 길어질 것 같아서 html 객체를 생성했다.

 

<div class="wrap">
    <div class="chart">
      <ul class="background">
          <li class="group group1"></li>
          <li class="group group2"></li>
          <li class="group group3"></li>
          <li class="group group4"></li>
      </ul>
      <canvas id="myChart"></canvas>
    </div>
    <div class="tab_wrap">
        <div class="tab tab1 active">11번 탭탭탭</div>
        <div class="tab tab2">22번 탭탭탭</div>
        <div class="tab tab3">3번 탭탭탭</div>
        <div class="tab tab4">4번 탭탭탭</div>
    </div>
</div>

.background도 영역 나누기

 

.chart {
    position: relative; 
    width: 700px;
}
.background {
    width: calc(100% - 47px);
    height: calc(100% - 61px);
    left: 27px;
    top: 32px;
    position: absolute;
}
.background > li {
    width: 50%;
    height: 50%;
    position: absolute;
}
.group2 {
    top: 0;
    right: 0;
}
.group3 {
    bottom: 0;
    left: 0;
}
.group4 {
    bottom: 0;
    right: 0;
}
.group.clicked {
    border: 1px solid red;
    box-sizing: border-box;
}
.group1 {background: rgba(0, 0, 0, 0.1);}
.group2 {background: rgba(0, 0, 0, 0.3)}
.group3 {background: rgba(0, 0, 0, 0.4)}
.group4 {background: rgba(0, 0, 0, 0.5)}
.group1.active {background: rgb(250, 128, 114, 0.5);}
.group2.active {background: rgb(250, 128, 114, 0.5)}
.group3.active {background: rgb(250, 128, 114, 0.5)}
.group4.active {background: rgb(250, 128, 114, 0.5)}
#myChart {position: relative; z-index:10; cursor:pointer;}
.tab {display: none; font-size: 40px;}
.tab.active {display: block;}
.tab1 {background: yellow;}
.tab2 {background: blue;}
.tab3 {background: green;}
.tab4 {background: orange;}

. 위치 잘 조정하고 색깔도 주고 잘 슥삭슥삭

 

 

function getCurrentZone(chart, x, y) {
    const { left, top, right, bottom, width, height } = chart.chartArea;

    if (x < left || x > right || y < top || y > bottom) {
        return null;
    } else if (x < left + width / 2 && y < top + height / 2) {
        return 1;
    } else if (x > left + width / 2 && y < top + height / 2) {
        return 2;
    } else if (x < left + width / 2 && y > top + height / 2) {
        return 3;
    } else {
        return 4;
    }
}
function onClickTab(chart, event) {
    let clickedZone = getCurrentZone(chart, event.x, event.y);
    const tabs = document.querySelectorAll('.tab_wrap .tab');

    tabs.forEach(tab => {
        if(tab.classList.contains(`tab${clickedZone}`)) {
            tab.classList.add('active')
        } else {
            tab.classList.remove('active')
        }
    })
    const tabWrap = document.querySelectorAll('.wrap .background li');
    tabWrap.forEach(tab => tab.classList.remove('clicked'));
    const clickTab = document.querySelector(`.background li:nth-child(${clickedZone})`);
    clickTab.classList.add('clicked')


    chart.update('none'); 
} 

function onHoverActive(chart, event) {
    let number = getCurrentZone(chart, event.x, event.y);

    const tabBackground = document.querySelectorAll('.background li');
    const correctZone = event.x >= chart.chartArea.left && 
                        event.x <= chart.chartArea.right &&
                        event.y >= chart.chartArea.top &&
                        event.y <= chart.chartArea.bottom;
    tabBackground.forEach(tab => tab.classList.remove('active'));

    if (number !== null) {
        const background = document.querySelector(`.background li:nth-child(${number})`);
        if (background) {
            background.classList.add('active');
        }
    }
    if (!correctZone) {
        tabBackground.forEach(tab => tab.classList.remove('active'));
    }
}

const data = {
    datasets:[{
        data: [{
                x: -10,
                y: 0
            }, {
                x: 0,
                y: 10
            }, {
                x: 10,
                y: 5
            }, {
                x: 0.5,
                y: 5.5
            }],
            backgroundColor: [
            'red',
            'salmon',
            'orange',
            'green'
         ],
         borderColor: [
            'red',
            'salmon',
            'orange',
            'green'
         ],
         borderWidth: 3,
         usePointStyle: true,
         pointStyle: 'circle',
         clip: false,
         hoverBackgroundColor: ['blue', 'blue', 'blue', 'blue']
    }]
    };                
const config = {
    type: 'scatter',
    data,
    options: {
        responsive: true,
        interaction: {
            intersect: false,
            mode: 'nearest', 
        },
        onClick: function(e) {
            onClickTab(myChart, e);
        },
        layout: {
            padding: {
                right: 20
            }
        },
        scales: {
            y: {
                beginAtZero: true
            },
            x: {
                type: 'linear',
                position: 'bottom',
            }
        }
    },
    plugins: [onClickTab]
}


const ctx = document.getElementById('myChart');
const myChart = new Chart(
    ctx,
    config
);

ctx.addEventListener('mousemove',(e) => {
    e.preventDefault();
    onHoverActive(myChart, e);
});

 

. 원래 차트 옵션 onHover로 이벤트를 줬었는데 마우스가 차트 영역을 떠나도 클래스 active가 사라지지 않는 현상이 있었다.

확인해보니 마우스이벤트 좌표를 잘 잡지 못해서 일어난 현상이어서 onHover말고  이벤트리스너를 이용해서 줬다.

 

 

 

아래는 chart.js를 이용한 방법


 

코드를 다시보니 줄일 수 있는데 내가 길게 쓴 것 같기도..

공부하자..

 

더보기

 


            let mouseX = null;
            let mouseY = null;
            let clickedZone = null;

            function getCurrentZone(chart, x, y) {
                const { left, top, right, bottom, width, height } = chart.chartArea;

                if (x < left || x > right || y < top || y > bottom) {
                    return null;
                } else if (x < left + width / 2 && y < top + height / 2) {
                    return 1;
                } else if (x > left + width / 2 && y < top + height / 2) {
                    return 2;
                } else if (x < left + width / 2 && y > top + height / 2) {
                    return 3;
                } else {
                    return 4;
                }
            }

            function onClickTab(chart, event) {
                clickedZone = getCurrentZone(chart, event.x, event.y);
                const tabs = document.querySelectorAll('.tab_wrap .tab');
               
                tabs.forEach(tab => {
                    if(tab.classList.contains(`tab${clickedZone}`)) {
                        tab.classList.add('active')
                    } else {
                        tab.classList.remove('active')
                    }
                })

                chart.update('none');
            }
            const onHoverBackground = {
                id: 'bgColorArea',
                beforeDraw(chart, args, options) {
                    const {
                        ctx,
                        chartArea: { left, top, width, height }
                    } = chart;
                       
                    ctx.save();

                    const drawZone = (zone, color) => {
                        const halfWidth = width / 2;
                        const halfHeight = height / 2;
                        ctx.fillStyle = color;
                       
                        switch (zone) {
                            case 1:
                                ctx.fillRect(left, top, halfWidth, halfHeight);
                                break;
                            case 2:
                                ctx.fillRect(left + halfWidth, top, halfWidth, halfHeight);
                                break;
                            case 3:
                                ctx.fillRect(left, top + halfHeight, halfWidth, halfHeight);
                                break;
                            case 4:
                                ctx.fillRect(left + halfWidth, top + halfHeight, halfWidth, halfHeight);
                                break;
                        }

                    }
                    const currentZone = getCurrentZone(chart, mouseX, mouseY);
                    if (mouseX <= width && mouseY <=  height) {                        
                        drawZone(currentZone, 'rgba(0,0,0,0.2)')
                    }
                    if (clickedZone !== null) {
                        drawZone(clickedZone, 'skyblue')
                    }
               
                    ctx.restore();
            }
           
        }
 

            const config =  {
              type: 'scatter',
              data: {
                datasets: [{
                    data: [{
                            x: -10,
                            y: 0,
                        }, {
                            x: 0,
                            y: 10,
                        }, {
                            x: 0,
                            y: 10,
                        }, {
                            x: 10,
                            y: 5,
                        }, {
                            x: 0.5,
                            y: 5.5,
                        }],
                     backgroundColor: [
                        'red',
                        'salmon',
                        'blue',
                        'orange',
                        'green'
                     ],
                     borderColor: [
                        'red',
                        'salmon',
                        'blue',
                        'orange',
                        'green'
                     ],
                     borderWidth: 3,
                     usePointStyle: true,
                     pointStyle: 'circle',
                     hoverPointStyle : image,
                     clip: false
                }]
              },
              options: {
                interaction: {
                    intersect: false,
                    mode: 'nearest',
                },
                onHover: function(event, chartElement) {

                    const canvasPosition = Chart.helpers.getRelativePosition(event, myChart);
                    const chartArea = myChart.chartArea;
                                       
                    if (
                        canvasPosition.x >= chartArea.left &&
                        canvasPosition.x <= chartArea.right &&
                        canvasPosition.y >= chartArea.top &&
                        canvasPosition.y <= chartArea.bottom
                    ) {
                        event.native.target.style.cursor = 'pointer';
                    } else {
                        event.native.target.style.cursor = 'default';
                    }

                    mouseX = canvasPosition.x;
                    mouseY = canvasPosition.y;
                    myChart.update('none');
                },
                onClick: function(e) {
                    onClickTab(myChart, e);
                },
                layout: {
                    padding: {
                        right: 20
                    }
                },
                scales: {
                    y: {
                        beginAtZero: true
                    },
                    x: {
                        type: 'linear',
                        position: 'bottom',
                    }
                },      
                plugins: {
                    tooltip: {
                        enabled: false,
                        events: ['mousemove'],
                        external: pluginCustomTooltip,
                       
                    }
                }        
              },
              plugins: [onHoverBackground]
            };
            const ctx = document.getElementById('myChart');
            const myChart = new Chart(
                ctx,
                config
            );