본문 바로가기
JavaScript/JQuery

[차트API] highcharts wind barb(바람 깃) 그리기

by amoomar 2024. 11. 25.
반응형

 

해당 포스팅에서는 상단 이미지 예시와 같이, 풍속 값을 기반으로 하여 바람 깃 자료를 svg형태로 그려내고 풍향 값을 참고하여 해당 svg를 회전시켜 wind barb를 highcharts 내 표출하는 방법에 대해 다루었다.

 

목차는 다음과 같이 하였다.

1. wind barb 정의
   1) 바람 깃 svg 처리 메서드
   2) svg 적용 메서드
   3) 로드 예시
2. highchart 표출
3. 구현 예시

 


 

1. wind barb 정의

바람 깃 생성을 위해 연관 작업을 처리하는 메서드를 3개로 나누어 작업하였다. 아래 목록화 된 목차 제목이 각 메서드 대한 내용이며, 연계처리하여 load메서드 실행->바람 깃 생성->바람 깃 svg 생성 순으로 호출되도록 하였다. 즉, 차트 객체 정의 혹은 차트 생성 시, 해당 차트에서 정의한 load메서드를 실행하면 wind barb를 그려낼 수 있다. 더 자세한 내용은 목차 순으로 상세하게 정리하였다.

function Meteogram() {} //로직 정의 전, 생성자 함수 필수

 

 

1) 바람 깃 svg 처리 메서드

아래 메서드는, 풍속 값을 전달 받아 값에 해당하는 모양으로 svg값을 path배열 객체로 반환하는 로직이다. 우선 주석과 함께 아래 코드를 훑어본 뒤 밑 단락에서 추가적인 설명을 첨부하겠다. iswindprofiler인자의 경우는 본인의 경우 차트 타입에 따라 바람깃의 표출을 상이하도록 처리하기 위해 추가한 인자이므로, 필요에 따라 생략해도 무관하다.

Meteogram.prototype.windArrow = function(speed, iswindprofiler) {
    var scale = 1.2; // 크기 배율
    var path = [];
    if(!iswindprofiler){ //머리포함
        path = [
            'M', 0, 7* scale,
            'L', -1.5* scale, 7* scale,
            0, 10* scale,
            1.5* scale, 7* scale,
            0, 7* scale,
            0, -10* scale
        ];
    }else{// 머리 미포함
        path = [
            'M', 0 * scale, 7 * scale, // 줄기 시작점
            'L', 0 * scale, -10 * scale // 줄기 끝점
        ];
    }

    speed=Math.round(speed)/1.944 //반올림한 풍속을 kt to m/s단위로 변환
    if (speed <= 1) {
        path = []; // 바람 속도가 낮을 경우 아무것도 그리지 않음
    } else if (speed < 5) {
        path.push('M', 0 * scale, -8 * scale, 'L', 4 * scale, -8 * scale); // 짧은 선
    } else if (speed < 7) {
        path.push('M', 0 * scale, -10 * scale, 'L', 7 * scale, -10 * scale); // 긴 선
    } else if (speed < 10) {
        path.push('M', 0 * scale, -7 * scale, 'L', 4 * scale, -7 * scale); // 짧은 선
        path.push('M', 0 * scale, -10 * scale, 'L', 7 * scale, -10 * scale); // 긴 선
    } else if (speed < 12.8) {
        path.push('M', 0 * scale, -7 * scale, 'L', 7 * scale, -7 * scale); // 긴 선
        path.push('M', 0 * scale, -10 * scale, 'L', 7 * scale, -10 * scale); // 긴 선
    } else if(speed < 15){
        path.push('M', 0 * scale, -4 * scale, 'L', 4 * scale, -4 * scale); // 짧은 선
        path.push('M', 0 * scale, -7 * scale, 'L', 7 * scale, -7 * scale); // 긴 선
        path.push('M', 0 * scale, -10 * scale, 'L', 7 * scale, -10 * scale); // 긴 선
    } else if(speed < 18){
        path.push('M', 0 * scale, -4 * scale, 'L', 7 * scale, -4 * scale); // 긴 선
        path.push('M', 0 * scale, -7 * scale, 'L', 7 * scale, -7 * scale); // 긴 선
        path.push('M', 0 * scale, -10 * scale, 'L', 7 * scale, -10 * scale); // 긴 선
    } else if(speed < 20){
        path.push('M', 0 * scale, -1 * scale, 'L', 4 * scale, -1 * scale); // 짧은 선
        path.push('M', 0 * scale, -4 * scale, 'L', 7 * scale, -4 * scale); // 긴 선
        path.push('M', 0 * scale, -7 * scale, 'L', 7 * scale, -7 * scale); // 긴 선
        path.push('M', 0 * scale, -10 * scale, 'L', 7 * scale, -10 * scale); // 긴 선
    } else if(speed < 23){
        path.push('M', 0 * scale, -1 * scale, 'L', 7 * scale, -1 * scale); // 긴 선
        path.push('M', 0 * scale, -4 * scale, 'L', 7 * scale, -4 * scale); // 긴 선
        path.push('M', 0 * scale, -7 * scale, 'L', 7 * scale, -7 * scale); // 긴 선
        path.push('M', 0 * scale, -10 * scale, 'L', 7 * scale, -10 * scale); // 긴 선
    } else if(speed < 25.5){
        path.push('M', 0 * scale, 2 * scale, 'L', 4 * scale, 2 * scale); // 짧은 선
        path.push('M', 0 * scale, -1 * scale, 'L', 7 * scale, -1 * scale); // 긴 선
        path.push('M', 0 * scale, -4 * scale, 'L', 7 * scale, -4 * scale); // 긴 선
        path.push('M', 0 * scale, -7 * scale, 'L', 7 * scale, -7 * scale); // 긴 선
        path.push('M', 0 * scale, -10 * scale, 'L', 7 * scale, -10 * scale); // 긴 선
    } else if(speed < 28){
        path.push('M', 0 * scale, -7 * scale, 'L', 7 * scale, -12 * scale, 'L', 0  * scale, -10  * scale) //삼각형
    } else if(speed < 30.5){
        path.push('M', 0 * scale, -4 * scale, 'L', 4 * scale, -7 * scale); // 짧은 선
        path.push('M', 0 * scale, -7 * scale, 'L', 7 * scale, -12 * scale, 'L', 0  * scale, -10  * scale) //삼각형
    } else if(speed < 33.4){
        path.push('M', 0 * scale, -4 * scale, 'L', 7 * scale, -8 * scale); // 긴 선
        path.push('M', 0 * scale, -7 * scale, 'L', 7 * scale, -12 * scale, 'L', 0  * scale, -10  * scale) //삼각형
    } else if(speed < 36){
        path.push('M', 0 * scale, -1 * scale, 'L', 4 * scale, -4 * scale); // 짧은 선
        path.push('M', 0 * scale, -4 * scale, 'L', 7 * scale, -9 * scale); // 긴 선
        path.push('M', 0 * scale, -7 * scale, 'L', 7 * scale, -12 * scale, 'L', 0  * scale, -10  * scale) //삼각형
    } else if(speed < 38.5){
        path.push('M', 0 * scale, -1 * scale, 'L', 7 * scale, -6 * scale); // 긴 선
        path.push('M', 0 * scale, -4 * scale, 'L', 7 * scale, -9 * scale); // 긴 선
        path.push('M', 0 * scale, -7 * scale, 'L', 7 * scale, -12 * scale, 'L', 0  * scale, -10  * scale) //삼각형
    } else if(speed < 41){
        path.push('M', 0 * scale, 2 * scale, 'L', 4 * scale, -1 * scale); // 짧은 선
        path.push('M', 0 * scale, -1 * scale, 'L', 7 * scale, -6 * scale); // 긴 선
        path.push('M', 0 * scale, -4 * scale, 'L', 7 * scale, -9 * scale); // 긴 선
        path.push('M', 0 * scale, -7 * scale, 'L', 7 * scale, -12 * scale, 'L', 0  * scale, -10  * scale) //삼각형
    } else if(speed < 43.5){
        path.push('M', 0 * scale, 2 * scale, 'L', 7 * scale, -3 * scale); // 긴 선
        path.push('M', 0 * scale, -1 * scale, 'L', 7 * scale, -6 * scale); // 긴 선
        path.push('M', 0 * scale, -4 * scale, 'L', 7 * scale, -9 * scale); // 긴 선
        path.push('M', 0 * scale, -7 * scale, 'L', 7 * scale, -12 * scale, 'L', 0  * scale, -10  * scale) //삼각형
    } else if(speed < 46){
        path.push('M', 0 * scale, 5 * scale, 'L', 4 * scale, 2 * scale); // 짧은 선
        path.push('M', 0 * scale, 2 * scale, 'L', 7 * scale, -3 * scale); // 긴 선
        path.push('M', 0 * scale, -1 * scale, 'L', 7 * scale, -6 * scale); // 긴 선
        path.push('M', 0 * scale, -4 * scale, 'L', 7 * scale, -9 * scale); // 긴 선
        path.push('M', 0 * scale, -7 * scale, 'L', 7 * scale, -12 * scale, 'L', 0  * scale, -10  * scale) //삼각형
    }
    else{
        path.push('M', 0 * scale, 5 * scale, 'L', 7 * scale, 0.5 * scale); // 긴 선
        path.push('M', 0 * scale, 2 * scale, 'L', 7 * scale, -3 * scale); // 긴 선
        path.push('M', 0 * scale, -1 * scale, 'L', 7 * scale, -6 * scale); // 긴 선
        path.push('M', 0 * scale, -4 * scale, 'L', 7 * scale, -9 * scale); // 긴 선
        path.push('M', 0 * scale, -7 * scale, 'L', 7 * scale, -12 * scale, 'L', 0  * scale, -10  * scale) //삼각형
    }

    return path;
};

scale객체의 값을 부여하여 바람 깃 아이콘의 사이즈를 일괄적으로 조절할 수 있도록 하였다. 본인의 경우 풍속 값의 단위가 작업 지시 사항 전달 과정에서 kt, m/s등으로 계속해서 변동이 있었기 때문에 부득이하게 kt단위로 전달된 풍속 값 인자를 m/s단위로 변경하여 해당 단위에서의 분기 값을 기준으로 하여 바람 깃 아이콘의 모양을 다르게 처리하였다.

 

바람 깃의 풍속 값 별 모양 예시를 아래 첨부하였다.

https://blog.mapbox.com/mapping-wind-barbs-to-show-speed-and-direction-4d3078add03d

 

Mapping wind barbs to show speed and direction

Wind barbs are ideal for visualizing wind patterns over a large area: they point in the direction of travel, and their length and notches at the end denote velocity at each position. For example, a…

blog.mapbox.com

 



2) svg 적용 메서드

인자로 전달된 highcharts객체 값을 기반으로 하여 위 로직을 통해 값으로써 생성한 svg아이콘을 정의한 스타일에 맞추어 차트 내 표출되도록 그려내는 기능을 하는 메서드이다. 추가 설명이 필요한 만큼의 복잡도는 아니기 때문에 이외 설명은 주석으로 대체하였다.

Meteogram.prototype.drawWindArrows = function(chart, iswindprofiler) {
    var meteogram = this;
    var dataSize = 1; // 모든 포인트에 표시하거나 간격을 조정
    var xLineHeight='';
    if(!iswindprofiler){
        xLineHeight = chart.plotHeight;//차트 바닥에 정렬
    }

    var speedData = chart.series[0].data.map(function(point) {
        return point.speed; // 데이터 포인트에 'speed' 속성이 있다고 가정
    });
    var directionData = chart.series[0].data.map(function(point) {
        return point.direction; // 데이터 포인트에 'direction' 속성이 있다고 가정
    });
    var colorData = chart.series[0].data.map(function(point) {
        return point.color; // 데이터 포인트에 'color' 속성이 있다고 가정
    });

    $.each(chart.series[0].data, function(i, point) {
        var arrow, x, y;

        if (i % dataSize === 0) {
            x = point.plotX + chart.plotLeft;
            if(!iswindprofiler){
                y = xLineHeight; //차트 바닥에 정렬
            }else{ y = point.plotY; }

            if (speedData[i] <= 1) {
                arrow = chart.renderer.circle(x, y, 3).attr({
                    fill: 'none'
                });
            } else {
                arrow = chart.renderer.path(
                    meteogram.windArrow(speedData[i], iswindprofiler)
                ).attr({
                    rotation: parseInt(directionData[i], 10),
                    translateX: x,
                    translateY: y
                });
            }
            arrow.attr({
                stroke:colorData[i],
                fill:colorData[i],
                'stroke-width': 1.5,
                zIndex: 5
            }).add();
        }
    });
};

 



3) 로드 예시

해당 아래 메서드는 단순히 drawWindArrows메서드를 실행시킬 목적의 함수이다. 별도 메서드로 분류한 이유는, 혹여 차트 로드 시 추가적으로 작업이 필요하여 다른 동작을 수행해야할 경우 해당하는 동작을 함수화 하여 load함수 내에서 한 번에 호출하여 동작시킬 수 있도록 후일의 유지보수를 용이하도록 처리하기 위함이다.

Meteogram.prototype.onChartLoad = function(chart, iswindprofiler) {
    this.drawWindArrows(chart, iswindprofiler);
};

 

 

하단 링크는 highcharts에서 제공하는 바람 깃 생성 데모이다.

https://www.highcharts.com/demo/highcharts/windbarb-series

 

Wind barb Demo | Highcharts

Install with NPM The official Highcharts NPM package comes with support for CommonJS and contains Highcharts, and its Stock, Maps and Gantt packages. npm install highcharts --save See more installation options

www.highcharts.com

 


2. highchart 표출

사용 방법은 highcharts 객체의 chart.events.load 시, Meteogram.prototype.onChartLoad가 실행될 수 있도록 하면 된다.

chart: {
            type: 'scatter', //차트 타입
            events: {
                load:function() { meteogram.onChartLoad(this, true); } //활용 예시
            }
        },

 


 

3. 구현 예시

아래 코드는 위 내용들을 종합하여 code pen으로 구현해낸 예시이다. 주석 내용으로 상세 흐름 이해 가능하다.

See the Pen highcharts wind barb by HamJeong (@HamJeong) on CodePen.

 

 

구현 예시와 더불어 ajax활용하여 시리즈 데이터를 비동기로 반환받고, 차트 내용을 업데이트 할 수 있도록 처리하는데에 활용한 코드를 일부 첨부하였다.

$.ajax({
    type: "get",
    url: "url",
    dataType: "json",
    data: { //전송 데이터 예시
        stnCd: $("#stnCd").val(),
        tm: $("#tm").val(),
        view: $("#view").val(),
        altitude: $("#altitude").val()
    },
    success: function(data) {
        // 1. 차트 객체 값 조정
        const leftChartData = data[Object.keys(data)[0]];
        windprofilerLeftChartsOptions.title.text = Object.keys(data)[0]; // 첫 번째 객체의 키
        windprofilerLeftChartsOptions.yAxis.max = parseFloat($("#altitude").val()); //최대 표출고도
        windprofilerLeftChartsOptions.xAxis.categories = leftChartData.xAxis; // xAxis 데이터
        windprofilerLeftChartsOptions.series[0].data = leftChartData.data.map(function(item) {// 첫 번째 시리즈 데이터
            return {
                x: parseFloat(item.x),  // x 값을 숫자로 변환
                y: parseFloat(item.y),  // y 값을 숫자로 변환
                speed: parseFloat(item.speed),  // 속도
                direction: parseFloat(item.direction),  // 방향
                color: item.color  // 색상
            };
        });

        // 2. 조정된 객체로 차트 업데이트
        Highcharts.chart('windprofiler_content_1', windprofilerLeftChartsOptions);
    },
    error: function(xhr, status, error) {
        console.log(error)
    }
});

 


 

 

highcharts에서 제공하는 AI 챗 서비스 링크를 첨부하였다. 국문으로 문답해서인지 답변 정확도는 떨어지는 편이지만, 간편한 동작 하나를 위해 전체 데모를 확인하고, 개발자 문서를 탐색하는 것 보다 빠르게 원하는 내용을 확인할 수 있다.

https://www.highcharts.com/chat/gpt/

 

Highcharts GPT - Charting Powered by ChatGPT | Highcharts

Highcharts Create real charts with simple prompts. Easily transform copied data from spreadsheet applications, refine outputs with follow-up prompts, and create charts in any language. While the tool is free for crafting and design, a Highcharts commercial

www.highcharts.com

 

반응형