トップ画像イメージ

オープンデータを取得し、そのデータをグラフに描画してみました

前回福岡県のオープンデータを使って、LineBotを作ってみましたが、テキスト情報だけで見にくかったので、グラフを見れるように機能を追加しました

前回の記事はこちら

グラフのイメージ

こちらです。

棒グラフイメージ

円グラフイメージ

今回はフロント側にvue.jsを利用していたので、 Google chartのライブラリを利用して、最新のデータを取得 ⇨ グラフに描画 という風に実装してみました。

※ちなみに、LineBotにグラフの画像を返却するのではなく、グラフを描画するwebサイトへの誘導を行うように実装しています。

LineBotで「グラフ」と送信すると、次のようにサイトへ誘導するようにしています。

ラインボットイメージ

こちらからもグラフを見れます。

コード部分

前回から修正した部分もございますので、全文掲載していきます。

router

Rails.application.routes.draw do
  root to: 'linebot#index' #追加グラフ描画用
  post '/shuhou' => 'linebot#shuhou'
  get 'linebot/chart' => 'linebot#chart'#追加グラフ用データの取得
end

controller

class LinebotController < ApplicationController
  require 'line/bot'
  require 'net/http'
  protect_from_forgery :except => [:shuhou]

  def index
  end

  #オープンデータのデータからグラフ用のデータを返却するメソッド
  def chart
    #オープンデータからデータを取得
    resources = get_recent_id()
    recent_updated_id = resources[resources.length - 1]["id"]
    second_updated_id = resources[resources.length - 2]["id"]
    recent_records = get_shuhou(recent_updated_id)
    next_records = get_shuhou(second_updated_id)
    recent_week = "#{recent_records[0]['年']}#{recent_records[0]['週']}週(#{recent_records[0]['月']}月)"
    second_week =  "#{next_records[0]['年']}#{next_records[0]['週']}週(#{next_records[0]['月']}月)"
    #チャート用のデータ返却
    chart_data = [['地域',recent_week,second_week]]
    pie_data = []
    recent_records.each do |recent|
      next_records.each do |second|
        if (recent["自治体名"] + recent["地点名"] ) == (second["自治体名"] + second["地点名"] ) 
          if recent["自治体名"] == recent["地点名"]
            timei = recent["自治体名"]
          else
            timei = recent["自治体名"] + recent["地点名"]
          end
          chart_data.push([timei,recent["(インフルエンザ)"].to_f,second["(インフルエンザ)"].to_f])
          pie_data.push([timei,recent["(インフルエンザ)"].to_f])
        end
      end
    end
    pie_data.sort! {|a,b| b[1] <=> a[1] } #円グラフの方は降順にデータが並ぶように調整
    pie_data.unshift(['地域','今週'])
    render json: {chart_data:chart_data,pie_data:pie_data}
  end

  def client
    @client ||= Line::Bot::Client.new { |config|
      config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
      config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
    }
  end
  
  #LineBotのメッセージ返却用メソッド(API部分)
  def shuhou
    body = request.body.read
    events = client.parse_events_from(body)
    signature = request.env['HTTP_X_LINE_SIGNATURE']
    unless client.validate_signature(body, signature)
      head :bad_request
    end
    events.each do |event|
      message = event.message['text']
      #ユーザーから「最新」と送られた場合
      if message == "最新"
        #オープンデータからデータを取得
        resources = get_recent_id()
        recent_updated_id = resources[resources.length - 1]["id"]
        records = get_shuhou(recent_updated_id)
        res_str = ["1医療機関でインフルエンザの感染が確認された人数は\n\n"]
        #いつの分のデータか表示用「
        recent_week = "#{records[0]['年']}#{records[0]['週']}週(#{records[0]['月']}月)"
        #recordsをeachで回し発生地点毎のインフルエンザ倍率を配列に格納
        records.each do |record|
          if record["自治体名"] == record["地点名"]
            timei = record["自治体名"]
          else
            timei = record["自治体名"] + record["地点名"]
          end
          res_str << "#{timei}:#{record["(インフルエンザ)"]}人\n"
        end
        res_str << "\n\nです。\n(#{recent_week})"
        return_str = res_str.join("")
      #ユーザーから「先週比」と送られた場合
      elsif message == "先週比"
        #オープンデータからデータを取得
        resources = get_recent_id()
        recent_updated_id = resources[resources.length - 1]["id"]
        second_updated_id = resources[resources.length - 2]["id"]
        recent_records = get_shuhou(recent_updated_id)
        next_records = get_shuhou(second_updated_id)
        #いつの分のデータか表示用
        recent_week = "#{recent_records[0]['年']}#{recent_records[0]['週']}週(#{recent_records[0]['月']}月)"
        second_week =  "#{next_records[0]['年']}#{next_records[0]['週']}週(#{next_records[0]['月']}月)"
        #返却する文字列の準備
        res_str = ["1医療機関でインフルエンザの感染が確認された人数の先週比は\n\n"]
        #最新の週報と1個前の週報で人数を比較
        recent_records.each do |recent|
          next_records.each do |second|
            if (recent["自治体名"] + recent["地点名"] ) == (second["自治体名"] + second["地点名"] ) 
              num = (recent["(インフルエンザ)"].to_f - second["(インフルエンザ)"].to_f).round(1)
              num = "+" + num.to_s if num >= 0
              if recent["自治体名"] == recent["地点名"]
                timei = recent["自治体名"]
              else
                timei = recent["自治体名"] + recent["地点名"]
              end
              res_str.push("#{timei}:#{num}人\n")
            end
          end
        end
        res_str << "\n\nです。\n(#{recent_week}#{second_week}の比較)"
        return_str = res_str.join("")
      #グラフを表示するurlを返却する
      elsif message == "グラフ"
        return_str = "グラフを表示したい場合はこちらをご覧下さい。\nhttps://imanau-bot.herokuapp.com/"
      #それ以外は使い方を送信する。
      else
        return_str  = "使い方\n\n福岡県が公開している「福岡県 感染症発生動向調査週報」のデータを元に、\n「1週間における、1医療機関当たりのインフルエンザの感染が確認された人数」\nをお知らせします。\n「最新」と送信すれば、最新の人数を、「先週比」と送信すれば、前回の報告と比較した人数の増減をお知らせします。その他、グラフを見たい場合は「グラフ」と送信してください。"
      end
      case event
      when Line::Bot::Event::Message
        case event.type
        when Line::Bot::Event::MessageType::Text
          message = {
            type: 'text',
            text: return_str
          }
          client.reply_message(event['replyToken'], message)
        end
      end
    end
    head :ok
  end

  #感染症週報のオープンデータの公開状況を取得する。resources["result"]["resources"][添字]["id"]で添字番目のオープンデータリソースidを取得
  def get_recent_id
    uri = URI.parse("https://ckan.open-governmentdata.org/api/3/action/package_show?id=43dd36e0-fc93-40af-8215-c17cbf964cae")
    response = Net::HTTP.get_response(uri)
    json = JSON.parse(response.body)
    resources = json["result"]["resources"]
    return resources
  end
  #指定したリソースidの感染症週報のデータを取得する。
  def get_shuhou(resource_id)
    uri = URI.parse("http://data.bodik.jp/api/action/datastore_search?resource_id=" + resource_id)
    response = Net::HTTP.get_response(uri)
    json = JSON.parse(response.body)
    records = json["result"]["records"]
    return records
  end
end


vue側

ページの見た目の部分、見た目は全く凝っていない。

<template>
  <div id="app">
    <div id="contents">
      
      <GChart
        type="BarChart"
        :data="chartData"
        :options="chartOptions"
      />
      <GChart
        type="PieChart"
        :data="pieData"
        :options="pieOptions"
      />
    </div>
    <div class="footer">
      <footer class="page-footer font-small bg-light">
        <!-- Copyright -->
        <div class="footer-copyright text-center py-3">© 2019 imanau
          <a href="https://www.imanau-blog.site/about">About imanau</a>
        </div>
        <!-- Copyright -->
      </footer>
    </div>
  </div>
</template>

<script>
import { GChart } from 'vue-google-charts';
import  axios  from 'axios';

export default {
  components:{
    GChart,
  },
  data: function () {
    return {
      message: "インフルエンザ増減",
      //棒グラフ
      chartData:[],
      //円グラフ
      pieData:[],
      chartOptions: {
        title: 'インフルエンザ感染確認人数増減(1週間・1医療機関辺り)',
        subtitle: '人数',
        height:800,
        hAxis:{
          format: "#人"
        }
      },
      pieOptions:{
        title:'今週のインフルエンザ感染確認数割合(1週間・1医療機関辺り)',
        height:800
      }
      
    }
  },
  methods:{
    chart_data_search(e){
      axios.get('/linebot/chart')
        .then((res) => {
            this.chartData = res.data.chart_data;
            this.pieData = res.data.pie_data;
        })
        .catch(error => {
          alert("データ取得失敗しました");
        })
    }
  },
  mounted(){
    this.chart_data_search();
  },
}
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>

まとめ

  • vue-google-charを使って、超簡単にグラフを描画できた
    • データをサーバーから渡してやるだけ(形を整えて)
  • Botもちゃんと考えれば、もっと面白いことが出来そう