DEV Community

Jameel Ur Rahman
Jameel Ur Rahman

Posted on • Updated on

ChartJS with Rails 6 and Webpacker

TOC

What this covers

Setting up ChartJS on a vanilla Rails 6 application

What prompted me to write this

For a long time now I wanted to build an uptime tracker with a clean developer friendly UX. Having used Pingdom in my workplace I found it quite inflexible and I "think" I can do better. 🤞

My professional experience was in RoR although pre Rails 6 and still using Sprockets. When building my uptime tracker I wanted to use ChartJS for visualizations and it took me a surprisingly long time to figure out how to do so. So I thought I'd share my findings.

Webpacker and Sprockets

Sprockets is a ruby library for compiling and serving web assets.

The latest version of Rails (I'm not sure at what point they made this switch) uses Webpacker.

Think of webpacker as an alternative to sprockets. It makes use of WebPack under the hood to help manage JS in Rails.

When using JS with Webpacker it's important to understand scope. I still haven't wrapped my head around using webpacker properly yet, but know this, Webpack does not make modules available to the global scope by default. This is part 1 of what tripped me up.

Setting up ChartJS on a vanilla Rails 6 app

Step 1: Vanilla Rails App

Make sure you have rails installed. I generally prefer to use rbenv. It makes it easier to manage different ruby versions.

  • Create the app using rails new chartjs-example
  • Let's also setup a simple home page
    • In your shell run rails generate controller Home index
    • Then edit routes.rb.
Rails.application.routes.draw do
  root to: 'home#index'
end
Enter fullscreen mode Exit fullscreen mode
  • In your shell run rails s then on your web browser go to localhost:3000. It should show you an empty page.

alt text

You now have a Vanilla Rails 6 application with a default home page.

Step 2: Install ChartJS

  • Use yarn (current default with Rails 6) to install chartjs. On your shell run yarn add chart.js
  • You now need to import and register all the ChartJS modules you need to use. Let's assume you need everything. You can go to the file /app/javascript/packs/application.js add then following import Chart from 'chart.js/auto';

You need to include import Chart from 'chart.js/auto'; as simply including require 'chart.js' will not work. You can choose to import specific modules by following the instructions here.

This is part 2 of what tripped me up for a long time.

Step 3: Create the chart

  • Go to app/views/home/index.html.erb and add the following
<canvas id="myChart" width="400px" height="400px"></canvas>
Enter fullscreen mode Exit fullscreen mode

We can then create a script tag and instantiate a new chart object to update the canvas
<script>
var myChart = new Chart(ctx, {...});
</script>

No you can't. Like I mentioned before, webpacker doesn't include things in the global scope by default. If you did the above and visited the page, you'd find your console throwing the error message

Uncaught ReferenceError: Chart is not defined

Instead you need to do the following

  • Add the following lines to your application.js. As you've imported your ChartJS modules there, Chart can be accessed within that scope.
document.addEventListener('turbolinks:load', () => {
  var ctx = document.getElementById('myChart').getContext('2d');
  var myChart = new Chart(ctx, {
  type: 'line',
  data: {
    labels: JSON.parse(ctx.canvas.dataset.labels),
    datasets: [{
      data: JSON.parse(ctx.canvas.dataset.data),
    }]
  },
  });
})
Enter fullscreen mode Exit fullscreen mode

Notice that you've not defined your datasets yet. i.e. labels (x axis) and data (y axis). You need to do that using the data- attributes in html.

  • Update index.html.erb and replace the previous canvas line with
<canvas id="myChart" width="200px" height="100px" data-labels="<%= @data_keys %>" data-data="<%= @data_values %>" ></canvas>
Enter fullscreen mode Exit fullscreen mode
  • Then you can pass your data to your view through your controller leaving the heavy data preparation logic outside your view. In app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    @data_keys = [
      'January',
      'February',
      'March',
      'April',
      'May',
      'June',
    ]
    @data_values = [0, 10, 5, 2, 20, 30, 45]
  end
end
Enter fullscreen mode Exit fullscreen mode

Where data_keys and data_values are populated with sample data.

If you now go to http://localhost:3000/ you'll see your newly created chart.

alt text

Key Takeaways

  • Rails 6 uses Webpacker and Yarn by default
  • Webpack does not make modules available to the global scope by default
  • Define your Chart inside application.js's scope and pass your data to the js snippet using the data- attributes

Appendix

Since I sometimes really want to see the whole file in a tutorial instead of just the change you need to make.

config/routes.rb

Rails.application.routes.draw do
  root to: 'home#index'
end
Enter fullscreen mode Exit fullscreen mode

app/views/home/index.html.erb

<h1>Home#index</h1>
<p>Find me in app/views/home/index.html.erb</p>

<canvas id="myChart" width="200px" height="100px" data-labels="<%= @data_keys %>" data-data="<%= @data_values %>" ></canvas>
Enter fullscreen mode Exit fullscreen mode

app/controllers/home_controller.rb

class HomeController < ApplicationController
  def index
    @data_keys = [
      'January',
      'February',
      'March',
      'April',
      'May',
      'June',
    ]
    @data_values = [0, 10, 5, 2, 20, 30, 45]
  end
end
Enter fullscreen mode Exit fullscreen mode

app/javascript/packs/application.js

// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"
import Chart from 'chart.js/auto';

Rails.start()
Turbolinks.start()
ActiveStorage.start()

document.addEventListener('turbolinks:load', () => {
    var ctx = document.getElementById('myChart').getContext('2d');
    var myChart = new Chart(ctx, {
    type: 'line',
    data: {
        labels: JSON.parse(ctx.canvas.dataset.labels),
        datasets: [{
            data: JSON.parse(ctx.canvas.dataset.data),
        }]
    },
    });
})
Enter fullscreen mode Exit fullscreen mode

If you found this useful please let me know! :D

Top comments (6)

Collapse
 
julrocas profile image
Julio Castillo

Super useful!
I've also found that if you want to use Chart.js in your views you can register it globally in application.js like this


import Chart from 'chart.js/auto';
global.Chart = Chart;

Enter fullscreen mode Exit fullscreen mode

And then you can call new Chart(...) in your views :D

Collapse
 
johnpitchko profile image
John Pitchko

This was perfect; thank you for sharing.

Collapse
 
wanderingsoul profile image
Jameel Ur Rahman

Thanks for the tip!

Collapse
 
alexreservationgenie profile image
Alex Key

Awesome write up! I didn't think about using a dataset on the canvas to store the labels/data. Have you used that method on a larger dataset? I'm working on it now, but wasn't sure if there would be any problems.

Before I always used an ajax call to hit an endpoint that returns the data so I definitely like this method better.

Also, I was getting undefined using ctx.canvas so I just used ctx.dataset.data

Collapse
 
igorkasyanchuk profile image
Igor Kasyanchuk

One more options from me: github.com/railsjazz/rails_charts

Collapse
 
bellenss profile image
Steven Bellens

Thanks for the article, confirming this is still working with Rails 7 & Turbo