D3로 시계 만들기

“소프트웨어 개발이 좋은 사람”이라는 블로그에서 d3를 이용해 아날로그 시계를 만든 글을 보고 코드를 살펴 보았습니다. d3는 개인적으로도 관심이 많고, 상당이 깊이 있게 공부를 했던(하지만 활용력은 아직까지도 얇은..) 자바스크립트 기반의 데이터 시각화 라이브러리입니다. 그 결과는 아래와 같습니다.

위의 시계에 대한 코드를 d3의 학습겸 해서 정리해 보고자 합니다. 먼저 시계에 대한 뷰는 아래처럼 오직 svg 태그 하나입니다.


시계의 크기에 해당하는 이 svg의 크기를 지정하기 위해 다음처럼 스타일을 지정합니다.

#clock {
    width: 400px;
    height: 400px;
}

시계의 구성요소는 시계의 형태인 큰 동그라미 부분, 12개의 숫자 부분, 시분초에 대한 바늘 부분으로 구성됩니다. 시분초에 대한 바늘은 Timer를 사용해 1초마다 갱신해 진짜 시계처럼 움직이도록 합니다.

스크립트 코드를 작성해 볼 것인데요. 가장 먼저 앞서 추가해둔 svg 요소를 d3의 선택자를 이용해 선택해 둡니다.

var clock = d3.select("#clock");

var center = parseInt(clock.style("height")) / 2;
var radius = center;

위의 코드는 추가적으로 svg의 높이값의 반값으로 중심점을 위한 center 변수와 시계의 반지름 크기값을 위한 radius 변수도 정의되어 있습니다.

앞서 시계는 3개의 부분으로 구성된다고 하였는데요. 다시 언급하면 시계의 형태인 큰 동그라미 부분, 12개의 숫자 부분, 시분초에 대한 바늘 부분입니다. 먼저 시계의 형태인 큰 동그라미 부분을 그리는 함수 drawFace를 반지름값(radius 변수)을 인자로 해 호출합니다.

drawFace(radius);

function drawFace(radius) {
    clock.append("circle")
        .attr("cx", center)
        .attr("cy", center)
        .attr("r", radius)
        .attr("class", "face");

    clock.append("circle")
        .attr({ 
            "cx": center, 
            "cy": center, 
            "r": radius * 0.1, 
            "fill": "#000" 
        });
}

위의 코드에서 face라는 class를 통해 스타일을 지정하고 있는데요. 이 .face에 대한 스타일 정의는 아래와 같습니다.

.face {
    fill : #FFF;
    stroke-width: 2px;
    stroke: #000;
}

또 아래는 12개의 숫자 부분을 구성하는 코드입니다.

drawNumbers(radius);

function drawNumbers(radius) {
    var pos = radius * 0.85;
    for (var num = 1; num < 13; num++) {
        var deg = 30 * num;
        var x = pos * Math.cos(Math.PI * (deg - 90) / 180);
        var y = pos * Math.sin(Math.PI * (deg - 90) / 180);
        var cx = x + center;
        var cy = y + center;
        var text = clock.append("text")
            .attr({ "x": cx, "y": cy, "class": "number" })
            .text(num)
            .style("font-size", radius * 0.15 + "px")
            .attr("transform", "rotate(" + deg + ", " + cx + ", " + cy + ")");
    }
}

위의 코드에서 number라는 class를 통해 숫자의 문자 스타일을 지정하고 있는데요. 이 .number에 대한 스타일 정의는 아래와 같습니다.

.number {
    font-family: arial;
    text-anchor: middle;
    text-align: center;
}

또 아래는 시, 분, 초에 대한 바늘을 구성하는 코드입니다. 시침, 분침, 초침을 각각 hourHand, minuteHand, secondHand 변수에 저장하고 있는데요. 이 변수를 통해 1초마다 각 침의 형태를 변경해 주게 됩니다.

var hourHand = drawHand(0, 0, radius*0.07);
var minuteHand = drawHand(0, 0, radius*0.05);
var secondHand = drawHand(0, 0, radius*0.01);

function drawHand(x, y, width) {
    var hand = clock.append("line")
        .attr({ 
            "x1": center, 
            "y1": center, 
            "x2": x + center, 
            "y2": y + center, 
            "class": "hands" })
        .style("stroke-width", width);

    return hand;
}

위의 코드에서 hand라는 class를 통해 바늘의 스타일을 정의하고 있습니다. 이 .hand에 대한 스타일 정의는 아래와 같습니다.

.hands {
    stroke: #000;
    stroke-linecap: round;
}

이제 1초마다 제 시간에 맞게 바늘을 움직도록 하는 코드는 아래와 같습니다.

drawClock();

function drawClock() {
    drawTime(radius);
    setTimeout(drawClock, 1000);
}

위의 코드에서 1초마다 실행되는 drawTime의 함수가 보이는데요. 바로 이 drawTime이 3개의 바늘을 현재 시간에 맞게 그 형상을 변경해 주는 함수이며 아래와 같습니다.

function drawTime(radius){
    var now = new Date();
    var hour = now.getHours();
    var minute = now.getMinutes();
    var second = now.getSeconds();
	
    //hour
    var pos = radius * 0.5;
    x = pos * Math.cos(Math.PI*((hour*30)-90+30/60*minute+30/60/60*second)/180); 
    y = pos * Math.sin(Math.PI*((hour*30)-90+30/60*minute+30/60/60*second)/180);
    hourHand.attr({"x1": center, "y1": center, "x2": x+center, "y2": y+center});

    //minute
    pos = radius*0.65;
    x = pos * Math.cos(Math.PI*((minute*6)-90+6/60*second)/180); 
    y = pos * Math.sin(Math.PI*((minute*6)-90+6/60*second)/180);
    minuteHand.attr({"x1": center, "y1": center, "x2": x+center, "y2": y+center});
	
    // second
    pos = radius*0.9;
    x = pos * Math.cos(Math.PI* ((second*6)-90)/180); 
    y = pos * Math.sin(Math.PI* ((second*6)-90)/180);
    secondHand.attr({"x1": center, "y1": center, "x2": x+center, "y2": y+center});
}

이상으로 d3를 이용해 svg 요소를 시계로 만들어 보는 코드를 살펴보았습니다.

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다