Making Charts with Open Flash Chart from Catalyst
Overview
Recently I was looking for a way to make some snazzy bar charts from a Catalyst application. I have successfully used GD and GD::Graph before, but wanted to try something different this time around. After exploring several possibilities I decided to have a crack at using Open Flash Chart version 2 (OFC2). In this paper, I discuss how I integrated Open Flash Chart into an existing Catalyst application.
Chart Data Source - The Model
The data source for the chart comes from student test scores stored in a database. I use a DBIx::Class custom ResultSet classes to get at the data, and then munge it a bit to suit my needs. As an example, here is how I get at averages for each of the five sections of the test for an arbitrary school and grade1.
sub school_section_averages { my $self = shift; my $admin_id = shift; my $school_id = shift; my $school_section_scores = $self->search_rs( { 'me.admin_id' => $admin_id, 'student2class.school_id' => $school_id, }, { 'select' => [ { avg => 'me.percentage_score' }, 'me.section_id', 'section.name', 'section.position', ], 'as' => [ 'average_score', 'section_id', 'section_name', 'section_position', ], 'join' => [ 'student2class', 'section' ], 'group_by' => [ 'me.section_id', 'section.name', 'section.position', ], 'order_by' => ['section.position'], } ); my @school_section_averages; while ( my $section_average = $school_section_scores->next ) { push @school_section_averages, sprintf( "%.0f", $section_average->get_column('average_score') ); } return \@school_section_averages; }
I do something similar to get at section averages by classroom teacher.
Mediating the Data - The Controller
In a standard Catalyst controller, I acquire the underlying data, and ship it to a Private action for further processing2. This processing is where I create a Perl data structure that defines all the attributes of a the chart. This data structure is then transformed into JSON which is the data format required by Open Flash Chart3. Let’s see what the majority of the struture looks like:
my $chart_data = { 'bg_colour' => '#ffffff', 'y_axis' => { 'tick_length' => 3, 'colour' => '#d000d0', 'stroke' => 1, 'max' => 100, 'grid_colour' => '#00ff00', 'offset' => 0, 'steps' => 10, }, 'x_axis' => { 'colour' => '#d000d0', 'stroke' => 1, 'labels' => { 'labels' => $labels, 'size' => 12, }, 'grid_colour' => '#00ff00', 'tick_height' => 10 }, 'elements' => $elements, 'title' => { 'text' => $title, 'style' => '{font-size: 20px; color:black; font-family: Verdana sans; text-align: center;}' }, 'y_legend' => { 'text' => 'Percent Correct', 'style' => '{color: #736AFF; font-size: 14px;}' }, 'tooltip' => { 'mouse' => 2, 'stroke' => 1, 'colour' => '#000000', 'background' => '#ffffff' }, };
The three variables $labels, $elements and $title have to be constructed as well, but I’m not showing them in their entirety here. Suffice it to say $elements is the most interesting where I define the actually chart values and chart styles. The $elements is an ArrayRef[HashRef] where the HashRef values could be a scalar or another ArrayRef. To help ease your head from spinning here is example of one element4:
{"alpha":"0.5","colour":"#9933CC","text":"Fergie Liscious","values":[93,84,92,89,88],"type":"bar","font-size":12}
Once I’ve packaged up the data into a Perl data structure, I can turn it into JSON with the help of the JSON module like so:
use JSON; my $json = new JSON; my $json_chart_data = $json->encode($chart_data);
Turning Data into a Chart - The View
Now that we have the data into the format needed by OFC2. Let’s focus on building the view. The approach I take is to construct a web page via Template Toolkit (TT) that has the necessary Flash and Javascript library links along with the JSON data embedded in the page. Here’s what the TT template for the page head looks like:
<script type="text/javascript" src="[% c.uri_for('/static/chart/json2.js') %]"></script> <script type="text/javascript" src="[% c.uri_for('/static/chart/swfobject.js') %]"></script> <script type="text/javascript"> swfobject.embedSWF("[% c.uri_for('/static/chart/open-flash-chart-custom.swf') %]", "ofc_averages_chart", "800", "400", "9.0.0", "[% c.uri_for('/static/chart/expressInstall.swf') %]"); </script> <script type="text/javascript"> function open_flash_chart_data() { // read data return JSON.stringify(data); } function findSWF(movieName) { if (navigator.appName.indexOf("Microsoft")!= -1) { return window[movieName]; } else { return document[movieName]; } } var data = [% json_chart_data %]; </script>
Lets’s go through this template in parts:
json2.js
Javascript JSON library used to stringify the JSON for OFC2
swfobject.js
Helper Javascript library to reduce the amount of javascript code needed in this template.
open-flash-chart-custom.swf
This Flash application is the center-piece of the charting process. It is a custom version of OFC25 It uses the open_flash_chart_data() function to consume the chart data and render the chart. The ofc_averages_chart parameter is the id of an HTML div where the chart will be placed in the web page.
Final View
What does all this work lead to? An example of the final output can be seen HERE
Footnotes
1 The $admin_id represent a test given to a certain grade level at a certain time. e.g First Grade Spring 20009.
2 Yes, I would be wise to put my controller on a diet and move more of the data processing into the model.
3 There are some Perl modules that can assist this process but the mature one is for the older (non-JSON) format, while the newer one is not stable.
4 I’m actually showing the element in it’s final JSON form, but the Perl form is very close, just substitute ‘:’ with ’ => ‘.
5 I’m using a slightly modified version of OFC2. It includes a patch that provides keys to shapes. The patch came from DZ. This custom library was compiled with Flex SDK 3.3 on Linux. Bug me for another article if that interests you.
Showing changes from previous revision. Removed | Added
