LoginSignup
1
0

More than 3 years have passed since last update.

【Rails6】Ajax + chart.jsでインタラクティブなグラフ描写

Last updated at Posted at 2020-09-30

今回はRuby on Railsで簡単なチャート機能をajaxとchat.jsを使って実装します。例題としてプルダウンで選択した人物の成長記録を描写してみます。(横軸:記録日、縦軸:身長)
尚、Railsのバージョンは6.0.3.3を利用しています。
身長.gif

ご留意ください

テーブル定義

カラム名fi カラム説明 データ型
id ID integer
name 名前 string
height 身長 float
rec_date 身長記録日 date
created_at 登録日 datetime
updated_at 更新日 datetime

今回はseeds.rbに二人分の身長記録データを登録しています。

準備

Gemに追記

Gemfile
gem 'chart-js-rails', '~> 0.1.4'

yarnでパッケージを追加

command
yarn add jquery chart.js

jQueryを扱えるようにする

config/webpack/environment.js
const { environment } = require('@rails/webpacker')
// jqueryを使用できるようにする
const webpack = require('webpack')
environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)
module.exports = environment

chart.jsモジュールの読み込み

app/javascript/packs/application.js
// chart.js利用のため追加
require("chart.js")

モデルの作成

command
rails generate model Height name:string height:float rec_date:date
rails db:migrate

初期データの作成&投入

seeds.rb
Height.create!(
  [
    {
      name: 'たかし',
      height: 150.3,
      rec_date: "1990-01-03",
    },
    {
      name: 'たかし',
      height: 168.3,
      rec_date: "1996-03-03",
    },
    {
      name: 'たかし',
      height: 178.4,
      rec_date: "2003-04-03",
    },
    {
      name: 'ゆき',
      height: 130.3,
      rec_date: "1987-05-07",
    },
    {
      name: 'ゆき',
      height: 144.1,
      rec_date: "1995-04-23",
    },
    {
      name: 'ゆき',
      height: 153.6,
      rec_date: "2000-05-13",
    },
  ]
)
command
rails db:seed

コントローラの作成

chart_sample_controller.rbにindexアクションとsearchアクションを用意します。searchアクションは$.ajax()を使ったリクエスト送信を受け取り、モデル内検索により名前に該当する身長のデータをjson形式で返すためのアクションになります。

command
rails generate controller ChartSample index search

ここまでで、必要なパッケージ、Gem、モデル、コントローラの作成まで終わりました。続いてルート設定とビューを用意します。

ルート設定とビュー

ルート設定

config/routes.rb
Rails.application.routes.draw do
  get 'chart_sample/index'
  get '/chart_sample/search', to: 'chart_sample#search'
end

Views

javascript_pack_tagでjsファイルを読み込んでいますが、ここではAjax+chart.jsを使ってグラフを描写するスクリプトをjQueryで記述しています。jsファイルはapp/javascript/packs/chart_sample/chart_user_height.jsに記述します。

app/view/chart_sample/index.html.erb
<h1>成長記録</h1>
<div class="contents">
  <select id="area">
    <option value="0">選択してください</option>
    <option value="1">たかし</option>
    <option value="2">ゆき</option>
  </select>
</div>

<div class="canvas">
  <canvas id="myChart" width="400" height="400"></canvas>
</div>

<%= javascript_pack_tag 'chart_sample/chart_user_height' %>

ビューの画面は以下のようになります。
出来上がりはプルダウンで名前を選択→下に成長記録のグラフが表示されるといった具合です。
スクリーンショット 2020-09-30 16.24.35.png

Ajax+chart.js

ここのコードは長いので順を追って説明して最後にまとめてソースコードを貼ります。

app/javascript/packs/chart_sample/chart_user_height.js(その1)
$(document).on('turbolinks:load', function () {
  $(function () {
    // chart表示用の自作関数を読み込む
    var chart_func = require("../origin_func.js")
    $('#area').change(function() {
      var selected_name = $('option:selected').text();
      var selected_index = $('option:selected').val();

ここではAjaxの影響等によりページ遷移でjQueryが正常に作動しない現象を防ぐため、turbolinks:loadで初回読み込み時、リロードどちらでも発火するようにしています。origin_func.jsはchart.jsによるチャート描写機能を関数化したものを読み込んでいます。これは後ほど説明します。先ほどのビューのセレクトボックスのインデックスをselected_indexに、名前をselected_nameに定義しています。

app/javascript/packs/chart_sample/chart_user_height.js(その2)
      if (selected_index > 0) {
        // ajaxを使ってSelectBoxの値をjson形式でリクエスト送信する
        // messages/searchesコントローラのindexアクションが受け取る
        $.ajax({
          type: 'GET', // リクエストのタイプ
          url: '/chart_sample/search', // リクエストを送信するURL
          data:  { name: selected_name }, // サーバーに送信するデータ
          dataType: 'json' // サーバーから返却される型
        })

ここではselected_index>0つまり名前(今回は「たかし」か「ゆき」)が選択された場合に処理される内容です。$.ajax()を使って取得した名前をコントローラに送信します。先ほどのルート設定の通り、chart_sample_controller.rbのsearchアクションへ送信されます。
chart_user_height.jsの続きの前にリクエストを受け取ったコントローラのsearchアクションの内容について説明します。

app/controllers/chart_sample_controller.rb
class ChartSampleController < ApplicationController
  def index
  end

  def search
    # ↓検索処理のコード(検索結果の一覧が入力される)
    @height = Height.where('name = (?)', "#{params[:name]}").order(rec_date: "ASC")    

    respond_to do |format|
      # リクエストされるフォーマットがHTML形式の場合
      format.html { redirect_to :root }
      # リクエストされるフォーマットがJSON形式の場合
      format.json { render json: @height }
    end
  end
end

searchアクションでは先ほどAjaxで送信されたリクエスト(人の名前)がparams[:name]に格納されているのでHeightモデル内で一致検索し、モデルから返された中身のrec_dateを古い順に並び替えています。これは後ほど、chart.jsでグラフ表示する際に横軸をrec_dateにするためです。JSON形式でリクエストされているのでformat.jsonが実行され、検索結果がJSON形式で返されます。分かりにくい場合はbinding.pryなどでデバッグしてみるとイメージがつきやすいと思います。
再びjsファイルに戻り描写処理についてみていきます。

app/javascript/packs/chart_sample/chart_user_height.js(その3)
        // ajaxからリクエストを受けとり
        .done(function (data) {
          var height_val = [];
          var height_name = [];
          var height_date = [];
          // chart.jsに渡すため配列に格納する
          $(data).each(function(index, height) {
            height_name.push(height.name);
            height_val.push(height.height);
            height_date.push(height.rec_date);
          });
          chart_func.bar_chart(document, 'myChart', "身長", height_date, height_val);
        })
      }
    })
  });
});

ここでは、モデルから受け取ったJSONフォーマットをchart.jsに渡すため配列型に直しています。例えばheight_valはたかし君の過去の身長が配列として[(身長),(身長),(身長)]といった具合に入ります。(もう少しスッキリと書ける良い方法がある気がするのですが・・・)
そして、日付データと身長データが含まれた配列を読み込んだ自作関数に渡します。自作関数はchart.jsでChartをインスタンス化しています。関数に渡す引数はchart_func.bar_chart(document, canvasタグのid名, グラフに付けたいラベル名(任意), 横軸データの配列, 縦軸データの配列)となります。

chart.jsによる描写処理

app/javascript/packs/origin_func.js
exports.bar_chart = function (document, id_name, label_name, data_x, data_y) {
    var elm = document.getElementById(id_name).getContext('2d');
    //既にチャートが描写されている場合はインスタンスを削除する
    if(myChart.constructor === Chart){
        myChart.destroy();
    };
    //棒グラフの描写
    myChart = new Chart(elm, { 
        type: 'bar',
        data: {
            labels: data_x,
            datasets: [{
                label: label_name,
                data: data_y,
                borderWidth: 1
            }]
        },
        options: {
            scales: {
                yAxes: [{
                    ticks: {
                        beginAtZero: true
                    }
                }]
            }
        }
    })
};
if(myChart.constructor === Chart){
        myChart.destroy();
    };

ここのif文による記述はcanvasを再利用するとグラフが前のグラフに重なって記述されてしまうため、もし既にChartのインスタンスが作られている場合はインスタンスをChart.destroyで削除する必要があるためです。myChart.constructorは読み込み時点ではHTMLCanvasElementからインスタンスが作成されるとChartとなります。
残りは配列データをもとにChartインスタンスを作成しています。
実装は以上になります。
chart_user_height.jsを途切れ途切れに説明したのでまとめて以下に貼り付けておきます。

app/javascript/packs/chart_sample/chart_user_height.js(まとめて)
$(document).on('turbolinks:load', function () {
  $(function () {
    // chart表示用の自作関数を読み込む
    var chart_func = require("../origin_func.js")
    $('#area').change(function() {
      var selected_name = $('option:selected').text();
      var selected_index = $('option:selected').val();
      if (selected_index > 0) {
        $("h1").css("color", "blue");  
        // ajaxを使ってSelectBoxの値をjson形式でリクエスト送信する
        // messages/searchesコントローラのindexアクションが受け取る
        $.ajax({
          type: 'GET', // リクエストのタイプ
          url: '/chart_sample/search', // リクエストを送信するURL
          data:  { name: selected_name }, // サーバーに送信するデータ
          dataType: 'json' // サーバーから返却される型
        })
        // ajaxからリクエストを受けとり
        .done(function (data) {
          var height_val = [];
          var height_name = [];
          var height_date = [];
          // chart.jsに渡すため配列に格納する
          $(data).each(function(index, height) {
            height_name.push(height.name);
            height_val.push(height.height);
            height_date.push(height.rec_date);
          });
          chart_func.bar_chart(document, 'myChart', "身長", height_date, height_val);
        })
      }
    })
  });
});

最後に

何かお気付きの点がございましたら、お気軽にコメントいただけると幸いです。今後はグラフの表現を持たせたり、保存機能を付けたりしてきたいと思います。
ここまでお付き合いいただきありがとうございました!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0