ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 코로나19 백신 접종 현황 차트 페이지 만들기 - (9) map 페이지와 highmap을 활용한 지도 차트
    Node.js/예제 2021. 6. 4. 22:55

     

    1. map 페이지

    People Vaccinated

    좌측 상단 map을 선택하면 맵 차트 페이지로 이동한다. 이 페이지에서는 세 가지 옵션을 제공하는데, 우측 상단 버튼을 통해 선택 가능하다. 첫 번째(People Vaccinated)는 국가별 백신 접종 인원 비율, 두 번째(People Fully Vaccinated)는 국가별 백신 접종 완료율, 세 번째(Total Vaccinated)는 국가별 전체 백신 접종 횟수에 대한 비율을 보여준다.

    People Fully Vaccinated
    Total Vaccinated

     

    worldmap.ejs

    <%- include('header') %>
    <%- include('hm') %>
      </head>
      <body>
        <div class="container">
          <%- include('navbar') %>      
          <div class="mapdiv" id="worldmap"></div>
        </div>    
      </body>
      <script src="js/worldmap.js"></script>
      <script>
        const ulInnerHtml = document.getElementById("navBar").innerHTML;
        const newBtns = `
          <li class="navli right" onclick="drawWorldMap('tv')"><a href="#">Total Vaccinated</a></li>
          <li class="navli right" onclick="drawWorldMap('pfv')"><a href="#">People Fully Vaccinated</a></li>
          <li class="navli right" onclick="drawWorldMap('pv')"><a href="#">People Vaccinated</a></li>
        `;
        document.getElementById("navBar").innerHTML = ulInnerHtml + newBtns;
        drawWorldMap('pv')
      </script>
    </html>
    

    맵 페이지에 대한 frontend 코드다. 상단 공통 헤더와 highmap 모듈을 위한 헤더를 불러왔고, 공통 navebar를 불러왔다. 또 내비게이션 바 내에 세 가지 옵션을 위한 버튼을 추가하였고, 각 버튼에는 drawWorldMap이라는 함수를 이벤트 리스너를 등록해두었다. 그리고 map 페이지 처음 이동 시 첫 번째 옵션에 대한 함수가 실행될 수 있도록 함수 호출도 추가하였다. 이 함수는 투입되는 인자에 따라 각각 페이지에 맞는 데이터를 ajax에서 요청하여 서버로부터 받아온다.

     

    2. 서버 코드

    map.js

    const express = require('express');
    const router = express.Router();
    
    const { getDataUsingSql } = require("./getdata");
    
    router.get('/', async (req, res) => {
      res.render("worldmap");
    });
    
    router.post('/mapdata', async (req, res) => {
      const { param } = req.body;
      const colname = param === "pv" ? "people_vaccinated" : (param === "pfv" ? "people_fully_vaccinated" : "total_vaccinations");
    
      const query = `
        SELECT country, iso_code, MAX(${colname}_per_hundred) AS value
        FROM "country-vaccinations"
        GROUP BY country, iso_code
      `;
      try {
        const data = await getDataUsingSql(query);
        const resultData = data.rows.map(el => {
          return {
            name: el[0],
            code: el[1],
            value: el[2] || 0,
          }
        })
        res.json(resultData)
      } catch (err) {
        console.error(err);
      }
    });
    
    module.exports = router;

    클라이언트에서 ajax로 데이터를 요청하면 post 라우터 /mapdata가 실행된다. 요청시 전달한 파라미터에 따라 쿼리 실행시 다른 컬럼(people_vaccinated_per_hundred, people_fully_vaccinated_per_hundred, total_vaccinations_per_hundred)을 조회하도록 하였다. 국가별로 그룹화 하였으며, 지도 차트를 그리기 위해서는 iso code도 필요하므로 같이 조회한다. 그리고 조회된 데이터의 rows를 { 국가명, iso코드, 조회값 } 형태의 데이터를 가진 배열로 가공한 후 클라이언트로 전달해준다. 측정된 결과가 없는 국가는 그 값이 null로 들어가있다. 이걸 그대로 전달하면 지도에 데이터 표시를 아예 안해주기 때문에 0%로 표시될 수 있도록 0으로 변환해주었다.

    3. highmaps

    하이차트는 지도 형태로 차트를 그릴 수 있는 기능도 제공해준다. demo 메뉴의 maps demo로 들어가면 다양항 형식의 지도 차트를 확인할 수 있다. 여기에서는 이들 중 Zoom to area by double click 예제를 사용하였다.

    worldmap.js

    function drawWorldMap(param) {
      $.ajax({
        url: "/map/mapdata",
        type: "POST",
        data: { param },
        dataType: "json",
        success: function (result) {
          const dtText = param === "pv" ? "People Vaccinated" : (param === "pfv" ? "People Fully Vaccinated" : "Total Vaccinations");
    
          Highcharts.mapChart('worldmap', {
            chart: {
              map: 'custom/world',
              borderWidth: 1,
            },
    
            colors: ['rgba(19,64,117,0.05)', 'rgba(19,64,117,0.2)', 'rgba(19,64,117,0.4)',
              'rgba(19,64,117,0.5)', 'rgba(19,64,117,0.6)', 'rgba(19,64,117,0.8)', 'rgba(19,64,117,1)'],
    
            title: {
              text: `${dtText} Per Hundred by Country`
            },
            credits: {
              enabled: false
            },
            exporting: {
              enabled: false
            },
            mapNavigation: {
              enabled: true
            },
    
            legend: {
              title: {
                text: `${dtText} (%)`,
                style: {
                  color: ( // theme
                    Highcharts.defaultOptions &&
                    Highcharts.defaultOptions.legend &&
                    Highcharts.defaultOptions.legend.title &&
                    Highcharts.defaultOptions.legend.title.style &&
                    Highcharts.defaultOptions.legend.title.style.color
                  ) || 'black'
                }
              },
              align: 'left',
              verticalAlign: 'bottom',
              floating: true,
              layout: 'vertical',
              valueDecimals: 0,
              backgroundColor: ( // theme
                Highcharts.defaultOptions &&
                Highcharts.defaultOptions.legend &&
                Highcharts.defaultOptions.legend.backgroundColor
              ) || 'rgba(255, 255, 255, 0.85)',
              symbolRadius: 0,
              symbolHeight: 14
            },
    
            colorAxis: {
              dataClasses: [{
                to: 1
              }, {
                from: 1,
                to: 15
              }, {
                from: 15,
                to: 30
              }, {
                from: 30,
                to: 50
              }, {
                from: 50,
                to: 70
              }, {
                from: 70,
                to: 90
              }, {
                from: 90
              }]
            },
    
            series: [{
              data: result,
              joinBy: ['iso-a3', 'code'],
              animation: true,
              name: `${dtText} per hundred`,
              states: {
                hover: {
                  color: '#a4edba'
                }
              },
              tooltip: {
                valueSuffix: '%'
              },
              shadow: false
            }]
          });
        },
        error: function (request, status, error) {
          console.error(error);
        }
      });
    }

    사용 방법은 이전에 살펴본 highchart의 다른 차트들과 크게 다르지 않다. 서버로부터 전달받은 데이터를 series의 데이터의 값으로 전달해준다. 맵 페이지에서는 옵션마다 요청 파라미터를 달리해주어야 하므로 ajax 요청시 인자로 전달받은 값을 파라미터로 전달해주는 부분을 추가하였다.

    그리고 데이터 범위에 따라 색을 달리 표시되게 하였는데, 이는 colorAxis.dataClasses에서 설정해주면 된다. 이것을 설정해주면 데이터를 단계별로 나누어 높은 값일 수록 진하게 표시해준다.

Designed by Tistory.