新規投稿
フォローする

amChartsを使って地図上にデータを表示

amChartsは,地図やグラフを簡単に作成できるライブラリです. 今回は,地図上にデータを表示するサンプルをご紹介します.

サンプル

カスタマイズビューにて,日本地図上にコロナ感染者のデータを表示します. 円の大きさでデータを可視化します.

※東京の円は本州上に表示されませんが,仕様です.(恐らく大島なども含めて中心を決めているのかと)

サンプルでは下記サイトのデータを引用しました.
https://www.stopcovid19.jp/

フォーム設定

レコード追加

Node.jsを用いてレコードの追加が可能です. 必要パッケージを取得後,下記「getCOVIT19.js」を実行します.

・getCOVIT19.js

const {KintoneRestAPIClient} = require("@kintone/rest-api-client");
const request = require('request-promise');
const csvParse = require('csv-parse');
const moment = require('moment');
require('twix');

const client = new KintoneRestAPIClient({
  baseUrl: 'https://****.cybozu.com', //kintoneのURL
  auth: {
    username: '****', //kintoneのログインユーザ名
    password: '****', //kintoneのログインパスワード
  },
});
const app = ****; //レコード追加先アプリ
const startDate = '2020-03-18';
const endDate = '2020-05-21';
const csvUrl = date => `https://www.stopcovid19.jp/data/covid19japan/${date}.csv`;

const dates = moment.twix(startDate, endDate).toArray('days').map(day => day.format('YYYY-MM-DD'));
Promise.all(
  dates.map(date =>
    request(csvUrl(date))
  )
).then(bodys =>
  Promise.all(bodys.map((body, i) =>
    new Promise(resolve =>
      csvParse(body.replace(/"/g, ''), {
        bom: true,
        quote: null,
        columns: true,
      }, (e, rows) => resolve(rows))
    )
  ))
).then(data => {
  const records = data.map((rows, i) => ({
    date: {
      value: dates[i]
    },
    table: {
      value: rows.map(row => ({
        value: Object.fromEntries(Object.entries(row).filter(([key, value]) =>
          (key && value)
        ).map(([key, value]) =>
          [key, {value: value}]
        ))
      }))
    }
  }));
  client.record.addAllRecords({app, records});
});

・コマンド

$ npm i @kintone/rest-api-client request request-promise csv-parse moment twix
$ node getCOVIT19.js

コード

HTML(カスタマイズビュー)

一覧名は「amCharts」とします.

<div id="chart-controllers"></div>
<div id="chartdiv" style="width: 100%; height: 60vh;"></div>

CSS

下記を読み込みます.

  • kintone-ui-component.min.css (こちらからダウンロード.)

JavaScript

下記を順に読み込みます.

・sample.js

(function() {
  "use strict";
  kintone.events.on([
    'app.record.index.show',
  ], function(event){

    //設定値
    var viewName = 'amCharts';
    var chartDomId = 'chartdiv';
    var chartControllersDomId = 'chart-controllers';
    var dateCode = 'date';
    var tableCode = 'table';
    var prefectureNameCode = 'name';
    var prefectureJpNameCode = 'name_jp';
    var columnCodes = [
      'npatients',
      'ncurrentpatients',
      'nexits',
      'ndeaths',
      'nheavycurrentpatients',
      'nunknowns',
      'ninspections',
    ];
    var columnColors = [
      '#f00',
      '#0f0',
      '#00f',
      '#ff0',
      '#f0f',
      '#0ff',
      '#000',
    ];
    var maxCircleRadius = 30;

    if(event.viewName !== viewName || !event.records.length) return;

    //データ取得用関数定義
    var prefectureMapper = am4geodata_japanLow.features.reduce(function(prefectureMapper, feature){
      prefectureMapper[feature.properties.name] = feature.properties.id;
      return prefectureMapper;
    }, {});
    var getData = function(dateIndex, tableColumnIndex){
      return event.records[dateIndex][tableCode].value.map(function(row){
        return {
          id: prefectureMapper[row.value[prefectureNameCode].value],
          name: row.value[prefectureJpNameCode].value,
          value: row.value[columnCodes[tableColumnIndex]].value
        };
      });
    };
    var getMaxValue = function(tableColumnIndex){ //選択したカラムについて,全レコードから最大値を取得
      return event.records.reduce(function(max, record){
        return record[tableCode].value.reduce(function(max, row){
          return Math.max(max, row.value[columnCodes[tableColumnIndex]].value);
        }, max);
      }, 0);
    };

    //図表作成(https://www.amcharts.com/docs/v4/reference/mapchart/)
    var chart = am4core.create(chartDomId, am4maps.MapChart);
    chart.geodata = am4geodata_japanLow;
    chart.projection = new am4maps.projections.Miller();

    //MapPolygonSeries追加(https://www.amcharts.com/docs/v4/reference/mappolygonseries/)
    var polygonSeries = chart.series.push(new am4maps.MapPolygonSeries());
    polygonSeries.useGeodata = true;
    var polygonTemplate = polygonSeries.mapPolygons.template;
    polygonTemplate.tooltipText = '{name}: {value}';
    polygonTemplate.tooltipPosition = 'fixed';

    //MapImageSeries追加(https://www.amcharts.com/docs/v4/reference/mapimageseries/)
    var bubbleSeries = chart.series.push(new am4maps.MapImageSeries());
    bubbleSeries.dataFields.id = 'id';
    bubbleSeries.dataFields.value = 'value';
    var imageTemplate = bubbleSeries.mapImages.template;
    imageTemplate.tooltipText = '{name}: {value}';

    //円作成
    var circle = imageTemplate.createChild(am4core.Circle);
    circle.fillOpacity = 0.5;
    var circleHeatRule = {
      target: circle,
      property: 'radius',
      min: 1,
      max: maxCircleRadius,
      minValue: 0,
      dataField: 'value',
    };
    bubbleSeries.heatRules.push(circleHeatRule);

    //valueが0の円を非表示
    bubbleSeries.events.on("dataitemsvalidated", function(){
      bubbleSeries.dataItems.each(function(dataItem){
        var mapImage = dataItem.mapImage;
        var circle = mapImage.children.getIndex(0);
        if (mapImage.dataItem.value == 0){
          circle.hide(0);
        }
        else if (circle.isHidden || circle.isHiding){
          circle.show();
        }
      });
    });

    //円の位置調整
    ['latitude', 'longitude'].forEach(function(adapter){
      imageTemplate.adapter.add(adapter, function(value, target){
        var polygon = polygonSeries.getPolygonById(target.dataItem.id);
        if(polygon){
          target.disabled = false;
          return polygon['visual' + adapter.slice(0, 1).toUpperCase() + adapter.slice(1)];
        }else{
          target.disabled = true;
        }
        return value;
      });
    });

    //初期化
    var initialData = getData(0, 0);
    polygonSeries.data = initialData;
    bubbleSeries.data = initialData;
    circle.fill = am4core.color(columnColors[0]);
    circleHeatRule.maxValue = getMaxValue(0);

    //ドロップダウンの設定
    var dateController = new kintoneUIComponent.Dropdown({
      items: event.records.map(function(record, index){
        return {
          label: record[dateCode].value,
          value: index
        }
      }),
      value: 0
    });
    var columnController = new kintoneUIComponent.Dropdown({
      items: columnCodes.map(function(tableColumnCode, index){
        return {
          label: tableColumnCode,
          value: index
        }
      }),
      value: 0
    });
    dateController.on('change', function(value) {
      var data = getData(value, columnController.getValue());
      polygonSeries.data = data;
      bubbleSeries.data = data;
    });
    columnController.on('change', function(value) {
      var data = getData(dateController.getValue(), value);
      polygonSeries.data = data;
      bubbleSeries.data = data;
      circle.fill = am4core.color(columnColors[value]); //円の色を設定
      circleHeatRule.maxValue = getMaxValue(value); //maxValueを設定
    });
    document.getElementById(chartControllersDomId).appendChild(dateController.render());
    document.getElementById(chartControllersDomId).appendChild(columnController.render());
  });
})();
1

0件のコメント

サインインしてコメントを残してください。