a_la_chart

DESCRIPTION:

‘a la Chart’ (ALC) is a framework for managing various chart implementations - from grabbing the data, to declaring how those values are mapped to the desired type of chart (pie, line, bar, etc), and finally rendering them. Note: ALC is very much in alpha development and not recommended for public use.

SUPPORT:

Note: very much in alpha development

INSTALL:

If you use Bundler, it is highly recommended to just add this line to your Gemfile

gem "a_la_chart"

OR - to install this as a gem from rubygems.org

gem sources -a http://rubygems.org
gem install a_la_chart

Set up the gem in your environment.rb

config.gem "a_la_chart", :source => "http://rubygems.org"

OR - to install this directly as a Rails plugin (not recommended, but available):

script/plugin install git://github.com/mobi/a_la_chart.git

Once you have installed ‘a la Chart’, you can start creating charts immediately. Since Google Charts can function without any further install (it just hits a Google URL to generate an image inline), you can technically use ‘a la Chart’ (ALC) without any further install - but that’s also kind of boring. If you need to use another supported charting framework, such as Fusion Charts or gRaphaël, you must install them locally, as per their normal install instructions. This generally means placing the required files (javascript, flash) in your project’s /public directory, and linking to them via html header tags in your relevant /app/views/layouts files.

For example, in our project we use the gRaphaël dot chart, so populate our /app/views/layouts/reports.html.haml with this HAML javascript tag

= javascript_include_tag "raphael", "jquery", "g.raphael.js", "g.dot.js"

We also use Fusion charts, and install them in a similar way (along with the necessary flash and javascript files under /public)

= javascript_include_tag "FusionCharts", "FusionMaps", "FusionChartsDOM"

USAGE:

ALC logic is in two parts: controller and view. To activate a controller add the ‘a_la_chart’ directive

class PeopleController < ApplicationController
  a_la_chart
end

Data and Mapping

To declare the style of data returned, you call ‘chart’ with the style of chart (you can have more than one, eg. ‘chart :pie, :line’). Within the chart directive, you return the list of ‘data’ objects (Objects or Hashes). You pass in a Hash which describes how to map each object to the relevant chart data. For example, we have a Person model, tied to a city name. We want to build a pie chart which counts the people in each city.

class PeopleController < ApplicationController
  a_la_chart

  chart :pie do
    data(:label => :city, :value => :total) do
      Person.all(:select => 'city, COUNT(people.*) as total', :group => 'city')
    end
  end
end

chart.apis.google.com/chart?cht=p&chd=t:6,5,3&chs=300x180&chl=Chicago|Indy|New%20York&.jpg

In a pie chart, there are two kinds of required data per slice: A value (how many people live in each city) and a label (what is the name of the city). The data Hash points the two generic requirements (:label and :value) to specific methods in the returned objects (or key names, if an array of Hash objects are returned). When generating the chart data, ALC will effectively call “item.city” for each label, and “item.total” for each value.

If your query does not return data which maps to a specific method or key, you can also map requirements to procedure blocks (Proc.new, proc, or lambda) which procedurally extracts values from each item in the returned list of data values, or integers which point to Array positions.

chart :pie do
  data(:label => proc{|item| item[0].blank? ? 'Unknown' : item[0]}, :value => 1) do
    Person.count(:group => :gender)
  end
end

ALC will effectively call “item.blank? ? ‘Unknown’ : item” to create each label, and “item” for each value.

View Chart

We have yet to define the type of charting engine we wish to use. Since that is a view-level decision, we will place that in the view. Before continuing, let us assume you wish to display a report page with a table of people information - alongside two pie charts. One which displays the count of people per city, and another which displays their genders.

To start, create a “def index” in the PeopleController which returns the list of people, as you normally would.

class PeopleController < ApplicationController
  a_la_chart

  chart :pie do
    data :label => :city, :value => :total do
      Person.all(:select => 'city, COUNT(people.*) as total', :group => 'city')
    end
    data :gender, :label => proc{|item| item[0].blank? ? 'Unknown' : item[0]}, :value => 1 do
      Person.count(:group => :gender)
    end
  end

  def index
    @people = People.all
  end
end

The corresponding index.html.haml (or index.html.erb, or whatever) will look something like this

= chart_tag :google, :pie
= chart_tag :google, :pie, :case => :gender
%table
  %tr
    %th Name
    %th City
  - @people.each do |person|
    %tr
      %td&= person.name
      %td&= person.city

Except for chart_tag, it’s pretty normal. In the first chart we pass in the minimum required fields for chart_tag: the chart implementation (:google, :fusion, :raphael are the first to be supported, more to come), and the chart type (dependent on the implementation, see a_la_chart/configs/*/config.yml for the lists). The second pie chart is populated with the :gender pie data. “:case” is how we can support several styles of data for a single type of chart.

Multiple Chart Support

What if we want to display the city data as a :bar chart? There are two options. The first option, we can move the city data to a separate chart block. You can have as many chart blocks in a single controller as supported types. While we’re at it, let’s rename the query select names to be ‘label’ and ‘value’, to avoid the extra typing of remapping the data.

chart :bar do
  data do
    Person.all(:select => 'city as label, COUNT(people.*) as value', :group => 'city')
  end
end

chart :pie do
  data :label => proc{|item| item[0].blank? ? 'Unknown' : item[0]}, :value => 1 do
    Person.count(:group => :gender)
  end
end

Then the view simply requests it.

= chart_tag :google, :bar
= chart_tag :google, :pie

chart.apis.google.com/chart?chs=320x120&cht=bhs&chd=s:Zxz&chxr=0,0,61&chxt=y,x&chxl=0:|Indy|Chicago|New%20York|&.jpg

Now you have two chart types. But suppose you want to show the same data in two ways. Since chart data can sometimes be fundamentally the same, deciding how to display the data is purely a view-level decision. For this Second option, you can declare multiple chart types in a single block. We will merge our charts together again.

chart :pie, :bar do
  data do
    Person.all(:select => 'label, COUNT(people.*) as value', :group => 'city')
  end
  data :gender, :label => proc{|item| item[0].blank? ? 'Unknown' : item[0]}, :value => 1 do
    Person.count(:group => :gender)
  end
end

And in the view

= chart_tag :google, :bar
= chart_tag :google, :pie, :case => :gender

More View Chart Config

The above example shows the minimum configs required. However, certain configs are supported by all implementations, such as :width and :height.

= chart_tag :fusion, :angular, :height => 300, :width => 300, :title => 'My Angular Chart'

More to come…

CONFIGURATION

Overriding Config

Internal configs can be overridden in rails environment configs (eg. environment.rb). For example, to use a custom :inline ERB template (paths are based on RAILS_ROOT):

ALaChart::Config[:fusion][:v3_1][:pie][:data] = 'app/views/a_la_charts/custom_inline.html.erb'

Then just copy the original template from the gem config dir, and make the desired changes.

NOTE: Creating custom templates can be complex and tie your project closely to a particular version of ALC. In short, caveat emptor, pal.

Custom Templates

Creating custom configs is done just like overriding configs - be sure you do not over write any existing chart configs.

For example, suppose you want the Fusion Stacked Column chart to contain an HTML legend table to of your own design, but you do not want to overwrite the existing Stacked Column chart. You can create a new chart like this (similar to the YAML stacked_column config):

ALaChart::Config[:fusion][:v3_1][:legend_stacked_column] = {
  :chart_type => 'StackedColumn2D',
  :inline => 'app/views/a_la_charts/legend_stacked_column.html.erb'
}

Next, copy the inline template from the a la chart config directory, place it in the path denoted by your :inline configuration, and paste your custom legend table at the end (or whatever changes you want), for example:

<table class="legend">
  <tr>
    <th colspan="2">Category</th>
    <th>Total</th>
  </tr>
  <% color_palette_clear(:fusion, :stacked_column) %>
  <% @totals.each do |total| %>
    <tr>
      <td style="background-color:#<%= color_palette_next(:fusion, :stacked_column) %>">&nbsp;</td>
      <td><%= total[:category] %></td>
      <td><%= total[:quantity] %></td>
    </tr>
  <% end %>
</table>

‘color_palette_clear’ and ‘color_palette_next’ are a built-in functions to reset and pop color values from the color_palette stack. They are two of several built in helpers. View them all in a_la_chart_helper.rb. But note, since they are still considered internal, they are subject to change from version to version. Again, caveat emptor.

THEMING

By default a la chart comes with no themes, and allows each framework to declare its own defaults. However, you probably want your charts to have a certain global look. The problem being that different frameworks have different configurations. For now, ALC does not attempt to replace the theming with any sort of universal mechanism (if you want a red pie chart for Fusion and Google, you must create two themes). This may change in subsequent versions.

For example, Fusion Charts have rich customizability, which must be configured with the chart data. Fusion can be given a custom color palette (the ordered list of colors to be ‘popped’ for each data segment), and a set of default chart options (values that will appear in the xml data ‘chart’ tag).

ALaChart::Config[:fusion][:theme] = :my_theme
ALaChart::Config[:fusion][:themes][:my_theme] = {
  :color_palette => [ '7BB465', 'B2B4B6', 'FEC35A', '65A4B5', '9E65B5', 'B57765', 'F7DF65' ],
  :default_options => {
    :useRoundEdges => '0',
    :alpha => '100',
    :bgColor => 'ffffff'
  }
}

More to come…

LICENSE:

Copyright © 2009 Mobi, released under the MIT license