神刀安全网

Make Easy Graphs and Charts on Rails with Chartkick

Make Easy Graphs and Charts on Rails with Chartkick

We work with data presented in the various formats every day. From my point of view, one of the most convenient formats for presenting numerical data is a graph. Users like graphs, especially interactive ones, because they are beautiful and fun – in contrast to dull static tables.

There are many solutions to render beautiful interactive graphs in web apps, but today we will discuss something special: a solution built for Rails that makes rendering graphs a breeze. This solution is called Chartkick and it was built by Andrew Kane. Chartkick can work with Google Charts , Highcharts , and Chart.js . It has many options for customization and also has a bunch of supporting libraries like groupdate , hightop , and active_median .

In this article we will discuss how to integrate Chartkick into a Rails app, render various graphs, customize them, make them load asynchronously, and how to further power up your code with groupdate and hightop gems.

The source code can be found on GitHub .

The working demo is available at Heroku .

Preparing the Application

Go ahead and create a new Rails application:

$ rails new HappyGrapher -T 

For this article I’ll be using Rails 5 release candidate, but the provided code examples should work with Rails 4 and 3 as well.

Suppose our app keeps track of “sporters” (athletes) and the competitions in which they participate. Here is the information about all tables we will require:

sporters

  • name ( string )
  • age ( integer ) – for this demo let’s suppose the age lies in the interval from 18 to 50
  • country_id ( integer ) – a foreign key to establish relation between the sporters and countries

countries

  • title ( string ) – we will add 50 random countries into our database

competitions

  • title ( string ) – we will have a bunch of absolutely random competitions

competition_results

This will be an intermediate table to establish a many-to-many relation between sporters and competitions .

  • sporter_id ( integer ) – a foreign key
  • competition_id ( integer ) – a foreign key
  • place ( integer ) – which place did the sporter take. For this demo, we’ll suppose that this column may take values from 1 to 6.

Create and apply all the necessary migrations:

$ rails g model Country name:string $ rails g model Sporter name:string age:integer country:references $ rails g model Competition title:string $ rails g model CompetitionResult sporter:references competition:references place:integer $ rake db:migrate 

Also create a controller, a view and a root route:

statistics_controller.rb

class StatisticsController < ApplicationController   def index   end end 

views/statistics/index.html.erb

<h1>Statistics</h1> 

config/routes.rb

[...] root 'statistics#index' [...] 

So far so good, but to render charts we will obviously require some sample data, so let’s add that now.

Loading Sample Data

To speed up the process of loading sample data, I’ll use two gems: faker that allows you to generate various texts from names and e-mails to paragraphs and pseudo-hacker phrases, and countries that greatly simplifies fetching information about existing countries. Add these gems into the Gemfile :

Gemfile

[...] gem 'countries' gem 'faker' [...] 

and install them:

$ bundle install 

Now open up seeds.rb file and paste this code to add 50 countries:

db/seeds.rb

ISO3166::Country.all.shuffle.first(50).each do |country|   Country.create({name: country.name}) end 

This will add totally random countries so don’t be surprised if you’ll end up having Antarctica or Sao Tome in your table.

Now we also need a bunch of sporters:

db/seeds.rb

100.times { Sporter.create({              name: Faker::Name.name,              age: rand(18..50),              country_id: rand(1..50)          }) } 

Here we use Faker to generate a sample name.

Next, competitions. I wasn’t able to find any ready-to-use list, so we’ll type sports names by hand:

db/seeds.rb

%w(tennis parachuting badminton archery chess boxing racing golf running skiing walking cycling surfing swimming skeleton).each {|c| Competition.create({title: c}) } 

And, lastly, the competition results:

db/seeds.rb

Competition.all.each do |competition|   sporters = Sporter.all.shuffle.first(6)   (1..6).each do |place|     CompetitionResult.create({                        sporter_id: sporters.pop.id,                        competition_id: competition.id,                        place: place,                        created_at: rand(5.years.ago..Time.now)                     })   end end 

Notes that we override the created_at column pretending that a competition took place some years or months ago.

Displaying a Simple Chart

Okay, everything is ready to start implementing the core functionality – graphs. Add the chartkick gem into the Gemfile :

Gemfile

[...] gem 'chartkick' [...] 

and install it:

$ bundle install 

Chartkick supports both Google Charts and Highcharts (with beta support for Chart.js ). For this demo I’ll be using Highcharts, but the installation process of Google Charts is very similar.

First of all, download the latest version of Highcharts and place it inside the javascripts directory (alternatively you may load it via CDN by adding javascript_include_tag into the layouts/application.html.erb ). Next, add these two files:

javascripts/application.js

[...] //= require highcharts //= require chartkick [...] 

That’s pretty much – we can now render charts. For starters, let’s display a bar chart illustrating sporters’ ages.

Load all sporters:

statistics_controller.rb

[...] def index   @sporters = Sporter.all end [...] 

and tweak the view:

views/statistics/index.html.erb

<%= bar_chart @sporters.group(:age).count %> 

We simply group sporters by age and calculate the number of items in each group. Really simple.

You are probably wondering how to define settings for your graph to give it a name, adjust width, height, and other stuff. That’s easy as well – some settings are being passed as arguments directly to the bar_chart (and other similar methods), other are being set inside the :library option.

Create a new StatisticsHelper and extract code there:

statistics_helper.rb

module StatisticsHelper   def sporters_by_age     bar_chart @sporters.group(:age).count, height: '500px', library: {       title: {text: 'Sporters by age', x: -20},       yAxis: {          allowDecimals: false,          title: {              text: 'Ages count'          }       },       xAxis: {          title: {              text: 'Age'          }       }     }   end end 

:library contains library-specific settings. Here we are preventing decimal numbers from appearing on the Y axis (obviously, we can’t have 2,5 sporters aged 20) and give it a name. X axis also has a name defined. On top of that, provide the name for the whole graph (it will appear at the bottom by default). Highcharts has loads of other options available, so be sure to browse its documentation .

Also note that settings can be defined globally as well.

Using Hightop

If you wish to display only the most “popular” ages among sporters, use the hightop gem – a small but useful library designed to solve such tasks. Simply include it into the Gemfile :

Gemfile

[...] gem 'hightop' [...] 

and run

$ bundle install 

Now you can display, for example, the ten most popular ages:

views/statistics/index.html.erb

<%= bar_chart @sporters.top(:age, 10) %> 

Rendering Graphs Asynchronously

If your database has a lot of data to process in order to render a graph, the page will load slowly. Therefore, it is better to render your graphs asynchronously. Chartkick supports this functionality, too. All you have to do is create a separate route and a controller action, then use this route inside a view. Note that it requires jQuery or Zepto.js to be present.

Create a new route:

config/routes.rb

[...] resources :charts, only: [] do   collection do     get 'sporters_by_age'   end end [...] 

and a controller:

charts_controller.rb

class ChartsController < ApplicationController   def sporters_by_age     result = Sporter.group(:age).count     render json: [{name: 'Count', data: result}]   end end 

Having this in place, simply modify your helper to call the newly created route:

statistics_helper.rb

[...] def sporters_by_age   bar_chart sporters_by_age_charts_path, height: '500px', library: {     [...]   } end [...] 

Now your chart will be loaded asynchronously allowing users to browse other contents on the page. The @sporters instance variable is not needed anymore, so you can remove it from the index method of StatisticsController .

More Graph Types

Column Chart

To demonstrate the usage of the column chart, let’s display how many sporters each country has. First of all, create a new helper similar to the one we defined earlier:

statistics_helper.rb

[...] def sporters_by_country   column_chart sporters_by_country_charts_path, library: {       title: {text: 'Sporters by country', x: -20},       yAxis: {           title: {               text: 'Sporters count'           }       },       xAxis: {           title: {               text: 'Country'           }       }   } end [...] 

Use it inside the view:

views/statistics/index.html.erb

[...] <%= sporters_by_country %> [...] 

Add the route:

config/routes.rb

[...] resources :charts, only: [] do   collection do     get 'sporters_by_age'     get 'sporters_by_country'   end end [...] 

As for the controller action, it is going to be a bit more complex as we have to construct the result manually:

charts_controller.rb

[...] def sporters_by_country   result = {}   Country.all.map do |c|     result[c.name] = c.sporters.count   end   render json: [{name: 'Count', data: result}] end [...] 

Note how the result hash is being constructed – the key is the country’s name and the value is the total number of sporters.

Reload the page and observe the result!

Stacked Column Chart

Let’s also display how many times each country took a certain place (from 1 to 6). Once again, define a new helper:

statistics_helper.rb

[...] def results_by_country   column_chart results_by_country_charts_path, stacked: true, height: '500px', library: {       title: {text: 'Results by country', x: -20},       yAxis: {           title: {               text: 'Count'           }       },       xAxis: {           title: {               text: 'Countries and places'           }       }   } end [...] 

Note the stacked: true option that provides the following result:

Make Easy Graphs and Charts on Rails with Chartkick

Use the helper inside the view:

views/statistics/index.html.erb

[...] <%= results_by_country %> [...] 

Add the route:

config/routes.rb

[...] resources :charts, only: [] do   collection do     get 'sporters_by_age'     get 'sporters_by_country'     get 'results_by_country'   end end [...] 

Lastly, create the controller action:

charts_controller.rb

[...] def results_by_country   result = Country.all.map do |c|     places = {}     (1..6).each do |place|       places[place] = c.sporters.joins(:competition_results).           where("competition_results.place = #{place}").count     end     {         name: c.name,         data: places     }   end   render json: result end [...] 

We take all the countries and use map to construct an array of data. Inside, find all the sporters from this country who took a certain place. joins is used to join with the competition_results table because information about the place is stored there. Then simply use where and count to get the desired value. Then, as we’ve already seen, assign the country’s name for the :name and the places hash for the :data . As a result, an array of hashes will be created.

Line Chart and Groupdate

The last chart type we will tackle today is the line chart. To demonstrate it, let’s display how many competitions were held each year.

Once again, create a helper

statistics_helper.rb

[...] def competitions_by_year   line_chart competitions_by_year_charts_path, library: {       title: {text: 'Competitions by year', x: -20},       yAxis: {           crosshair: true,           title: {               text: 'Competitions count'           }       },       xAxis: {           crosshair: true,           title: {               text: 'Year'           }       }   } end [...] 

:crosshair option is used to display a helpful crosshair following the user’s pointer.

Make Easy Graphs and Charts on Rails with Chartkick

Use this helper in your view:

views/statistics/index.html.erb

[...] <%= competitions_by_year %> [...] 

and add a new route:

config/routes.rb

[...] resources :charts, only: [] do   collection do     get 'sporters_by_age'     get 'sporters_by_country'     get 'results_by_country'     get 'competitions_by_year'   end end [...] 

Now we need to create a new controller action, but how are we going to group competitions by year and count them? Of course, we may construct our own query, but the author of Chartkick already took care of it and crafted a handy Groupdate gem . As the name suggests, it allows you to group records by year, month, day, and more. It supports timezones, ranges of dates, formatting, ordering, and other fancy stuff, so it is a nice solution to use with Chartkick.

Add Groupdate into your Gemfile :

Gemfile

[...] gem 'groupdate' [...] 

The only problem with Groupdate is that it does not support SQLite3, so you’ll have to user some other DMBS. For this demo I’ll be using PostgreSQL. Please note that if you decide to use MySQL, timezone support must be installed as well.

Tweak your Gemfile once again by replacing sqlite3 with the pg gem:

Gemfile

[...] gem 'pg' [...] 

Then install the gems:

$ bundle install 

and modify the database.yml config file:

config/database.yml

[...] development:   adapter: postgresql   encoding: unicode   database: your_database   pool: 5   username: your_user   password: your_password   host: localhost   port: 5432 [...] 

Now run the migrations again and populate the tables with sample data

$ rake db:migrate $ rake db:seed 

Don’t forget to create a database ( rake db:create ) prior to performing those commands.

Now we can code the controller’s action:

charts_controller.rb

[...] def competitions_by_year   result = CompetitionResult.group_by_year(:created_at, format: "%Y").count   render json: [{name: 'Count', data: result}] end [...] 

Looks great. The :format option allows you to provide the format for the keys. As long as we want to display only the years, I used %Y . The full list of available directives can be found in the official Ruby documentation .

Reload your page once again and observe the final result. If you are not satisfied with how the graphs look like, play with the display settings found in the Highcharts documentation .

Conclusion

In this article we’ve discussed Chartkick – a great gem to simplify chart rendering. We tried using various types of charts, made them loading asynchronously, and also employed additional gems, like Groupdate and Hightop. Of course, there is more to these gems, so be sure to browse their docs and experiment further with the code.

As always, feel free to post your questions and feedback. Happy charting and see you soon!

Tags:’Charts and Graphs’, Ruby on Rails

Ilya Bodrov is personal IT teacher, a senior engineer working at Campaigner LLC, author and teaching assistant at Sitepoint and lecturer at Moscow Aviations Institute. His primary programming languages are Ruby (with Rails) and JavaScript. He enjoys coding, teaching people and learning new things. Ilya also has some Cisco and Microsoft certificates and was working as a tutor in an educational center for a couple of years. In his free time he tweets, writes posts for his website , participates in OpenSource projects, goes in for sports and plays music.

转载本站任何文章请注明:转载至神刀安全网,谢谢神刀安全网 » Make Easy Graphs and Charts on Rails with Chartkick

分享到:更多 ()

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址