chart.js 코드 정리

MIT 라이선스인 chart.js를 예제를 통해 간단하게 정리해 봅니다. 구현하고자 하는 모습은 아래의 영상과 같습니다.

꺽은선 차트이며, 2개의 데이터 축을 가지고 1초마다 데이터가 변경됩니다.

먼저 DOM 구조입니다.

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<div>
    <canvas id="chart"></canvas>
</div>

CSS는 다음과 같구요.

@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@100;300;400;500;700;900&display=swap');

body {
    margin: 0;
    padding: 0;
}

div {
    width: 600px;
    height: 400px;
    padding: 20px;
}

이제 JS 코드에 대한 내용인데, 먼저 차트를 위한 기본 골격은 다음과 같습니다.

Chart.defaults.font.family = 'Noto Sans KR';

const ctx = document.getElementById('chart');

const config = {
    // CORE PART !!
};

const chart = new Chart(ctx, config);

위의 config에 속성값을 지정함으로써 차트를 정의하는 세세한 내용을 결정할 수 있습니다. config에 대한 속성값은 다음과 같이 정의했습니다.

const config = {
    type: 'line',
    data: {
        labels: ['빨강', 'Blue', 'Yellow', '초록', 'Purple', '오렌지'],
        datasets: [
            {
                label: '변곡점1',
                data: [12, 19, 3, 5, 2, 3],
                backgroundColor: 'yellow',
                borderColor: 'black',
                borderWidth: 1
            },
            {
                label: '변곡점2',
                data: [10, 16, 7, 6, 4, 2],
                backgroundColor: 'white',
                borderColor: 'gray',
                borderWidth: 1
            }
        ]
    },
    options: {
        maintainAspectRatio: false,
        plugins: {
            title: {
                display: true,
                text: '차트, 그것이 알고 싶다.'
            }
        },
        scales: {
            x: {
                title: {
                    display: true,
                    text: '색상'
                }
            },
            y: {
                title: {
                    display: true,
                    text: '변곡량'
                }
            }
        },

    }
}

앞서 실행 화면을 보면 1초마다 차트의 값이 변경되는 것을 볼 수 있습니다. 이에 대한 코드는 다음과 같습니다.

setInterval(() => {
    const datasets = config.data.datasets;

    for (let iDataset = 0; iDataset < datasets.length; iDataset++) {
        const data = datasets[iDataset].data;
        for (let iValue = 0; iValue < data.length; iValue++) {
            data[iValue] += Math.random() * 2.0 - 1.0;
        }
    }

    chart.update();
}, 1000);

크리깅(Kriging) 공간분석

공간 상에 분포된 값 기반의 보간 방식 중 하나인 크리깅을 기능 단위로 만든 코드를 정리한 글입니다. GIS 엔진은 웹 GIS 컴포넌트인 FingerEyes-Xr를 사용했습니다. 크리깅 알고리즘은 오픈소스 라이브러리인 kriging.js를 사용하였으므로 Leaflet이나 OpenLayers에 대한 API에 익숙한 개발자라면 해당 GIS 컴포넌트로도 크리깅 결과에 대한 효과적인 시각화를 구현할 수 있을 것입니다. 웹 기반에서 수행되지만 별도의 서버가 필요하지 않습니다. 지도는 VWorld의 배경지도를 그대로 이용할 것이며 크리깅을 위한 입력 데이터는 실행시 동적으로 생성할 것이기 때문입니다.

결과를 이미지로 먼저 살펴보면 아래와 같습니다.

0~100까지의 값을 가지는 총 35개의 지점이 있고 특정 영역 안에서 크리깅 분석을 수행해 그 결과를 그라디언트 색상으로 표현하는 것인데요. 먼저 특정 영역에 대한 좌표를 지정하고 지도에 표시하는 코드는 다음과 같습니다.

let psd = new Xr.data.PolygonShapeData([[
    new Xr.PointD(14289447.80, 4437166.67),
    new Xr.PointD(14289920.87, 4437347.65),
    new Xr.PointD(14289963.20, 4437210.07),
    new Xr.PointD(14290033.05, 4437198.42),
    new Xr.PointD(14289981.19, 4436636.45),
    new Xr.PointD(14289942.04, 4436488.28),
    new Xr.PointD(14289879.60, 4436349.64),
    new Xr.PointD(14289733.55, 4436420.55),
    new Xr.PointD(14289717.67, 4436438.54)
]]);

let psr = new Xr.data.PolygonGraphicRow(0, psd);

psr.brushSymbol().opacity(0);
psr.penSymbol().color('#ff0000');

gl.rowSet().add(psr);

0~100까지 난수로 부여된 값을 가지는 총 35개의 지점을 지도에 표시하는 코드는 다음과 같구요.

const x = [
    14289539.88, 14289633.01, 14289713.44, 14289812.93, 14289912.41,
    14289598.08, 14289698.63, 14289809.75, 14289883.83, 14289952.62,
    14289747.31, 14289760.01, 14289779.06, 14289793.88, 14289815.04,
    14289836.21, 14289864.78, 14289920.87, 14289698.63, 14289665.82,
    14289758.95, 14289697.57, 14289771.65, 14289732.49, 14289804.46,
    14289828.8, 14289839.38, 14289853.14, 14289885.95, 14289853.14,
    14289878.54, 14289876.42, 14289942.04, 14289929.34, 14289912.41
];

const y = [
    4437041.79, 4437076.72, 4437109.52, 4437148.68, 4437185.72,
    4436892.57, 4436923.26, 4436970.88, 4436999.46, 4436999.46,
    4436877.75, 4436834.36, 4436785.67, 4436736.99, 4436681.96,
    4436626.92, 4436563.42, 4436512.62, 4436824.83, 4436761.33,
    4436740.17, 4436667.14, 4436638.57, 4436577.18, 4436553.90,
    4436927.49, 4436879.87, 4436834.36, 4436790.97, 4436723.23,
    4436687.25, 4436629.04, 4436890.45, 4436744.40, 4436600.47
];

const t = [];

for (let i = 0; i < x.length; i++) {
    t[i] = Math.random() * 100.0;

    let p = new Xr.PointD(x[i], y[i]);
    let tsd = new Xr.data.TextShapeData({ x: x[i], y: y[i], text: t[i] >> 0 });
    let pgr = new Xr.data.TextGraphicRow(i + 1, tsd);

    gl.rowSet().remove(i + 1);
    gl.rowSet().add(pgr);
}

크리깅을 위한 입력값이 준비되어 있으므로 크리깅 분석을 아래의 코드처럼 수행합니다.

const variogram = kriging.train(t, x, y, "spherical", 0, 100);

이제 크리깅 분석 결과를 지도에 표시하기 위해 GridLayer를 사용하게 되는데, 크리깅 분석 결과 모델을 토대로 모르는 지점에 대한 값도 예측할 수 있으며 이 값들을 토대로 적절하게 색상을 배합하면 됩니다. 이러한 코드는 다음과 같습니다.

gridLyr.reset();

const minX = psr.MBR().minX;
const maxX = psr.MBR().maxX;
const minY = psr.MBR().minY;
const maxY = psr.MBR().maxY;

for (let x = minX; x < maxX; x += cellRes) {
    for (let y = minY; y < maxY; y += cellRes) {
        const v = kriging.predict(x, y, variogram);
        gridLyr.value(x, y, v);
    }
}

let clrTbl = new Xr.ColorTable(6);
                    
clrTbl.set(5, 225, 228, 177, 230);
clrTbl.set(4, 190, 208, 122, 230);
clrTbl.set(3, 152, 193, 99, 230);
clrTbl.set(2, 97, 168, 93, 230);
clrTbl.set(1, 46, 146, 85, 230);
clrTbl.set(0, 20, 102, 59, 230);

if (clrTbl.build()) {
    gridLyr.updateByColorTable(clrTbl, psd);
    map.update();
}

GridLayer에 대한 변수는 gridLyr이며 크리깅 보간을 통해 gridLayer 내부의 전체 셀값들이 업데이트됩니다. updateByColorTable 함수를 통해 표현할 색상과 원하는 경계 이외의 셀은 투명하게 잘리게 됩니다. 원하는 경계는 psd라는 변수로 지정하고 있는데 이 psd는 앞서 생성한 특정 영역에 대한 그래픽 요소 객체입니다. 참고로 gridLyr 객체의 생성 코드는 다음과 같습니다.

let cellRes = 1;
let gridLyr = new Xr.layers.GridLayer("grid",
    {
        mbr: psr.MBR(),
        resolution: cellRes
    }
);

이상으로 잘 만들어진 크리깅 라이브러리를 이용하여 웹 상에서 크리깅 분석을 수행하고 그 분석 결과를 시각화하는 코드에 대해 살펴 보았습니다.

Javascript의 Array, Set, Object, Map에 대한 데이터 순회하기

자바스크립트에서 데이터 컨테이너로써의 관점에서 데이터를 순회하는 코드를 정리합니다.

먼저 Array입니다.

const arr = new Array(1, 2, 3, 4, 5)

console.log('Array Iteration 1')
for (let i = 0; i < arr.length; i++) {
    console.log(arr[i])
}

console.log('Array Iteration 2')
arr.forEach(v => {
    console.log(v)
})

console.log('Array Iteration 3')
for (let v of arr) {
    console.log(v)
}

console.log('Array Iteration 4')
for (let i in arr) {
    console.log(arr[i])
}

다음은 Set입니다.

const set = new Set([1, 2, 3, 4, 'Hello'])

console.log('Set Iteration 1')
for (let v of set) {
    console.log(v)
}

console.log('Set Iteration 2')
for (let v of set.values()) {
    console.log(v)
}

console.log('Set Iteration 3')
set.forEach(v => {
    console.log(v)
});

다음은 Object입니다.

const obj = { a: 1, b: 2, c: 3, 9: 4, e: 'Hello' }

console.log('Object Iteration 1')
const keys = Object.keys(obj) // [ 'a', 'b', 'c', '9', 'e' ]

for (let i = 0; i < keys.length; i++) {
    const k = keys[i]
    const v = obj[k]
    console.log(k, v)
}

console.log('Object Iteration 2')
const values = Object.values(obj) // [ 1, 2, 3, 4, 'Hello' ]
for (let i = 0; i < values.length; i++) {
    const v = values[i]
    console.log(v)
}

console.log('Object Iteration 3')
const entries = Object.entries(obj) // [ ['a', 1], ['b', 2], ['c', 3], ['9', 4], ['e', 'Hello'] ]
for (let i = 0; i < entries.length; i++) {
    const k = entries[i][0]
    const v = entries[i][1]

    console.log(k, v)
}

console.log('Object Iteration 4')
for (let k in obj) {
    const v = obj[k]
    console.log(k, v)
}

끝으로 Map입니다.

const map = new Map( [ ['a', 1], ['b', 2], ['c', 3], ['9', 4], ['e', 'Hello'] ])

console.log('Map Iteration 1')
for (let [k, v] of map) {
    console.log(k, v)
}

console.log('Map Iteration 2')
for (let k of map.keys()) {
    console.log(k, map.get(k))
}

console.log('Map Iteration 3')
for (let v of map.values()) {
    console.log(v)
}

console.log('Map Iteration 4')
for (let [k, v] of map.entries()) {
    console.log(k, v)
}

console.log('Map Iteration 5')
map.forEach(function(v, k) {
    console.log(k, v)
})

사실 Map의 출현으로 더 이상 Object를 데이터 컨테이너로써 사용하는 것은 옳지 않습니다. Object는 기본적으로 생성될 때 기본 키값을 갖지만 Map은 개발자가 추가하지 않은 데이터는 갖지 않습니다. 또한 Map의 Key는 문자열 객체 이외에도 타입도 가능합니다. 또한 저장한 순서대로 값을 얻을 수 있으며 데이터의 개수도 size 함수를 통해 바로 얻을 수 있습니다. 게다가 Map은 데이터의 추가와 삭제 시 Object보다 성능이 뛰어납니다.

VWorld에서 제공하는 지적도를 WFS로 사용하기

VWorld는 다양한 주제도를 전국 단위로 OpenAPI로 제공합니다. WMS와 WFS 등의 방식으로 제공하는데요. VWorld의 지적도를 WFS 방식으로 사용하는 내용을 정리해 봅니다. 먼저 최종 결과는 다음과 같습니다.

VWorld에서 제공하는 지적도에 대한 WFS 방식으로 레이어를 추가하는 코드는 다음과 같습니다.

let parcLyr = new Xr.layers.WFSLayer(
    "wfs_parc",
    {
        proxy: "http://www.gisdeveloper.co.kr:8080/Xr",
        url: "http://api.vworld.kr/req/wfs",
        typename: "lp_pa_cbnd_bubun",
        key: "##09##85-1##3-3##5-A##6-####6##C####",
        domain: "http://localhost:56612"
    }
);
lm.add(parcLyr);

WFS 방식의 장점은 공간 데이터를 좌표값으로 받아와 클라이언트에서 직접 그릴 수 있다는 점입니다. 즉, 다양한 심벌을 자유롭게 지정할 수 있습니다. 그에 대한 코드는 아래와 같습니다.

let parcTheme = parcLyr.theme();
let parcPen = parcTheme.penSymbol();
let parcBrush = parcTheme.brushSymbol();

parcPen.color('#ffff00').width(1);
parcBrush.opacity(0);

WFS 방식의 또 다른 장점은 속성 데이터를 함께 제공한다는 것인데요. 이 속성 데이터를 이용하여 원하는 텍스트 심벌을 사용하여 레벨을 표현할 수 있습니다. 해당하는 코드는 이래와 같습니다.

let label = parcLyr.label();
label.enable(true);
label.formatter().fieldName("jibun");

let labelTheme = label.theme();
labelTheme.symbol().strokeColor("#000000").strokeWidth(2).size(12).fontFamily('맑은 고딕').color("#ffffff");

VWorld에서 제공하는 WFS 방식을 통한 지도 레이어는 매우 활용도가 높습니다. 즉, 전국범위의 공간 데이터를 좌표와 속성 데이터를 그대로 받아 원하는 형태로 자유롭고 다양하게 사용할 수 있기 때문입니다. 하지만 VWorld에서 제공하는 WFS 방식은 WMS 방식 보다 제공되는 지도의 종류 수가 적습니다. 예를 들어 건물 레이어의 경우 WMS로는 제공하지만 WFS로는 제공하지 않습니다. VWorld의 OpenAPI를 통해 지적도처럼 건물도 WFS 방식으로 제공되어 활용할 수 있었으면 좋겠습니다.