Skip to main content

19 Charts with Chartkick and Groupdate

Episode 194 · June 19, 2017

Add various types of charts to your Rails app with Chartkick and use Groupdate to make easy group queries in SQL

ActiveRecord Frontend Javascript


Transcripts

What's up guys, this episode we're going to talk about ghraphing charts in your rails app, we're going to be using a library called Chartkick, which is actually a wrapper around chart js, google charts and Highcharts, maybe some other options, I'm not sure, but basically it's a wrapper around those, and a helper for you to take your rails queries, and convert that into JSON that those charting libraries can render appropriately, so it's kind of building out an agnostic way for you to do that. We're also going to use a gem called groupdate, which allows us to group by day, by week, by hour of the day, and a whole bunch of other things, and it even supports time zones, which is awesome. That's going to give us the ability to write a single one liner, where we can say we want a line chart that we group the users by day when they were created and we display the count, so for each of those days we get a count in this graph, and it takes your active_record query, it does the advanced group by day, which is part of active record by default, and then it goes and takes that result, converts it into a hash, and then passes that into the line chart which sets up everything so that Chartkick's JavaScript can go ahead and tell your graphing library, like chart js, or google charts or Highcharts to actually render this out on the page, and you get a lot of other options, like pie charts, column charts, bar charts, area charts and so on. You can even do even a few that are exclusive to Google charts, like the world map here, so your geographical charts and timelines, and all of this is handled for us really nicely by the Chartkick library, so we just need to install that gem, and the groupdate gem, and we'll have access to adding all of this functionality in. So let's take a look at an example. First off, let's start by adding gem for our Chartkick and groupdate into our Gemfile, and save those and then run bundle to install them. I'm going to open up the user's controller here, and show you what I've got right now, we simply load up all of the users, and then we render out the html format of this page, so if we go into that

app/views/user/index.html.erb

<h1>Users</h1>

<%= line_chart User.group_by_day(:created_at).count %>

We can display that on our page, but only after we get the chart js library added to our asset pipeline, so first off, we need to go to

app/assets/javascripts/application.js

//= require rails-ujs 
//= require turbolinks
//= require Chart bundle 
//= require chartkick 
//= require_tree 

If you'd like to use Google charts, or Heighcharts here, you can actually get rid of this Chart bundle line and add the appropriate line from the instructions for adding in the library as needed for google charts or high charts, but we're going to use this because it comes with Chartkick out of the box, so if we have all that put together well, this will generate a line chart for the data that we have in the user's table. So if we look at this, we can then see that we have every single user graphed down in our database, and I have data in here ranging back to June 2013, so we have 4 years worth of data here, and that is quite a lot to graph out. One of the examples that they mentioned is that we don't have to actually render out all that JSON right in our view. So if you look at this, in our view source, this actually has taken and generated a id="chart-1", and this has some JavaScript in there to create a new Chartkick linechart called "chart-1". Now of course, what we've got here is a huge amount of data for the chart to render out, so we can see that it's got a ton of stuff that it generated for those four years, and if you have a giant database, this can take a while to query all of that if you're looking across your entire data set, so what we want to do here, is figure out a better way of handling that, and the way we can do that, is instead of passing in the data directly into the line chart, we can actually give it a url so

<%= line_chart "/charts/new-users" %>

something like that, we can have a chart endpoint for all of the new users, and graph those out, and so then this could be what we use for our line chart instead, so we would pass that in, and really we would want to use the rails helper method for this, so we want something like

<%= line_chart charts_new_users_path %>

We need to dive into our routes and create one so that we can have a JSON response endpoint like so. If we hop over to our routes.rb

namespace :charts do 
    get "new-users"
    end 

rake routes

We can see that line here, at the bottom for charts/new-users, so we can use endpoints like this inside of our charts controller to generate those and have those passed back, so if we edit

app/controllers/charts_controller.rb

class ChartsController < ApplicationController 
    def new_users 
        render json: User.group_by_day(:created_at).court 
    end 
end 

What will happen now, is when we go back to the browser, we will see it says "loading", and then it makes an AJAX request for all of that same data, and then it displays that, so you saw it take a little bit of time where that query ran, and so this page can respond immediately, and then all of our endpoints can be run individually if you have a bunch of charts on your page you can have a bunch of requests running instead, so that your initial page load is not super duper slow, and possibly times out. That is good, and that gets us down to a 29 ms respond for the users index, and that query as you can see here took 111 miliseconds to query into accounts across four years of data. As you can tell, some of these queries can be extremely slow, so the benefit of making these as AJAX requests, instead of trying to query all of that in the controller before you render the page will save you a lot of time, you can give the user a page back to look at, they will be able to see the loading state of the charts while this data is being generated, and then once it's ready, they can see it. So if you have some fast graphs, you can display those immediately, but if you have any that are slow, you can do this so that the user at least knows that it's loading on the page, and whenever the query is finished, you can handle that. Another nice piece about this is that if you get a bunch of these endpoints, and you have some for users, you can just namespace :users section in here, and then you're going to have a charts/users/new or whatever, canceled, or subscribed or whatever, and you can have all of your endpoints organized by your model types here as well, so if you had maybe episodes like GoRails might, or whatever, you can have your namespaces here, and then organize all of that stuff inside of your controllers appropriately once this starts to get a lot more complex, and then, the beauty of that is that if you do all this, then in your views, you can simply render out that chart, and this doesn't have to do anything special in its own controller or in the views to query the database, all of that is handled inside of that AJAX request, so that makes it really easy to put charts anywhere you want in your application, and the data loading is already handled inside of the one controller action that responds to this url. Since we've got the group-date gem here, we can actually change this to group_by_month instead of by day, so that we can get a less dense graph, and that will automatically give us this one where it graphs the month, and you'll see that June 1rst was their very first month, a low month, but afterwards we had quite a few more users, and as you can see here, March 2014 was probably our biggest month of all time, so this is really interesting and it gives us a nice visual of the trends of our registration over that four years. Now, what if we want to see what months are the best months, and do something a little bit different, we can easily go in here and add a new chart, and so we might have one here that is "By Month", so we can add that in, and so instead of having our line_chart charts_new_users_path, we can create a new chart, and of course you would probably organize that a little better, but you would have charts_by_month_users_path, and in our charts_controller.rb*

def by_month_users 
    render json: User.group_by_month_of_year(:created_at).count 
end 

This will actually give us a graph that shows us which months are the most popular months, and because I've randomly generated data, it is actually a lot more stable than what you would probably see in your real data on a business, and you would see here that march is actually the highest month of all of the generated data that I've got, and unfortunately, it just gives us 1-12, and we would like to improve that, so

charts_controller.rb

def by_month_users 
    render json: User.group_by_month_of_year(:created_at).count.map{ |k, v| []}
end 

We should be able to refresh that and see that now we get January, February, March, April and May and so on in our graph, so we can go and tweak those values as necessary inside of our mapping in our controller, and then the charts can just simply hit that and grab whatever we need automatically, and because this data is not actually contiguous, we should probably change this to a column chart to display the data a little bit better, so if we do column chart, here we can see now those months and see which ones are our big ones, and which ones are our low ones, in a little bit better manner, because this is a time period, those lines should connect to each other, but there are actually looking at slices of January for any year, February for any year and so on, so a different style of graph fits this one a little bit better and we can do all kind of awesome things by changing it to a pie chart. This one isn't actually useful, because you can't tell the differences between it, but that can be useful in certain situations. Now I can't get into all the options and all the charts and everything you can do with this, but I can tell you that one of the most interesting things that they have, if you go down here there's a refresh tag, which you can pass in a number of seconds, and that will go ahead and grab your urls data again every number of seconds that you pass in, and update the chart accordingly, so if you want to graph out the new user's registrations for the last week or the last month, you can actually just have it refresh every 60 seconds, and keep showing you the real time data on that page, which I think is incredibly awesome to have just built into the library, so that makes it extremely easy if you set it up this way, where you have your charts organized nicely in their own namespace inside your controllers, and then your controllers are going to handle all of the queries, and so that leaves you with these awesome lines in your views which are only really caring about how that chart gets rendered, weather it needs to refresh or not, the colors, those other options, and so you've organized this in a nice way that when you add many charts to your app you have all of the database query stuff set up in the controllers, nice and separate from your views, and your views only really have to care about how it gets displayed on the page, and that is really it, so the Chartkick library makes all this extremely easy, almost too easy, and the group-date library makes querying for different data sets really really easy as well. I do want to mention that the group-date library has support for time zones, you have to set that either globally or inside of your queries, so you need to pass in the time zone option, which you could pass in from the user model or whatever if you have the time zone set to the specific users, you can also customize the day of the week that it starts on as well, and there's a few other options here, like you can grab the last eight weeks worth of stuff or whatever, so all of these have various options that you can use and take advantage of, all of this works out of the box on Postgresql, but if you're using MySQL, you need to install time zone support, and then, if you're using sqlite, some of this stuff is not going to work for you, so I would encourage you to make sure that you're using MySQL or Postgresql out of the box here. So if you're ever curious, one protip before we go, is that if you're ever curious what data they're using in these examples, when it says Meda.group(:country).count, I wasn't really sure what "Country" looked like, is it the country name? Like United States of America, or United States, or US or what, I wasn't really sure, and so I wanted to take a look at this and see what it actually did. So if you open up view source on the page and use search "GeoChart" you can see the exact line of JavaScript to render that. So let's take a look at this in our console here, and you'll see that they passed in a nested array of "United States", and "Russia" and "Germany", and so on. These all work by the name of the country but I was kind of curious if you could say: US, let's change it to 31, and you can rerun that code, which will re-render that template. Now we have the US as 31, and it has appropriately assigned that value to the right country, so you can use either one that you like, but the way that you can test out whether it works or not is you can take a look at the "view source" for the Chartkick website and see what JavaScript they're using, and then really you just need to figure out how to generate this type of data on your JSON endpoint, and another thing to point out here is that this example of course uses a nested array, but you may actually be returning a JSON object instead, so we can try it with that, and we can run it and say: Ok well if you have a JSON object instead of a nested array, that's going to work just as well passing that in. So you are free to use either option, and take a look at those examples and fiddle with them and see how they work, all you have to do is make sure you generate this JSON object correctly on your endpoints, and you can easily go test those out, because they're all set up on their own individual endpoints.

That's it for this episode on Chartkick, it was so easy to add these graphs to your rails app, that I almost didn't cover it because it was too easy, and that says something, so I hope you enjoyed this episode, and I will talk to you in the next one. Peace v

Transcript written by Miguel

Discussion