[Rails 6] Interactive graph drawing with Ajax + chart.js

This time, I will implement a simple chart function in Ruby on Rails using ajax and chat.js. As an example, let's draw the growth record of the person selected from the pull-down menu. (Horizontal axis: recording date, vertical axis: height) The Rails version is 6.0.3.3. 身長.gif

Please note

table definition

Column name fi Column description Data type
id ID integer
name name string
height height float
rec_date Height record date date
created_at Registration date datetime
updated_at Update date datetime

This time, the height record data for two people is registered in seeds.rb.

Preparation

Added to Gem

Gemfile


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

Add package with yarn

command


yarn add jquery chart.js

Be able to handle jQuery

config/webpack/environment.js


const { environment } = require('@rails/webpacker')
//Make jquery available
const webpack = require('webpack')
environment.plugins.prepend('Provide',
  new webpack.ProvidePlugin({
    $: 'jquery/src/jquery',
    jQuery: 'jquery/src/jquery'
  })
)
module.exports = environment

loading chart.js module

app/javascript/packs/application.js


// chart.Added for js use
require("chart.js")

Creating a model

command


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

Creation & input of initial data

seeds.rb


Height.create!(
  [
    {
      name: 'Takashi',
      height: 150.3,
      rec_date: "1990-01-03",
    },
    {
      name: 'Takashi',
      height: 168.3,
      rec_date: "1996-03-03",
    },
    {
      name: 'Takashi',
      height: 178.4,
      rec_date: "2003-04-03",
    },
    {
      name: 'snow',
      height: 130.3,
      rec_date: "1987-05-07",
    },
    {
      name: 'snow',
      height: 144.1,
      rec_date: "1995-04-23",
    },
    {
      name: 'snow',
      height: 153.6,
      rec_date: "2000-05-13",
    },
  ]
)

command


rails db:seed

Creating a controller

Prepare an index action and a search action in chart_sample_controller.rb. The search action is an action to receive a request transmission using $ .ajax () and return the height data corresponding to the name in json format by in-model search.

command


rails generate controller ChartSample index search

That's it for creating the packages, gems, models, and controllers you need. Next, prepare the route settings and views.

Route settings and views

Route setting

config/routes.rb


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

Views The js file is read by javascript_pack_tag, but here the script that draws the graph using Ajax + chart.js is written in jQuery. The js file is described in ʻapp / javascript / packs / chart_sample / chart_user_height.js`.

html:app/view/chart_sample/index.html.erb


<h1>Growth record</h1>
<div class="contents">
  <select id="area">
    <option value="0">Please select</option>
    <option value="1">Takashi</option>
    <option value="2">snow</option>
  </select>
</div>

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

<%= javascript_pack_tag 'chart_sample/chart_user_height' %>

The view screen looks like this: When it's done, select the name from the pull-down menu → the graph of the growth record is displayed below. スクリーンショット 2020-09-30 16.24.35.png

Ajax+chart.js The code here is long, so I will explain it step by step and paste the source code together at the end.

app/javascript/packs/chart_sample/chart_user_height.js (1)


$(document).on('turbolinks:load', function () {
  $(function () {
    //Load your own function for chart display
    var chart_func = require("../origin_func.js")
    $('#area').change(function() {
      var selected_name = $('option:selected').text();
      var selected_index = $('option:selected').val();

Here, in order to prevent the phenomenon that jQuery does not work properly due to page transition due to the influence of Ajax etc., turbolinks: load is set to fire both when loading for the first time and reloading. ʻOrigin_func.jsis loading a function of the chart drawing function by chart.js. This will be explained later. The index of the select box of the previous view is defined asselected_index and the name is defined as selected_name`.

app/javascript/packs/chart_sample/chart_user_height.js (2)


      if (selected_index > 0) {
        //Request the value of SelectBox in json format using ajax
        // messages/The index action of the searches controller receives
        $.ajax({
          type: 'GET', //Request type
          url: '/chart_sample/search', //URL to send the request
          data:  { name: selected_name }, //Data to send to the server
          dataType: 'json' //Type returned from the server
        })

Here, selected_index> 0, that is, what is processed when the name (this time" Takashi "or" Yuki ") is selected. Send the name obtained using $ .ajax () to the controller. As per the route setting above, it will be sent to the search action of chart_sample_controller.rb. This section describes the contents of the search action of the controller that received the request before the continuation of chart_user_height.js.

app/controllers/chart_sample_controller.rb


class ChartSampleController < ApplicationController
  def index
  end

  def search
    #↓ Search processing code(A list of search results is entered)
    @height = Height.where('name = (?)', "#{params[:name]}").order(rec_date: "ASC")    

    respond_to do |format|
      #If the requested format is HTML
      format.html { redirect_to :root }
      #If the requested format is JSON format
      format.json { render json: @height }
    end
  end
end

In the search action, the request (person's name) sent by Ajax earlier is stored in params [: name], so a match search is performed in the Height model, and the rec_date of the contents returned from the model is searched. Sorted in chronological order. This is because the horizontal axis will be rec_date later when displaying the graph with chart.js. Since the request is in JSON format, format.json is executed and the search results are returned in JSON format. If it is difficult to understand, try debugging with binding.pry etc. and it will be easier to get an image. Let's go back to the js file again and look at the drawing process.

app/javascript/packs/chart_sample/chart_user_height.js (3)


        //Receive request from ajax
        .done(function (data) {
          var height_val = [];
          var height_name = [];
          var height_date = [];
          // chart.Store in an array for passing to 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", height_date, height_val);
        })
      }
    })
  });
});

Here, the JSON format received from the model is converted to an array type in order to pass it to chart.js. For example, height_val is an array of Takashi-kun's past heights such as [(height), (height), (height)]. (I think there is a better way to write it a little more clearly ...) Then, pass it to the self-made function that reads the array containing the date data and height data. The self-made function instantiates Chart with chart.js. The argument passed to the function is chart_func.bar_chart (document, id name of canvas tag, label name to be attached to graph (optional), array of horizontal axis data, array of vertical axis data).

Depiction processing by 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');
    //Delete the instance if the chart is already drawn
    if(myChart.constructor === Chart){
        myChart.destroy();
    };
    //Depiction of bar graph
    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 the canvas is reused, the graph will be overlaid on the previous graph, so if an instance of Chart has already been created, use Chart.destroy for the description here. This is because it needs to be deleted. myChart.constructor will be Chart when an instance is created from HTMLCanvasElement at the time of loading. The rest is creating a Chart instance based on the array data. That's all for the implementation. I explained chart_user_height.js intermittently, so I will paste it below.

app/javascript/packs/chart_sample/chart_user_height.js (collectively)


$(document).on('turbolinks:load', function () {
  $(function () {
    //Load your own function for chart display
    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");  
        //Request the value of SelectBox in json format using ajax
        // messages/The index action of the searches controller receives
        $.ajax({
          type: 'GET', //Request type
          url: '/chart_sample/search', //URL to send the request
          data:  { name: selected_name }, //Data to send to the server
          dataType: 'json' //Type returned from the server
        })
        //Receive request from ajax
        .done(function (data) {
          var height_val = [];
          var height_name = [];
          var height_date = [];
          // chart.Store in an array for passing to 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", height_date, height_val);
        })
      }
    })
  });
});

Finally

If you have any questions, please feel free to comment. In the future, I would like to add a graph representation and a save function. Thank you for staying with us so far!

Recommended Posts

[Rails 6] Interactive graph drawing with Ajax + chart.js
Introduced graph function with rails
[Rails] Make pagination compatible with Ajax
Implement the Like feature in Ajax with Rails.
Output simple graph with Rails Use gem-chartkick / groupdate
[Rails 6] RuntimeError with $ rails s
Handle devise with Rails
Graph creation with JFreeChart
[Rails] Learning with Rails tutorial
[Rails] Test with RSpec
[Rails] Development with MySQL
Supports multilingualization with Rails!
Double polymorphic with Rails