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

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

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


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

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

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

 

이번시간에는 텍스트를 실제로 넣어보도록 소스코드를 변경해보겠다.

기존에 만들었던 반복문을 함수로 바꾼뒤에 계산식을 조근 변경하고 strokeRect 함수 부분을 텍스트로 바꾸어주자.

텍스트 그리는 방법은 예전 방법과 동일하다.

길이를 계산해서 빼 주는 방법이 추가가 되어야 중앙정렬된 모습이 나타나게 된다.

function makeText(index){
    event_array.forEach((itm, idx) => {
        var half = (itm[1] - itm[0]) / 2;
        var degg = itm[0] + half;
        var xx = Math.cos(degreesToRadians(degg)) * radius * 0.7 + width / 2;
        var yy = Math.sin(degreesToRadians(degg)) * radius * 0.7 + height / 2;

        var txt = value[idx] + '';
        var minus = ctx.measureText(txt).width / 2;  //텍스트 절반길이
        ctx.save();
        if(index == idx){
            ctx.font = "normal bold 20px serif";
            ctx.fillStyle = 'blue';
        } else {
            ctx.font = "normal 14px serif";
        }
        ctx.fillText(txt, xx - minus, yy);
        ctx.restore();
    });
}

 

여기서 0.7값을 곱해준 이유는 반지름 값을 적게하면 원 내부로 가까워지는 효과를 줄 수 있기 때문이다.

 

해당함수를 isInsideArc 함수에 달아주면 작업은 끝이나게 된다.

위 내용에 대한 최종 소스코드이다.

<!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, 200, 300, 200];
    var degree = 360;
    var radius = width * 0.7 / 2;

    if(radius > height * 0.7 / 2){
        radius = height * 0.7 / 2;
    }



    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);
            makeText(inn.index);
        } else {
            if(drawed){
                hoverCanvas(-1);
                makeText(-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();
        }
    }


    //도(degree)를 라디안(radian)으로 바꾸는 함수
    function degreesToRadians(degrees) {
        const pi = Math.PI;
        return degrees * (pi / 180);
    }

    function makeText(index){
        event_array.forEach((itm, idx) => {
            var half = (itm[1] - itm[0]) / 2;
            var degg = itm[0] + half;
            var xx = Math.cos(degreesToRadians(degg)) * radius * 0.7 + width / 2;
            var yy = Math.sin(degreesToRadians(degg)) * radius * 0.7 + height / 2;

            var txt = value[idx] + '';
            var minus = ctx.measureText(txt).width / 2;  //텍스트 절반길이
            ctx.save();
            if(index == idx){
                ctx.font = "normal bold 20px serif";
                ctx.fillStyle = 'blue';
            } else {
                ctx.font = "normal 14px serif";
            }
            ctx.fillText(txt, xx - minus, yy);
            ctx.restore();
        });
    }
    makeText(-1);  //최초 호출

</script>
    

 

위 내용을 실행하면 이제 제법 그럴싸한 원형차트의 모습이 그려지게된다.

 

여기까지 원형차트를 그리는 방법에 대해서 살펴보았다.

실제로 캔버스는 대부분의 이벤트와 그리는 효과에 대해서 자동(?)으로 기능을 제공해주지 않는다.

Jquery에서 사용한 fadeIn함수, fadeOut함수라던지..animate 함수라던지..

이런 모든 함수의 기능을 직접 수학적인 방법으로 만들어야 하는 것 이다.

다음시간에는 실제로 여러가지 효과를 만들어본 원형차트를 한번 살펴 보도록 하자.

 

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

댓글