DRAWING GRAPH CHARTS
OVERVIEW
Statistical data is best viewed using graph images. A possible way to integrate graphs into a Catalyst Application could result in creating a custom View for drawing the graphs requested. Today we will examine how such a task could look like.
Preparations
First we have to find and get warm with graph drawing libraries. There are plenty of them we can choose from on CPAN. My choice was Imager::Graph. This module offers the typical kinds of graphs (Area, Bar, Column, Horizontal, Line, Pie and StackedColumn) and allows customization of almost every parameter.
Imager::Graph
depends on Imager, so
the same prerequisites apply here. Please see
Scaling images on demand
for a list of C-libraries required for installing Imager::Graph
.
How to talk to the view
Inside any Controller we like to prepare some data which is forwarded to the view where the actual drawing occurs. A typical use case inside your Controller might look like this:
sub some_method :Local { my ($self, $c) = @_; # ...get data somehow $c->stash( title => 'Title for your artwork', width => 600, height => 400, data => [42, 30, 33, 32, 38, 44, 39], y_max => 50, y_min => 0, labels => [qw(Mon Tue Wed Thu Fri Sat Sun)], ); $c->forward('View::Graph'); }
Implementing the View
Add a view View/Graph.pm to your Catalyst Application and fill it with the code below. The tick arithmetic admittedly looks a bit awkward but it works well with negative and positive values and draws tick lines at multiples of a power of 10 depending on the minimum and maximum values.
package YourApp::View::Graph; use Moose; use Imager::Graph::Column; use namespace::autoclean; extends 'Catalyst::View'; sub process { my ( $self, $c ) = @_; # Please check this path! my $font = Imager::Font->new(file => '/Library/Fonts/Arial.ttf') or die "Error: $!"; my $graph = Imager::Graph::Column->new(); my $y_max = $c->stash->{y_max} // 20; my $y_min = $c->stash->{y_min} // 0; my $delta = 1; my $ticks = 9999; my $count = 0; while ($ticks > 10 && $count < 100) { $y_max = int(($y_max + (9 * $delta)) / (10 * $delta)) * (10 * $delta); $y_max = 10 if $y_max < 10; $y_min = int(($y_min - (9 * $delta)) / (10 * $delta)) * (10 * $delta); $y_min = 0 if $y_min > 0; $ticks = int(($y_max - $y_min) / (5 * $delta)) + 1; if ($ticks > 10) { $ticks = int(($y_max - $y_min) / (10 * $delta)) + 1 } if ($ticks > 20) { $delta *= 10; } $count++; } $graph->add_data_series($c->stash->{data}); $graph->set_style('fount_lin'); $graph->show_horizontal_gridlines(); $graph->use_automatic_axis(); $graph->set_y_max($y_max); $graph->set_y_min($y_min); $graph->set_y_tics($ticks); $graph->set_image_width($c->stash->{width} // 600); $graph->set_image_height($c->stash->{height} // 400); my $img = $graph->draw( column_padding => 20, labels => $c->stash->{labels}, title => $c->stash->{title} // 'Untitled', font => $font, hgrid => { style => "dashed", color => "#888" }, graph => { outline => { color => "#F00", style => "dotted" }, }, fills => [ qw(60ff60 a0a0ff) ], ) or die $graph->error; my $data; $img->write( data => \$data, type => 'jpeg' ) or die "could not write image: $!"; $c->response->body($data); $c->response->content_type('image/jpeg'); } __PACKAGE__->meta->make_immutable; 1;
The rendering time typically is not an issue. On my machine typical graphs are rendered in less than 100ms.
For More Information
See Imager::Graph for the full details.
Summary
Rendering graphs inside a View is not a very complicated task and can get triggered with just a few values from a Controller.
Author
Wolfgang Kinkeldei <wolfgang [at] kinkeldei [dot] de>