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

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

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


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

Html Canvas (Html 캔버스) 튜토리얼 (차트만들기!) - 20 : 원 차트 3

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

저번시간에는 이벤트를 부여하는 첫번째 방법으로 반지름 이내에 마우스가 들어온 경우에 대해서 감지하는 방법까지 알아 보았다.

이번시간에는 각 그려진 부채꼴 영역별로 감지된 도(degree)를 통해서 범위내에 들어온 경우에 대해서 살펴보도록 하겠다.

앞 시간 마지막 부분에서 아크탄젠트(탄젠트의 역함수)를 통해 중심점부터 마우스가 클릭한 곳 까지의 라디안 값을 구한뒤에 도(degree) 값으로 변환하였다.

 

해당 값을 보면 값이 -180 ~ 180 사이의 값이 나오게 되므로 이를 사용하기 쉽게 하려면 +180을 더해주면 된다.

그러면 0~ 360 값이 나오게 되므로 해당 값을 가지고 부채꼴 영역에 들어왔는지 판별하면 된다.

 isInsideArc함수를 수정하여보자.

function isInsideArc(x1, y1){
    var result1 = false;
    var result2 = false;
    var index = -1;
    var circle_len = radius;
    var x = width/2 - x1;
    var y = height/2 - y1;
    var my_len = Math.sqrt(Math.abs(x * x) + Math.abs(y * y));  //삼각함수
    if(circle_len >= my_len){
        result1 = true;
    }            
    
    var rad = Math.atan2(y, x);
    rad = (rad*180)/Math.PI;  //음수가 나온다
    rad += 180;  //캔버스의 각도로 변경

    if(result1){
        event_array.forEach( (arr,idx) => {   //각도 범위에 해당하는지 확인
            if( rad >= arr[0] && rad <= arr[1]){
                result2 = true;
                index = idx;
            }
        });
    }
    return {result1:result1, result2:result2 ,index:index, degree : rad};
}

 

반지름에 들어온경우 event_array를 반복문을 통해서 해당 값이 범위에 있는지 확인하도록 변경 하였다.

event_array 값은 거의 맨 처음 시간에 했던 내용으로, 시작각과 종료각이 존재하는 배열이다.

그러면 해당코드를 수정한뒤에 실행시켜보자.

반지름보다 작거나같고, 범위내에 들어오게 되면 index값이 해당 배열의 인덱스로 바뀐다.

 

BAR 차트에서도 사용했던 방법처럼 마우스가 해당 조건에 모두 만족하면 도형을 변형시켜보도록 하자.

반지름 값을 키우면 도형의 크기가 커지고, 색깔을 변하게해서 시각적인 효과를 부여해보자.

 

이벤트 조건에 모두 만족한다면 그리는 방법은 처음 부채꼴을 그리는 방법과 거의 동일하게 하여주면 된다.

hoverCanvas라는 함수를 만들어보자.

function hoverCanvas(index){
    ctx.clearRect(0,0,width, height);  //지우기
    for (var i = 0; i < conv_array.length; i++) {  //다시그리기
        var item = conv_array[i];
        ctx.save();
        ctx.beginPath();
        var innRadius = radius;
        ctx.moveTo(width / 2 , height / 2 );
        if(index == i){  //대상이면 색칠 및 크기조정
            ctx.lineWidth = 2;
            ctx.strokeStyle='blue';
            innRadius = radius * 1.1;
        } 
        if (i == 0) {
            ctx.arc(width / 2, height / 2, innRadius, (Math.PI / 180) * 0, (Math.PI / 180) * item, false);
            degree = item;  //초기화
        } else {
            ctx.arc(width / 2, height / 2, innRadius, (Math.PI / 180) * degree, (Math.PI / 180) * (degree + item), false);
            degree = degree + item;
        }
        ctx.closePath();
        ctx.stroke();
        ctx.restore();
    }
}

 

해당 함수를 addEventListener 부분에 추가하여주면 작업은 끝이났다.

아래는 최종 소스코드이다.    * click이벤트를 mousemove로 바꾸었다.

<!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;}
</style>
<body>
    <canvas width='400' height='400' id='canvas'></canvas>
</body>
</html>

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

    var value = [100, 100, 100];
    var degree = 360;
    const radius = 50;

    var sum = value.reduce((a, b) => a + b);
    var conv_array = value.slice().map((data)=>{
        var rate = data / sum;
        var myDegree = degree * rate;
        return myDegree;
    });


    degree = 0;
    var event_array = value.slice().map( arg=> []);
    for(var i=0;i < conv_array.length;i++){
        var item = conv_array[i];
        ctx.save();
        ctx.beginPath();
        ctx.moveTo(width/2, height/2);
        if(i == 0){
            ctx.arc(width/2, height/2, radius, (Math.PI/180)*0, (Math.PI/180)* item , false);
            degree = item;
            event_array[i] = [0, degree];
        } else {
            ctx.arc(width/2, height/2, radius, (Math.PI/180)*degree, (Math.PI/180)*(degree + item), false);
            event_array[i] = [degree, degree+item];
            degree =  degree + item;
        }
        ctx.closePath();
        ctx.stroke();
        ctx.restore();
    }

    var drawed = false;
    canvas.addEventListener('mousemove', function (event) {
        var x1 = event.clientX - canvas.offsetLeft;
        var y1 = event.clientY - canvas.offsetTop;
        var inn = isInsideArc(x1, y1);
        //console.log(inn);
        if(inn.index > -1){
            drawed = true;
            hoverCanvas(inn.index);
        } else {
            if(drawed){
                hoverCanvas(-1);
            }
            drawed = false;
        }
    }); 

    function isInsideArc(x1, y1){
        var result1 = false;
        var result2 = false;
        var index = -1;
        var circle_len = radius;
        var x = width/2 - x1;
        var y = height/2 - y1;
        var my_len = Math.sqrt(Math.abs(x * x) + Math.abs(y * y));  //삼각함수
        if(circle_len >= my_len){
            result1 = true;
        }            
        
        var rad = Math.atan2(y, x);
        rad = (rad*180)/Math.PI;  //음수가 나온다
        rad += 180;  //캔버스의 각도로 변경

        if(result1){
            event_array.forEach( (arr,idx) => {   //각도 범위에 해당하는지 확인
                if( rad >= arr[0] && rad <= arr[1]){
                    result2 = true;
                    index = idx;
                }
            });
        }
        return {result1:result1, result2:result2 ,index:index, degree : rad};
    }

    function hoverCanvas(index){
        ctx.clearRect(0,0,width, height);  //지우기
        for (var i = 0; i < conv_array.length; i++) {  //다시그리기
            var item = conv_array[i];
            ctx.save();
            ctx.beginPath();
            var innRadius = radius;
            ctx.moveTo(width / 2 , height / 2 );
            if(index == i){  //대상이면 색칠 및 크기조정
                ctx.lineWidth = 2;
                ctx.strokeStyle='blue';
                innRadius = radius * 1.1;
            } 
            if (i == 0) {
                ctx.arc(width / 2, height / 2, innRadius, (Math.PI / 180) * 0, (Math.PI / 180) * item, false);
                degree = item;  //초기화
            } else {
                ctx.arc(width / 2, height / 2, innRadius, (Math.PI / 180) * degree, (Math.PI / 180) * (degree + item), false);
                degree = degree + item;
            }
            ctx.closePath();
            ctx.stroke();
            ctx.restore();
        }
    }
    
</script>
    

 

완성된 모습이다.

 

 

다음시간에는 차트 내부에 텍스트를 입히는 방법에 대해서 알아보도록 하겠다.

 

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

댓글