본문 바로가기
블로그 이미지

방문해 주셔서 감사합니다! 항상 행복하세요!

  
   - 문의사항은 메일 또는 댓글로 언제든 연락주세요.
   - "해줘","답 내놔" 같은 질문은 답변드리지 않습니다.
   - 메일주소 : lts06069@naver.com


Html 캔버스/Html 캔버스 튜토리얼 (내가만든 차트!)

Html Canvas (Html 캔버스) 튜토리얼 (차트만들기!) - 17 : 바 차트 5

야근없는 행복한 삶을 위해 ~
by 마샤와 곰 2020. 2. 20.

 

이번시간에는 툴팁 효과를 나타내는 기능을 만들어 보겠다.

툴팁은 사실 그리 어려운 기능이 아니다. 여태껏 만들어온 방법과 거의 비슷하게 만들면 된다.

툴팁을 만들 개념을 정리하여보자.

1. 툴팁은 div테그로 만든다.

2. 툴팁은 position이 absolute이며 마우스가 움직일 때 마다 따라다니게 한다.

3. 차트 영역에 들어오면 툴팁을 보이게하고, 차트영역에 벗어나면 툴팁을 가린다.

 

3가지 개념으로 툴팁에 대한 작업을 해 주면 된다.

먼저 툴팁테그를 만들어준다.

 

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Canvas</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<style>
    body{padding: 5%;text-align: center;}
    canvas{border: 1px solid gray;border-radius: 3px;}
    #im_hover{
        width: 50px;
        height: auto;
        padding : 5px;
        border: 1px solid rgb(177, 177, 177);
        position: absolute;
        display: none;
        background: rgba(111,231,43,0.5);
        border-radius: 5px;
    }
</style>
<body>
    <canvas width='400' height='400' id='canvas'></canvas>
    <div id='im_hover'></div>
</body>
</html>
<script>
//...생략
</script>

 

요렇게 해주면 툴팁 테그는 처음에는 보이지 않는 상태로 존재하지만 absolute 속성이 있기 때문에 어디든 자유로이 나타낼 수 있다.

다음으로 툴팁함수를 만들어주자.

function toolTipMaker(text, pos_x, pos_y, onOff){
    var hover = document.getElementById('im_hover');
    if(!onOff){
        hover.style.display = 'none';
        hover.innerHTML = '';
    } else {
        hover.style.display = 'block';
        hover.style.left =  pos_x + pos_x*0.02; //너무 딱 달라붙어서 간격을 주었다.
        hover.style.top = pos_y + pos_y*0.02; //너무 딱 달라붙어서 간격을 주었다.
        hover.innerHTML = text;
    }
}

 

위함수는 툴팁이 보이는 상황이면 display를 block으로 하여주고 마우스의 위치값을 넣어서 나타나게 하였다.

툴팁이 보이지 않아야할 상황에서는 display를 none으로 해 주었다.

 

여기서는 캔버스에서 사용한 위치값 빼주는 방법을 쓰지 않았다.

그냥 그대로 사용해야 실제 마우스의 위치값이기 때문이다.

캔버스가 html로부터 떨어진 값을 빼 주면 엄한곳에 나타나므로 주의하자.

툴팁은 마우스를 따라다니는 개념이니까 말이다.

 

위 코드를 어디에 적용시켜야 하는걸까?

바로 기존에 mousemove 이벤트를 준 구간에 추가만 하면 간단하게 끝이난다.

var drawed = false;
canvas.addEventListener('mousemove', function (event) {
    var x1 = event.clientX - canvas.offsetLeft;
    var y1 = event.clientY - canvas.offsetTop;        
    var inn = isInsideRect(x1, y1);
    //console.log(inn);
    if (inn.result) {
        drawed = true;
        hoverDrawing(inn.index);
        toolTipMaker(inn.value, event.clientX, event.clientY, true);  //요렇게 추가!
    } else {
        if(drawed){
            hoverDrawing(-1);
            toolTipMaker(0, 0, 0, false);  //초기화, 가려주기
        }
    }
}); 

 

해당 함수를 넣고 실행하여보자.

잘 따라다닌다!!

 

 

그리고 자주보이는, 중복되어서 계산이되는 비율과 관련된 내용은 commonCal이라는 함수를 만들어서 코드를 조금 더 다듬어 보았다.

아래코드가 최종 완성본이다.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Canvas</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<style>
    body{padding: 5%;text-align: center;}
    canvas{border: 1px solid gray;border-radius: 3px;}
    #im_hover{
        width: 50px;
        height: auto;
        padding : 5px;
        border: 1px solid rgb(177, 177, 177);
        position: absolute;
        display: none;
        background: rgba(111,231,43,0.5);
        border-radius: 5px;
    }
</style>
<body>
    <canvas width='400' height='400' id='canvas'></canvas>
    <div id='im_hover'></div>
</body>
</html>

<script>    
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
   
    var width = canvas.clientWidth;
    var height = canvas.clientHeight;

    var position = {
        min_x : width * 0.1,
        max_x : width * 0.9,
        min_y : height * 0.1,
        max_y : height * 0.9
    }
    var wid = 50;

    var value = [90, 50, 60];
    function drawing(){
        var virtualVal = value.slice().map((arg)=>1); 
        var adder = 0;
        var interval = setInterval(() => {
            ctx.clearRect(0, 0, width, height);  //추가
            virtualVal.forEach( (data, idx) => {
                var devide = idx / value.length ;
                if(value[idx] > data){
                    data++;
                    virtualVal[idx] = data;   
                }
                var my_pos = commonCal(devide, data);
                ctx.strokeRect(my_pos[0], my_pos[1], my_pos[2],my_pos[3]);
            });  
            var chekcer = virtualVal.slice().map((arg)=>false);
            virtualVal.forEach( (data1, idx1) => {
                value.forEach( (data2, idx2) => {
                    if(data1 >= data2 && idx1 == idx2){
                        chekcer[idx1] = true;
                    }
                });
            }); 
            var breaker = true;
            chekcer.forEach( arg => {
                if(!arg){
                    breaker = false;
                }
            });     
            if(breaker){
                console.log('fin');
                clearInterval(interval);
                makeText()
            }
        }, 10);        
    }

    drawing();

    function makeText(){
        value.forEach((data, idx) =>{
            var devide = idx / value.length;
            var len = (wid/2) - ctx.measureText(data+'').width / 2;
            ctx.strokeText(data, position.min_x + position.max_x*devide + len, position.max_y + position.min_y/2);
        });        
    }
    
    function hoverDrawing(hoverIdx){
        ctx.clearRect(0,0,width, height);
        value.forEach( (data, idx) => {
            ctx.save();
            if(idx == hoverIdx){
                ctx.strokeStyle = 'blue';
            }
            var devide = idx / value.length;
            var my_pos = commonCal(devide, data);
            ctx.strokeRect(my_pos[0], my_pos[1], my_pos[2],my_pos[3]);            
            ctx.restore();
        });      
        makeText();  
    }

    var drawed = false;
    canvas.addEventListener('mousemove', function (event) {
        var x1 = event.clientX - canvas.offsetLeft;
        var y1 = event.clientY - canvas.offsetTop;        
        var inn = isInsideRect(x1, y1);
        //console.log(inn);
        if (inn.result) {
            drawed = true;
            hoverDrawing(inn.index);
            toolTipMaker(inn.value, event.clientX, event.clientY, true);
        } else {
            if(drawed){
                hoverDrawing(-1);
                toolTipMaker(0, 0, 0, false);
            }
        }
    }); 

    function isInsideRect(x1, y1){
        var result = false;
        var index = -1;
        var showValue = -1;
        for(var i=0; i < value.length;i++){
            var data = value[i];

            var devide = i / value.length ;
            var start_x = position.min_x + position.max_x*devide;
            var end_x = start_x + wid;

            var start_y = position.max_y* ( 1-(data/100) );
            var end_y = position.max_y ; 
            
            if(x1 >= start_x && x1 <= end_x){  

                if(y1 >= start_y && y1 <= end_y){
                    result = true;
                    index = i;
                    showValue = data;
                    break;
                }
            } 
        }
        return {result:result, index:index, value:showValue};
    }

    function toolTipMaker(text, pos_x, pos_y, onOff){
        var hover = document.getElementById('im_hover');
        if(!onOff){
            hover.style.display = 'none';
            hover.innerHTML = '';
        } else {
            hover.style.display = 'block';
            hover.style.left =  pos_x + pos_x*0.02; //너무 딱 달라붙어서 간격을 주었다.
            hover.style.top = pos_y + pos_y*0.02; //너무 딱 달라붙어서 간격을 주었다.
            hover.innerHTML = text;
        }
    }
    

    function commonCal(devide, data){
        return [
            position.min_x + position.max_x*devide, 
            position.max_y* ( 1-(data/100) ), 
            wid, 
            position.max_y - position.max_y* ( 1-(data/100) )
        ];
    }

</script>
    

 

이제 바차트에서 남은 것을 정리하여보자.

1. 화면 사이즈가 변할때의 이벤트 처리(다시그리기, 스크롤이 생긴후의 마우스 위치값 계산하는 방법 등)

2. 바 크기가 고정값이였던 것을 데이터량에 따라 동적으로 부여하는 방법

3. 이쁘게 꾸미는 것(?)

 

위 3가지에 대해서는 직접 고민해서 완성하여보자.

여기까지 Bar 차트의 기본적인 만들기 방법에 대해서 살펴보았다.

다음시간에는 원형 차트를 한번 만들어 보도록 하겠다.

실제 구동모습이다.

 * 툴팁은 Iframe 때문에 잘 안맞게 나오므로 참고하자.

 

반응형
* 위 에니메이션은 Html의 캔버스(canvas)기반으로 동작하는 기능 입니다. Html 캔버스 튜토리얼 도 한번 살펴보세요~ :)
* 직접 만든 Html 캔버스 애니메이션 도 한번 살펴보세요~ :)

댓글