NAME
Catalyst::View::GraphViz - GraphViz View Class
SYNOPSIS
Use the helper to create a View class
script/myapp_create.pl view GraphViz GraphViz
This creates the MyApp::View::GraphViz class.
Build the GraphViz object
#In some method (some View method, since
#that's where the View code should go. See below.)
use GraphViz;
$graph = GraphViz->new();
$graph->add_node("Hello", shape => 'box');
$graph->add_node("world", shape => 'box');
$graph->add_edge("Hello", "world");
$c->stash->{graphviz}->{graph} = $graph;
$c->stash->{graphviz}->{format} = "cmapx"; #HTML image map (default: png)
Forward to the View
#Meanwhile, maybe in a private end action
if(!$c->res->body) {
if($c->stash->{template}) {
$c->forward('MyApp::View::TT');
} elsif($c->stash->{graphviz}->{graph}) {
$c->forward('MyApp::View::GraphViz');
} else {
die("No output method!\n");
}
}
DESCRIPTION
This is the Catalyst view class for GraphViz. Your application subclass should inherit from this class.
This plugin renders the GraphViz object specified in $c->stash->{graphviz}->{graph}
into the $c->stash->{graphviz}->{format}
(one of e.g. png gif, or one of the other as_* methods described in the GraphViz module. PNG is the default format.
The output is stored in $c->response->output
.
The normal way of using this is to render a PNG image for a request and let Catalyst serve it.
Another use of this View is to let it generate the text of a client side imagemap (using a SubRequest) which you then put into the web page currently being rendered. See below for an example.
BUILD THE GRAPHVIZ OBJECT IN A VIEW
The Catalyst::View::GraphViz takes a pre-built GraphViz object to render.
But where should this GraphViz object be constructed? Preferrably in a View class, since the GraphViz graph contains nodes with different colors, shapes, etc.
Consider how the GraphViz View relates to templating systems:
Templating System GraphViz
----------------- --------
Model | Model object(s) Model object(s) (a graph)
Output | Rendered HTML Rendered graph image/imagemap
View | TT/Mason/? View::GraphViz
View code | Custom template file Custom View class
Set "look" | $c->stash->{template} $c->stash->{graphview}->{view}
Set model object | varies $c->stash->{graphview}->{object}
E.g. "look" | update.thtml MyApp::View::OddEvenGraph
So when using TT as a rendering engine, the template contains the instructions for how to display the Model object. You have many templates for displaying the same model object in different ways.
And when using GraphViz as a rendering engine, the View class contains the instructions for how to display the Model object. You have many View classes for displaying the same model object in different ways.
Here's how to create a specific View class for each type of graph.
MyApp::View::OddEvenGraph
As an example, let's create a view to render a graph of numbers, where the odd number nodes are boxes, and the even are ellipses.
Our model object is set like this somewhere (for a quick demo, just put it in the MyApp::default sub):
$c->stash->{graphview}->{object} = {
3 => 2,
4 => 3,
1 => 3,
2 => 1,
};
This can of course be anything that you can interpret as a graph and would like to visualize using GraphViz, not necessarily a single deep data structure like this. It could equally well be an array ref with model objects which together form a graph.
After setting up the model object, we assign the correct View class to render it.
$c->stash->{graphview}->{view} = "MyApp::View::OddEvenGraph";
$c->stash->{graphviz}->{format} = "cmapx"; #Optionally override the format
And, maybe in a private end
action, we forward to the view if it's set, like this:
sub end : Private {
my ( $self, $c ) = @_;
if(!$c->res->body) {
if($c->stash->{template}) {
$c->forward('MyApp::View::TT');
} elsif(my $view = $c->stash->{graphview}->{view}) {
$c->forward($view);
} else {
die("No output method!\n");
}
}
}
That's what your application looks like. Now we need to create the actual View class MyApp::View::OddEvenGraph. Use the helper GraphView:
script/myapp_create.pl view OddEvenGraph GraphView
In the class MyApp::View::OddEvenGraph you can modify the process sub to suit your needs.
sub process {
my ($self, $c) = @_;
my $graph = $c->stash->{graphview}->{object} or
die('No object specified in $c->stash->{graphview}->{object} for rendering');
my $graphviz = GraphViz->new(node => {
name => "oddeven",
});
$graphviz->add_node($_, shape => ($_ % 2) ? "box" : "ellipse") for(keys %$graph);
while(my ($from, $to) = each %$graph) {
$graphviz->add_edge($from, $to);
}
$c->stash->{graphviz}->{graph} = $graphviz;
$c->forward('MyApp::View::GraphViz');
return 1;
}
As you can see, the purpose of this method is to transform the model graph object into a GraphViz object, which is then forwarded to the GraphViz View.
MAKE A SUBREQUEST TO GENERATE AN IMAGEMAP
Together with the ability to render a GraphViz image soon comes the need to generate a client-side imagemap which can be inserted in the web page showing the image.
You need a) an action that renders the GraphViz object in the cmapx format, and b) to call that action from within another action so you can assign the resulting HTML text to your stash and then put it in the template.
Render as an Imagemap
If your ordinary png action looks like this:
sub png : Local {
my ( $self, $c ) = @_;
$c->stash->{graphview}->{view} = "MyApp::View::OddEvenGraph";
$c->stash->{graphview}->{object} = ...;
}
then your imap action should look like this:
sub imap : Local {
my ( $self, $c ) = @_;
$c->stash->{graphview}->{view} = "MyApp::View::OddEvenGraph";
$c->stash->{graphview}->{object} = ...;
$c->stash->{graphviz}->{format} = "cmapx";
}
Call the Imagemap Action
First make sure you have the SubRequest plugin loaded:
use Catalyst qw/SubRequest/;
This is how to perform the SubRequest. Let's assume these actions are in the Controller "Graph":
$c->stash->{html_imagemap} = $c->subreq("/graph/imap");
#Reset after subreq (until this bug is fixed: http://rt.cpan.org/NoAuth/Bug.html?id=15790 )
$c->response->content_type("text/html");
Now you can simply output the imagemap text in the template
[% html_imagemap %]
<img name="graph" src="/graph/png" USEMAP="#oddeven" border=0>
Note 1: The name "oddeven" is the same as the one set in the
GraphViz->new(name => "oddeven");
Note 1a: Unfortunately this isn't quite true. The name of the GraphViz image is always hardcoded to "test". Bug that needs to be fixed: http://rt.cpan.org/NoAuth/Bug.html?id=14882
Note 2: The nodes will not be clickable unless they have a URL property, so you need to specify that for each
$graphviz->add_node(URL => "/graph/node/select?name=$name");
in the View class. Actually, there is a clever shortcut in GraphViz for this, so instead of specifying it for each node, you can set a default when calling
my $graphviz = GraphViz->new(node => { URL => '/graph/node/select?name=\N' });
The \N is a placeholder for the name of each node.
METHODS
new($c)
The constructor for the GraphViz view. Sets up the template provider, and reads the application config.
process($c)
Render the GraphViz object specified in $c->stash->{graphviz}
.
Output is stored in $c->response->output
.
SEE ALSO
CHANGES
0.05
Docs
0.01 - 0.04
Makefile and Windows/Unix stuff
AUTHOR
Johan Lindstrom, johanl@cpan.org
CREDITS
Largely based on the TT view.
Obviosly uses Acme's GraphViz module, which in turn uses the brilliant GraphViz package (http://www.graphviz.org/).
Quick link to a useful, but obscure, doc page: http://www.graphviz.org/pub/scm/graphviz2/doc/info/shapes.html
COPYRIGHT
This program is free software, you can redistribute it and/or modify it under the same terms as Perl itself.