GASで新型コロナウイルスの感染者数チャートを作ってみた

  • このエントリーをはてなブックマークに追加
  • Pocket
  • LINEで送る
EyeCatchImageforCoronavirus

連日新型コロナウイルス関連のニュースが世間を騒がせています。商業施設や娯楽施設なども短縮営業や営業停止を決めていたり、一般事業会社でも臨時休業やテレワーク、時差出勤などを推奨する企業も増えてきました。昨今ではオンラインを利用してできる業務も増えてきていますので、これを機に業種によってはこういった働き方も一般化してくるかもしれませんね。

さて、毎日ニュースで新たに感染したというニュースが流れていますが、私の方でもGoogle Chart APIのGeoChartを活用して日本の都道府県別に患者数分布チャートを作成してみました。暗い話題のニュースですが、その話題性に便乗して空気を読まずに『GASではこんなこともできるよ』という紹介をしようかと思っています。不謹慎かもしれませんが、興味ある方は読み進めてください。

今回はGoogle Apps ScriptとGoogle Chart APIを利用して数値データを地図上に落とし込んでチャート化するGeoChartを作成してみました。今回はChart APIに加えて、Maps Javascript APIも利用しています。

アプリ概要

【WEBアプリ】新型コロナウイルスの感染状況ジオチャートアプリ

今回はいくつかのAPIを利用してシステムを構築しています。

実際の操作画面の動画をGIFイメージ化しましたので、下記に貼り付けます。

coronavirus screenvideo

スライドバーを操作することで、対象日を変更できるようにしてあります。対象日を変更すると都道府県別に対象日までの患者数累計がジオチャートとして表示されるようになっています。患者数が一人でもいれば黄色が地図上に表示され、色が赤色に近くなるごとに人数が多いことを表しています。一応ダイヤモンド・プリンセス号の感染者数はチャートには落とし込んでいません。

今回のシステムではGoogle Apps ScriptのHtml ServiceというHTML形式のウェブページを出力する方法でユーザーインターフェイスを出力し、そのHTML上にChart APIを生成したジオチャートを描画しています。Chart APIでは、様々なチャートを出力することができますが、その中から今回はGeoChartを選択して出力しました。

システムの構成

『データベース』スプレッドシート(リンク

『ユーザーインターフェイス』Google Apps ScriptのHtmlServiceで出力したユーザーインターフェイス

『サーバー側の処理』Google Apps Script

『フロント側の処理』HTML, CSS, Javascript (jQuery)

『外部連携API』Google Chart API, Maps Javascript API, Geocoding API

System Overview for GeoChart

 システムの説明

今回のシステムでは、アプリにアクセス時にスプレッドシートに保存してある都道府県別の患者数データを取得します。取得ができたら、ユーザーインターフェイス側のJavascriptでデータを調整してChart APIにデータを渡してチャートデータを生成しています。スプレッドシートから取得したデータはグローバル変数へ格納していますので、アクセス時にスプレッドシートからデータを取得後は、JavascriptとChart APIの処理だけで日別のデータをチャートを描画し直しています。

意外と簡単にシステム自体は構築できます。下記にGAS側の処理とJavascript側の処理を記載します。

Google Apps Scriptの処理内容
//HTML形式のインターフェイスを表示する処理とデータベース用スプレッドシートからデータを取得する処理
var sheetId = "124ZBoMIyZSmO8LzHV2jdgyOT5DF8vx5j1xVgnh9Jm28"; //データベースのシートID

function doGet() {
  var html = HtmlService.createTemplateFromFile("body");
  return html.evaluate().setTitle('新型コロナウイルスの感染状況ジオチャートアプリ').addMetaTag("viewport", "width=device-width,initial-scale=1, maximum-scale=1.0, minimum-scale=1.0");
}

function getAllData() {
  var ss = SpreadsheetApp.openById(sheetId);
  var sheet = ss.getSheetByName("都道府県別日別新規患者数");
  var data = sheet.getDataRange().getValues();
  var prefectures = data[0];
  var total = {};
  for (var i in prefectures) {
    total[prefectures[i]] = 0;
  }
  var array = {};
  var length = data.length - 1;
  var date = Date.parse(new Date(Utilities.formatDate(new Date(), "JST", "yyyy/MM/dd")));
  for (var i = 1; i <= length; i++) { var object = {}; for (var j in prefectures) { if (prefectures[j] !== "") { if ((data[i][j] !== "" && data[i][j] > 0) || total[prefectures[j]] > 0) {
          total[prefectures[j]] += Number(data[i][j]);
          object[prefectures[j]] = total[prefectures[j]];
        }
      }
    }
    if (Object.keys(object).length > 0) {
      array[Utilities.formatDate(new Date(data[i][0]), "JST", "yyyy/MM/dd")] = object;
    }
    if (Date.parse(new Date(data[i][0])) === date) {
      break;
    }
  }
  return array;
}
フロント側のJavascriptコード
//ユーザーインターフェイス側の動的処理

<script src=”https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js”></script>
<script src=”https://cdn.jsdelivr.net/npm/sweetalert2@9″></script>
<script type=”text/javascript” src=”https://www.gstatic.com/charts/loader.js”></script>

<script>

var weekday = ["日", "月", "火", "水", "木", "金", "土"];
  var dates = [];
  var allData;
  var chartData;
  var chart;
  
  google.charts.load('current', {
                     'packages':['geochart'],
                     'mapsApiKey': 'AIzaSyDczaHfd7eQ4K5miYrwZo48J32y7tkobe8'
  });
                     
  $(function() {
    $(window).on("load", function(e) {
      showLoader();
      google.script.run.withSuccessHandler(setData).withFailureHandler(error).getAllData();
    });
  });
  
  function setData(e) {
    hideLoader();
    allData = e;
    for (var key in allData) {
      dates.push(key);
    }
    dates.sort(function(a, b) {
      return Date.parse(new Date(a)) - Date.parse(new Date(b));
    });
    var startDate = dates[0];
    var endDate = dates[dates.length - 1];
    var slider = '<div class="time-select-slider">' +
                   '<input id="slider" class="slider" type="range" min="' + Date.parse(new Date(startDate)) + '" max="' + Date.parse(new Date(endDate)) + '" step="86400000">'
                 '</div>';
    $("#date").after(slider);
    var value = new Date(endDate);
    $("#slider").val(Date.parse(value));
    var array = test(value);
    drawRegionsMap(array);
  }
    
  function drawRegionsMap(e) {
    var flg = false;
    if (chartData === undefined) {
      chartData = google.visualization.arrayToDataTable(e);
      flg = true;
    }
    else {
      var rows = JSON.parse(chartData.toJSON())["rows"];
      var length = rows.length - 1;
      var pref = [];
      var values = [];
      for (var i in e) {
        pref.push(e[i][0]);
        values.push(e[i][0]);
      }
      for (var i = length; i >= 0; i--) {
        var prefecture = rows[i]["c"][0]["v"];
        var index = pref.indexOf(prefecture);
        if (index >= 0) {
          chartData.setValue(i, 1, Number(e[index][1]));
        }
        else {
          chartData.removeRow(i);
        }
      }
      var rows = JSON.parse(chartData.toJSON())["rows"];
      var length = rows.length - 1;
      var pref = [];
      for (var i in rows) {
        pref.push(rows[i]["c"][0]["v"]);
      }
      for (var i in e) {
        if (e[i][0] === "都道府県") {
          continue;
        }
        if (pref.indexOf(e[i][0]) === -1) {
          chartData.addRow(e[i]);
        }
      }
    }
    var options = {
      region: 'JP',
      resolution: 'provinces',
      colorAxis: {colors: ['gold', 'red']},
      height: 600
    };
    if (flg) {
      chart = new google.visualization.GeoChart(document.getElementById('main'));
    }
    chart.draw(chartData, options);
  }
  
  $("#slider").live("input", function() {
    var value = Number($(this).val());
    var array = test(value);
    drawRegionsMap(array);
  });
  
  function test(value) {
    var date = new Date(value);
    var year = date.getFullYear();
    var month = date.getMonth() + 1;
    day = date.getDate();
    day = "対象日: " + year + " 年 " + month + " 月 " + day + " 日(" + weekday[date.getDay()] + ")";
    $("#date").text(day);
    for (var i in dates) {
      var date = Date.parse(new Date(dates[i]));
      if (date <= value) {
        var target = dates[i];
      }
    }
    var array = [["都道府県", "患者数"], ["total", 200]];
    for (var key in allData[target]) {
      var temp = [key, allData[target][key]];
      array.push(temp);
    }
    return array;
  }
  
  $("#slider").live("change", function() {
    var value = Number($(this).val());
    var array = test(value);
    drawRegionsMap(array);
  });
  
  function showLoader() {
    $("#loader").fadeIn();
    $("#modal").fadeIn();
  }
  
  function hideLoader() {
    $("#loader").fadeOut();
    $("#modal").fadeOut();
  }

  function alertMsg(msg) {
    Swal.fire(msg);
    hideLoader();
  }
  
  function error(e) {
    var msgTitle = "エラーメッセージ";
    $(".msgTitle").text(msgTitle);
    alertMsg(e.message);
  }
</script>

行数はGAS側のスクリプトは38行、Javascript側のスクリプトは150行なので、大した処理は描いていません。この程度の行数を書くだけでGeoChartのような少しだけインパクトのあるシステムが構築できるのはいろいろなAPIを用意してくれていることによる結果だと思います。

参考ページ

新型コロナウイルス感染症患者の発生状況 – 厚生労働省の症例一覧より患者数を集計しました

Google Chart API – Chart APIの公式ページ

CSS(SASS)でスライドバー(input[type=”range”])をアレンジする – 見た目の良いスライドバーのコードを拝借しました

 

SNSでもご購読できます。

コメントを残す

*