search
LoginSignup
0
Help us understand the problem. What are the problem?

posted at

updated at

Rails × chart.js でサウナ活動(投稿)データに連動するラベル付きの円グラフを作ってみた。

0. はじめに

こんにちは、まつけんです。
サウナ記録を管理するアプリ(転職活動用のポートフォリオ)に組み込んだ
投稿に連動する円グラフをどうやって実装したのかを解説する記事です。(下の画像参照)

sakatugraph.png

↓ポートフォリオの解説記事です↓

この記事のゴール(とりあえず)

  • chart.jsの円グラフがどういう風にWebアプリに組み込まれるのか分かる
  • gemのgonを用いて、Controllerで定義した変数をJavaScriptで使う方法が分かる
  • chart.jsの「データに連動するグラフ」の作成方法が分かる

ポートフォリオのWebアプリに動的に変化するchart.jsを使用したグラフを実装する際に苦労したので、過去の自分に教えるつもりでそのやり方をまとめました。
(主にControllerからJavaScriptへデータを受け渡すのに苦労しました)

解説しないこと

  • chart.jsグラフの背景色、ボーダーカラーの設定の仕方など
  • Railsアプリの作成の仕方

サ活円グラフの作成背景

日頃から最高にととのった日の、サウナ水風呂外気浴の時間を記録して、
まとめたい思いがありました。

これら最高のサ活時間の平均を円グラフとすることで、
後々入るサウナでもその平均サ活時間 (円グラフデータ) を参考に

ととのうことができるのではないかと思いました。

要するに 「サ活円グラフを通して、サウナライフをより良くしよう!」
みたいな作成背景です。

環境

  • PC: MacBook Pro (Intel Core i9 2019)
  • OS: macOS Big Sur バージョン11.5.1
  • Ruby: Ruby 2.6.8
  • Ruby on Rails: Ruby on Rails 6.1.4.1
  • DB: PostgreSQL14.0

まずchart.jsとは(簡単に)

良い感じのデザインのグラフがすぐに作れるオープンソースのJavaScriptライブラリです。
以下8種類のグラフ・複合グラフが利用可能。

  • 折れ線グラフ
  • 棒グラフ
  • レーダーチャート
  • 鶏頭図(polar area chart)
  • ドーナツグラフ・円グラフ
  • バブルチャート
  • 散布図
  • 面グラフ

今回は円グラフを利用します。

chart.js公式サイト
有志によって日本語版が作られたそう▶chart.js公式サイト(日本語翻訳版)

↓こんな感じでグラフが簡単に作成できるJavaScriptのライブラリ↓

See the Pen Untitled by matsuken314 (@matsuken314) on CodePen.

1. 円グラフの仕様(やりたかったこと)

やりたいこと
マイページのユーザー投稿一覧データ(サウナ水風呂外気浴)それぞれの平均値を円グラフとしてマイページに表示させたい。

↓実際に円グラフが表示されているマイページを見てもらえるとよりイメージしやすいです↓

phonto.JPG

なので、例えばマイページに投稿が3つあって、

サウナ: 10分、水風呂2分、外気浴6分
サウナ: 12分、水風呂1分、外気浴8分
サウナ: 8分、水風呂3分、外気浴4分

とすると円グラフにはこれら①②③の平均値を表示させたいので、
サウナ: (10+12+8) / 3 = 10分水風呂(2+1+3) / 3 = 2分外気浴(6+8+4) / 3 = 6分という表示をする処理を行います。

上の例の数値↑と下の円グラフの数値↓が違うのはご了承ください笑
sakatugraph.png

2. 実装の流れ

実装の流れとしては下記の通りです。

①Gemをインストール
②Modelに自作メソッドを記述
③計算したグラフデータをControllerからJavaScriptに渡す記述
④JavaScriptで変数データを読み込み
⑤Viewで表示

では順を追って解説していきます。

①Gemをインストール

以下2つのGemをインストール。

  • ControllerからJavaScriptに変数を渡せるようにするためのgon
  • 円グラフを描くためのchart-js-rails

Gemfileに以下の通り追記。

Gemfile
# Gon(コントローラからJavaScriptに変数を渡せるようにする)
gem 'gon'

# chart.js(円グラフ表示)
gem 'chart-js-rails'

ターミナルでbundle installを実行し、インストール。

$ bundle install

②Modelに自作メソッドを記述

このパートの目的はModelにユーザーのサウナ水風呂外気浴のそれぞれの平均値を返す自作メソッドを記述することです。(ロジックの詳細は割愛します)

以下がプログラムで扱う主な変数の説明です。

  • sauna_ave・・・・ユーザーが投稿したサウナ時間の合計の平均
  • water_ave・・・・ユーザーが投稿した水風呂時間の合計の平均
  • totonoi_ave・・・ユーザーが投稿した外気浴時間の合計の平均

これら3つの変数をまとめて配列として返す circle_dataメソッドです。
この自作メソッドを次の③で使います。

app/models/post.rb
class Post < ApplicationRecord
  def self.circle_data(user)
    sauna_ave = user.posts.average(:sauna_one).round(1)
    water_ave = user.posts.average(:water_one).round(1)
    totonoi_ave = user.posts.average(:totonoi_one).round(1)
    #以下の配列がcircle_dataを使った時の返り値
    [sauna_ave, water_ave, totonoi_ave] 
     #以上の配列がcircle_dataを使った時の返り値
  end
end

③計算したグラフデータをControllerからJavaScriptに渡す記述

【Controllerの記述】
このパートの目的
Controller内で②のModelで計算したユーザーの
サウナ水風呂外気浴のそれぞれの平均値データ3つをJavaScriptに渡す用に変数を定義することです。

ここでgonの登場です。
JavaScript側に渡すGon用の変数を定義します。
下記のように変数の頭にgon.をつけます。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    @posts = @user.posts.order(created_at: :desc)

   # ↓ 変数の頭に gon. をつける ↓
    gon.Totonoi_data = @posts.circle_data(@user) #ここで代入したデータをJavaScriptに渡す
   # ↑ 変数の頭に gon. をつける ↑

  end
end

こうすることでグラフデータをControllerからJavaScriptに渡す準備は整いました。
gon、めっちゃ便利ですよね。

④JavaScriptで変数データを読み込み

ここでは以下の3点がポイントです。

  • 円グラフを使用するための記述
  • サウナ, 水風呂, 外気浴の変数データを読みこむための記述
  • 円グラフにサウナ, 水風呂, 外気浴の文字ラベルと動的な数値を表示させるための記述

順を追って、解説していきます。
これら3つの説明以外は省略しています。(背景色の付け方など)

最終的なJavaScriptのコード

app/javascript/packs/totonoi_circle.js
document.addEventListener('turbolinks:load', () => {
   var ctx = document.getElementById('myChart').getContext('2d');
   var dataLabelPlugin = {
    afterDatasetsDraw: function (chart, easing) {
        var ctx = chart.ctx;
        chart.data.datasets.forEach(function (dataset, i) {
            var meta = chart.getDatasetMeta(i);
            if (!meta.hidden) {
                meta.data.forEach(function (element, index) {
                    ctx.fillStyle = '#333';

                    var fontSize = 14;
                    var fontStyle = 'bold';
                    var fontFamily = 'Helvetica Neue';
                    ctx.font = Chart.helpers.fontString(fontSize, fontStyle, fontFamily);
 
                    var dataString = chart.data.labels[index]+`\r\n`+dataset.data[index].toString()+'';
 
                    ctx.textAlign = 'center';
                    ctx.textBaseline = 'middle';
                    var position = element.tooltipPosition();
                    ctx.fillText(dataString, position.x, position.y - (fontSize / 2) );
                   })
               }
           })
       }
   }

   var myChart = new Chart(ctx, {
       type: 'pie',   
       data:{
               labels: ['サウナ', '水風呂', '外気浴'], 
               datasets: [{
                   label: '# of Votes',
                   data: gon.Totonoi_data,
                   backgroundColor: [
                       'rgba(255, 99, 132, 0.2)',
                       'rgba(54, 162, 235, 0.2)',
                       'rgba(97,195,89, 0.2)'
                   ],
                   borderColor: [
                       'rgba(255, 99, 132, 1)',
                       'rgba(54, 162, 235, 1)',
                       'rgba(97,195,89, 1)'
                   ],
                   borderWidth: 1
               }]
        },
        plugins: [dataLabelPlugin]  
   });
   draw_graph();
})

④-1 円グラフを使用するための記述

下記のように
データセットtypeに対してpieを指定することで、
円グラフを使用することができます。
注目箇所はコメントアウトして、目立たせています。

document.addEventListener('turbolinks:load', () => {
   var ctx = document.getElementById('myChart').getContext('2d');
   var myChart = new Chart(ctx, {

       // ↓ここ注目↓ 
       type: 'pie',   // 円グラフを使用するため「type」のプロパティを「pie」に指定する
       // ↑ここ注目↑ 

       data:{
            datasets: [{
                label: '# of Votes',
                backgroundColor: [
                    'rgba(255, 99, 132, 0.2)',
                    'rgba(54, 162, 235, 0.2)',
                    'rgba(97,195,89, 0.2)'
                ],
                borderColor: [
                    'rgba(255, 99, 132, 1)',
                    'rgba(54, 162, 235, 1)',
                    'rgba(97,195,89, 1)'
                ],
                borderWidth: 1
            }]
       }
   });
   draw_graph();
})

こうすることで円グラフを使用するための準備は完了です。
他の種類のグラフも表示できるので気になる方はこちらからチェックしてみてください。

④-2 サウナ, 水風呂, 外気浴の変数データを読みこむための記述

Controllerで代入した
サウナ, 水風呂, 外気浴の変数データのgon.Totonoi_data
下記のようにdatadatasetsdataのプロパティに指定します。
注目箇所はコメントアウトして、目立たせています。

document.addEventListener('turbolinks:load', () => {
   var ctx = document.getElementById('myChart').getContext('2d');
   var myChart = new Chart(ctx, {
       type: 'pie',   
       data:{
            datasets: [{
                label: '# of Votes',

                // ↓追記する所↓ 
                data: gon.Totonoi_data, //ここの「data」のプロパティを「gon.Totonoi_data」にする
                //  ↑追記する所↑

                backgroundColor: [
                    'rgba(255, 99, 132, 0.2)',
                    'rgba(54, 162, 235, 0.2)',
                    'rgba(97,195,89, 0.2)'
                ],
                borderColor: [
                    'rgba(255, 99, 132, 1)',
                    'rgba(54, 162, 235, 1)',
                    'rgba(97,195,89, 1)'
                ],
                borderWidth: 1
            }]
       }
   });
   draw_graph();
})

こうすることで、Controllerでgon.Totonoi_dataに代入した値をJavaScriptで読み込むことができます。

④-3 円グラフにサウナ, 水風呂, 外気浴の文字ラベルと動的な数値を表示させるための記述

④-2までの状態だと、Viewファイルの記述をしても下画像のように円グラフにラベルが付与されない状態です。
今回はラベルを加えて、リッチなデザインにしたいので、もう少し僕と一緒に頑張りましょう笑。
そのほうがデザイン性も高くなるので。
スクリーンショット 2022-05-28 17.15.20.png

下記3点で円グラフに文字ラベルと数値を表示させるための記述は完了です。

  • dataLabelPluginを、円グラフ作成のchart.jsプログラムより先に定義する
  • datalabelsに対して、['サウナ', '水風呂', '外気浴']を記述
  • 先に定義したdataLabelPluginをchart.jsプログラムで次のように記述して組み込む→plugins: [dataLabelPlugin]

dataLabelPluginの書き方は下記サイト参考。↓

以上のことを踏まえたのが下記のコードです。(最終的なJavaScriptのコード)
注目箇所はコメントアウトして、目立たせています。

最終的なJavaScriptのコード
document.addEventListener('turbolinks:load', () => {
   var ctx = document.getElementById('myChart').getContext('2d');

   // まずはdataLabelPluginをchart.jsプログラムより先に定義する
   // ↓以下が追記する所↓
   var dataLabelPlugin = {
    afterDatasetsDraw: function (chart, easing) {
        var ctx = chart.ctx;
        chart.data.datasets.forEach(function (dataset, i) {
            var meta = chart.getDatasetMeta(i);
            if (!meta.hidden) {
                meta.data.forEach(function (element, index) {
                    ctx.fillStyle = '#333';

                    var fontSize = 14;
                    var fontStyle = 'bold';
                    var fontFamily = 'Helvetica Neue';
                    ctx.font = Chart.helpers.fontString(fontSize, fontStyle, fontFamily);
 
                    var dataString = chart.data.labels[index]+`\r\n`+dataset.data[index].toString()+'';
 
                    ctx.textAlign = 'center';
                    ctx.textBaseline = 'middle';
                    var position = element.tooltipPosition();
                    ctx.fillText(dataString, position.x, position.y - (fontSize / 2) );
                   })
               }
           })
       }
   }
   // ↑以上が追記する所↑

   var myChart = new Chart(ctx, {
       type: 'pie',   
       data:{
                // 表示する文字ラベルを次のように指定する。
                // ↓以下が追記する所↓ 
               labels: ['サウナ', '水風呂', '外気浴'],  //ここの「labels」のプロパティを「 ['サウナ', '水風呂', '外気浴'] 」にする
                // ↑以上が追記する所↑

               datasets: [{
                   label: '# of Votes',
                   data: gon.Totonoi_data,
                   backgroundColor: [
                       'rgba(255, 99, 132, 0.2)',
                       'rgba(54, 162, 235, 0.2)',
                       'rgba(97,195,89, 0.2)'
                   ],
                   borderColor: [
                       'rgba(255, 99, 132, 1)',
                       'rgba(54, 162, 235, 1)',
                       'rgba(97,195,89, 1)'
                   ],
                   borderWidth: 1
               }]
        },

        //上で定義したdataLabelPluginを次のように組み込む
        // ↓以下が追記する所↓ 
        plugins: [dataLabelPlugin]  
        // ↑以上が追記する所↑ 

   });
   draw_graph();
})

こうすることでようやく、JavaScriptで
円グラフにサウナ, 水風呂, 外気浴の文字ラベルと動的な数値を表示させるための準備ができました:raised_hands:
次のチャプターでViewファイルの設定をすることでようやく下画像のように円グラフの完成形になります。もう一息!
スクリーンショット 2022-05-29 15.40.25.png

dataLabelPluginの書き方は下記サイト参考。↓

⑤Viewで表示

ここでは以下の2点がポイントです。

  • gonを使用するViewファイルに<%= Gon::Base.render_data %>を記述
  • Viewファイルの円グラフを表示したい箇所に<canvas>タグを記述

順を追って、解説していきます。

最終的なViewファイルのコード

app/views/users/show.html.erb
<%= Gon::Base.render_data %>
 <canvas id= "myChart"></canvas>

⑤-1 gonを使用するViewファイルに<%= Gon::Base.render_data %>を記述

Gonを用いて、Controllerで定義した変数データを使用するViewファイルに以下の記述は必須です
<%= Gon::Base.render_data %>

この記述が無い場合、データと連動しないグラフが完成します:innocent:(笑)

app/views/users/show.html.erb
<!--  動的データの円グラフを表示するViewファイルに以下を追加  -->
<%= Gon::Base.render_data %>
<!--  動的データの円グラフを表示するViewファイルに以上を追加  -->

僕はこの記述を忘れて、
「なんでデータがグラフに連動しないんだろう。JavaScriptの記述に問題があるのだろうか」と考え、
JavaScriptの書き方に問題点がないか数時間ほど探して時間を潰したので(笑)、同じ思いをする人を1人でも減らしたいです。笑

⑤-2 Viewファイルの円グラフを表示したい箇所に<canvas>タグを記述

円グラフを表示したい箇所に<canvas> タグを記述し、idにJavaScriptファイルで下記のように定義したmyChartを指定します。
var myChart = new Chart(ctx, { 省略・・・・

app/views/users/show.html.erb
<%= Gon::Base.render_data %>

<!-- 円グラフを表示するViewファイルの任意の箇所に以下を追加  -->
 <canvas id= "myChart"></canvas>
<!-- 円グラフを表示するViewファイルの任意の箇所に以上を追加  -->

Viewファイルの記述は最低限これだけでOKです笑
これでようやく円グラフを実際に表示することができます
スクリーンショット 2022-05-29 15.40.25.png

以上の円グラフを表示するまでの流れをもう1度まとめると下記の通りです。

①Gemをインストール
②Modelに自作メソッドを記述
③計算したグラフデータをControllerからJavaScriptに渡す記述
④JavaScriptで変数データを読み込み
⑤Viewで表示

3. Gon(Gem)を扱う上での注意事項

仕様上、htmlソース内に変数が表示されるので、セキュリティ上隠す必要がある場合はGonは使わないほうがいいです。

下記画像のように検証ツールを開くとがっつり定義した変数のgon.Totonoi_dataが表示されます。
スクリーンショット 2022-05-22 17.22.49.png

4. まとめ

今回は作ったものはデータに連動する円グラフでしたが、chart.jsで扱っている折れ線グラフや棒グラフなどでも同じようなことができます。
いろんなアプリに「データに連動するグラフ」は応用できると思うので、本記事がそれのサポートになれば幸いです。
最後までご覧いただきありがとうございました!

余談(よゆーがあったらやって欲しいくらい)

Gemをインストールした時点で適当なViewファイルに以下のように記述すると、
投稿とは連動しませんが良い感じの棒グラフができるので(下に画像あります)、余裕がある方は息抜きがてらやってみてください。

また、パラメータをいじることで、chart.js感覚がつかめてきます。

「感覚って何や!」ってなるかもしれませんが、
「どこをいじったらどういう感じで変わるのか」と言ったところでしょうか。

例えばdatabackgroundColorなどの
数字を変えるとグラフの色や大きさが変わり、

「ああ、こんな感じね。なるほど理解。」

と理解度が上がったことによる自己肯定感も少し上がります。(僕だけかも知れませんが笑、、、)

適当なviewファイル
<canvas id="myChart" width="400" height="400"></canvas>
<script>
var ctx = document.getElementById("myChart");
var myChart = new Chart(ctx, {
    type: 'bar',
    data: {
/*これより下のパラメーターをいじってみてchart.jsの感覚を掴んでみてください!!*/
        labels: ["", "", "", "", "", ""],
        datasets: [{
            label: '得票数',
            data: [7, 8, 3, 5, 2, 3],
            backgroundColor: [
                'rgba(255, 99, 132, 0.2)',
                'rgba(54, 162, 235, 0.2)',
                'rgba(255, 206, 86, 0.2)',
                'rgba(75, 192, 192, 0.2)',
                'rgba(153, 102, 255, 0.2)',
                'rgba(255, 159, 64, 0.2)'
            ],
            borderColor: [
                'rgba(255,99,132,1)',
                'rgba(54, 162, 235, 1)',
                'rgba(255, 206, 86, 1)',
                'rgba(75, 192, 192, 1)',
                'rgba(153, 102, 255, 1)',
                'rgba(255, 159, 64, 1)'
            ],
            borderWidth: 1
        }]
    },
    options: {
        scales: {
            yAxes: [{
                ticks: {
                    beginAtZero:true
                }
            }]
        }
    }
});
</script>

↓こんな感じの棒グラフが出てくるので、コーヒーでも飲みながらやってみてください↓

スクリーンショット 2022-05-05 19.33.49.png

一応codepenでもchart.jsのパラメータをいじれるようにしときました。

See the Pen Untitled by matsuken314 (@matsuken314) on CodePen.

参考サイト

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
0
Help us understand the problem. What are the problem?