matsuok’s diary

あくまでも個人的意見であり感想です

データベースからグラフ作成 Ruby

  • 歩行テーブルと気象データを日付でジョイントしたビューから、グラフ化する
  • 歩行距離、日平均気温の時系列折れ線グラフ
  • 歩行距離と気温は縮尺が違うので、歩行距離に合わせる
  • 期間内天候比率を円グラフ化するが、要素が多いとエラーとなるので、比率が低いデータは、「その他」として集計
  • グラフライブラリーでは標準フォントが日本語に対応していないので、ttfファイルを指定
  • 結果ha,pngファイルとして生成される
require "date"
require 'gruff'
# 棒グラフ
# gruff-example.rb
# g = Gruff::Bar.new
# エリアグラフ
# gruff-example.rb
# g = Gruff::Area.new
# 横棒グラフ
# gruff-example.rb
# g = Gruff::SideBar.new
# 円グラフ
# gruff-example.rb
# g = Gruff::Pie.new
# 日本語対応してないから
# gruff-example.rb
# g.font = '適当なフォント名.ttf'
require 'sqlite3' # sqlite3読み込み
puts "$0:#{$0}"
begin
  db = SQLite3::Database.new 'health.db'
rescue SQLite3::SQLException => e
  puts "open database return: "
  puts "#{e}\n"
end

table_name = "vwalking"

g = Gruff::Line.new('1024x768')
g.font = 'msgothic.ttf'
#文字サイズの変更
g.legend_font_size = 20
g.title = "Walking and weather"
# g.theme_37signals
a_distance = []
a_weater = []
a_avgtemp = []
a_wdate = []
a_Weatherreport = { nil => nil }

sql = "select wdate, wdate,walkingdistance,avgtemp,Weatherreport from vwalking;"
begin
  n = 0
  db.execute(sql) do |row|
    a_distance << row[2]
    a_avgtemp << row[3].to_f/5
    a_wdate << row[0]
    n += 1
    if a_Weatherreport[ row[4] ] == nil
      a_Weatherreport[ row[4] ] = 1
    else
      a_Weatherreport[ row[4] ] += 1
    end
  end
rescue SQLite3::SQLException => e
  puts "#{sql} database return:}"
  puts "#{e}\n"
end
sql = "select count(wdate), max(wdate), min(wdate),avg(walkingdistance),avg(avgtemp) from vwalking;"
cnt = 0
fromdate = ""
todate = ""
avgtemp = 0
avgwalk = 0
begin
  db.execute(sql) do |row|
    cnt = row[0]
    todate = row[1]
    fromdate = row[2]
    avgtemp = row[4]
    avgwalk = row[3]
  end
rescue SQLite3::SQLException => e
  puts "#{sql} database return:}"
  puts "#{e}\n"
end
puts "record:#{cnt.to_s} #{sql} return\n}"
db.close
puts "database close return\n}"
puts 'start:' + fromdate + ' to:' + todate + "\n"
total_msg = "合計件数:#{cnt} 平均歩行距離(km):#{avgwalk.to_s} 期間内平均気温(℃):#{avgtemp.to_s}\n"
puts total_msg

# 	labels:ハッシュで列名を指定する。すべての列に指定しなくても良い。
wdate = { 0 => "yyyy/mm/dd" }
cnt = 0
a_wdate.each_with_index do |n, i|
  if i.modulo(100) == 0
    wdate [i]  = "."
  else
    wdate [i]  = nil
  end
  cnt += 1
end
wdate[0] = fromdate # 先頭ラベル
wdate[cnt - 1] = todate # 最終ラベル

a_averaget = a_avgtemp.map { |n| (avgtemp.to_f/5).round(3) }
a_averagew = a_distance.map { |n| avgwalk }
g.labels = wdate
g.data "歩行距離(km):", a_distance
g.data "期間内平均気温(℃):", a_avgtemp
g.data "合計件数:#{cnt} 平均歩行距離(km):#{((avgwalk.to_f).round(3)).to_s}", a_averagew, '#0000ff' #blue
g.data "期間内平均気温(℃):#{(avgtemp.to_f).round(3).to_s}", a_averaget, '#ff0000' # red

g.write('gruff-walkingWeatherline.png')

g = Gruff::Pie.new('1024x768')
g.font = 'msgothic.ttf'
#文字サイズの変更
g.legend_font_size = 20
g.title = "Walking and weather"
#0を頂点にする。デフォルトだと右90度から始まる。
g.zero_degree = -90

#値をソートしない(デフォルトはtrue)
# g.sort = false

# 値の微細なデータをその他として集計
cnt = 0
a_Weatherreport.each do | key, value|
  if value.to_i < 20
    cnt += value.to_i
  end
end
a_Weatherreport["その他"] = cnt.to_i
# 集計した値の微細なデータを削除
a_Weatherreport.delete_if do |key, value|
  value.to_i < 20
end
# p a_Weatherreport
a_Weatherreport.each do | key, value|
  if value != nil
   p "'#{key.to_s}', [#{value.to_i}]"
   g.data "'#{key.to_s}'", [value.to_i]
 end
end

g.write('gruff-walkingWeatherpie.png')

実行結果

f:id:matsuok:20200602100617p:plain
f:id:matsuok:20200602100658p:plain