| SVG-Template-Graph documentation | Contained in the SVG-Template-Graph distribution. |
SVG::Template::Graph - Perl extension for generating template-driven graphs with SVG
use SVG::Template::Graph;
$data = [
{
barGraph=>1,#<barGraph|[lineGraph]>
barSpace=>20,
'title'=> '1: Trace 1',
'data' => #hash ref containing x-val and y-val array refs
{
'x_val' =>
[50,100,150,200,250,
300,350,400,450,500,550],
'y_val' =>
[100,150,100,126,100,
175,100,150,120,125,100],
},
'format' =>
{ #note that these values could change for *each* trace
'lineGraph' => 1,
'x_min' => 0,
'x_max' => 600,
'y_min' => 50,
'y_max' => 200,
'x_axis' => 1, #draw x-axis
'y_axis' => 1, #draw y-axis
#define the labels that provide the data context.
'labels' =>
{
#for year labels, we have to center the axis markers
'x_ticks' =>
{
'label' =>[2002,2003,2004],
'position'=>[100,300,500],
},
'y_ticks' =>
{
#tick mark labels
'label' => [ -250, 0, 250, 500],
#tick mark location in the data space
'position' => [50, 100, 150, 200],
},
},
},
},
];
#construct a new SVG::Template::Graph object with a file handle
my $tt = SVG::Template::Graph->new($file);
#set up the titles for the graph
$tt->setGraphTitle(['Hello svg graphing world','I am a subtitle']);
#generate the traces.
$tt->drawTraces($data,$anchor_rectangle_id);
#serialize and print
print $tt->burn();
Template::Graph:SVG is a module for the generation of template-driven graphs using Scalable Vector Graphics (SVG). Using this module, it is possible to define a template SVG document with containers which are populated with correctly scaled plot images.
None by default.
Refer to the examples directory inside this distribution for working examples.
#construct a new SVG::Template::Graph object with a file handle my $tt = SVG::Template::Graph->new($file);
The constructor for the class. Takes a template file name as an argument
serialise the image. See SVG::xmlify for more details
my $svg_element_ref = $tt->setGraphTitle ($string|\@strings, %attributes)
Generate the text for the Graph Title Returns the reference to the text element
set the title of a trace
$tt->setTraceTitle( $string|\@strings, %attributes )
Generate the text for the Graph X-Axis Titles Returns the reference to the text element
$tt->setXAxisTitle($axis_number, $string, %attributes) $tt->setXAxisTitle($axis_number, \@strings, %attributes)
Generate the text for the Graph Y-Axis Titles Returns the reference to the text element
$tt->setYAxisTitle($axis_number, $string,%attributes) $tt->setYAxisTitle($axis_number, \@strings,%attributes)
Check that a group exists (is a valid, defined group in the SVG DOM) and create a group in the document if id does not exist. Return the group element This ensures that even if a designer failed to generate a group ID in the drawing, you will get a drawing with the right group names. If type is defined, an element of $type with %attributes is defined.
my $svg_element = $tt->_gg ($id) my $svg_element = $tt->_gg ($id,'rect',width=>10,height=>10, x=>10,y=>10,fill=>'none',stroke=>'red')
Internal method called by setGraphTitle and setAxisTitle to do the actual work. Not really intended to be accessed from the outside but available for advanced users.
$tt->_setAxisText ($id,$text|\@text,%attributes)
the input structure required by sub drawTraces to generate the graph. Refer to the examples included in this distribution in the examples directory for working samples.
$struct = [{
'tracetype' => 'linegraph',
'title'=> '1: Trace 1',
'data' => #hash ref containing x-val and y-val array refs
{
'x_val' =>
[0, 2, 4,
6, 8, 10,
12,14,16,
18,20],
'y_val' =>
[4, 2, 5,
3, 7, 4 ,
9, 9, 2,
4, 3],
},
'format' =>
{
'x_max' => 600, #or for your case, the date value of the 1st point
'x_min' => 0, #or for your case, the date value of the last point
'y_max' => 0.35,
'y_min' => -0.1,
'x_title' => 'Calendar Year',
'y_title' => '% Annual Performance',
'x_axis' => 0, # do not automatically draw an x-axis
'y_axis' => 1, #automatically draw a y-axis
#define the labels that provide
#the data context.
'labels' =>
{
#for year labels, we have to center the axis markers
'x_ticks' =>
{
'label' =>[2002,2003,2004],
'position' =>[100,300,500],
},
y_ticks =>
{
#tick mark label
'label' => [ '-10.00%', '-5.00%', '0.00%',
'5.00%', '10.00%', '15.00%',
'20.00%', '25.00%', '30.00%',
'35.00%' ],
#tick mark location in the data space
'position' => [-0.10,-0.5,0,
-.5,.10,.15,
.20,.25,.30,
.35],
},
},
},
legend_title => 'Some Interesting Data',
},
];
if $id_anchor_data is an array reference, then it uses it to describe the extents of the viewbox into which the current drawing will happen. If $id_anchor_data is a string then its associated xml element is assumed to be a rectangle and getCanvasBoxBoundaries uses the rectangle geometry.to define the plot bounding box. hash references are not supported.
Action: set the boundary box data in the object and returns the array reference:
[xmin, ymin, xmax, ymax]
returns the value of the boundary box of the data set which places the graph in the image as an array reference:
[xmin, ymin, xmax, ymax]
draw one or both of axes (zero-value line) of the drawing data space. Draws both axes unless one of the axes is passed as a string.
drawAxis('somegroupid','x') draws the x-axis line into group 'somegroupid'.
drawAxis('somegroupid','y') draws the y-axis line indo group 'somegroupid'
drawAxis('somegroupid') draws both x- and y- axis lines into group 'somegroupid'
construction detail: draws the content into a group.
given a structure describing the incoming drawing parameters, generates the SVG lines, axes, and ticks and returns the number of traces that were handled. If $anchor_id is defined and is a rectangle ID, then the drawing will take place in id.if $anchor_point is defined and it is an array of 4 real numbers, then this will be taken to be the location where the insertion box goes.
The format for the array is: [x0 y0 x1 y1]. in canvas dimension
scales the points for lines appropriately and generates the correct polyline or path or polygon element, scaled and inverted to match paper space.
if $anchor_data is defined, then it is either the id of a rectangle whose geometry will contain the results, or it is an array reference which contains the viewbox defilition [x0,y0,x1,y1] where the graph is to be placed.
this is the method in which the generation of the graph is handled
returns the reference of the polyline/path/polygon tag that was generated.
draw a line graph trace
$g->barGraph( $index, [\@x,\@y], \@canvas, $barSpace, %styling_attributes)
draw a bar graph trace
$p->drawGridLines ($target_svg_element_ref,$transformation_ref,$format_structure_ref)
draw the gridlines for a graph as defined in the formatting data structure for each trace.
$p->handleFurnishings( $orientation, $format, \%anchor_refs);
single point for handling grid lines, gridline lables, and gridline tickmarks this method is a factory method for generating vertical or horizontal furnishings for the trace the anchor hash reference contains the following keys:
line label tick
parameters
gridline orientation
$orientation = 'x' or 'y'
Gridline context-format hash reference
$format - hash reference
defines what is shown and what is not.
whose values are svg element object references where the respective entities are to be appended as children.
draw a single grid line
$p->drawTick( ['x'|'y'], $index, $tick, $args, %attrs);
tickmark-generation handler
grid lable generator
return the front and back extensions to lines based on the definition (or lack of) tickmarks in the label construct
Example of a label definition:
$label = {
'y_ticks' => {
'style' => {
'right' => '10'
},
'position' => [
'150',
'100',
'0',
'-75'
],
'label' => [
'Much',
'Some',
'None',
'Lost'
]
},
'x_ticks' => {}
};
$self->D()
returns the SVG Document object
$self->T($name)
returns the currently invoked transformation object. Returns transformation object $name if requested by name
define the graph target (currently only rectangles are accepted) on top of which the data will be drawn
returns the current graph target
generates a reference to an array of $count+1 evenly distributed values ranging between $min and $max
$tt->autoGrid(0,100,10);
format an array of values according to formatting rules
$tt->Format \@array,$format,$format_attribute[,@more_format_attributes] $format can be 'time' or 'printf'
for 'time', uses the Time::localtime
example 1: formatting to print the verbose date
$a = [ '0', '2.75', '5.5', '8.25', '11' ]; my $b = $tt->Format($a,'time',"%a %b %e %H:%M:%S %Y");
returns
$b = [ 'Thu Jan 1 01:00:00 1970', 'Thu Jan 1 01:00:02 1970', 'Thu Jan 1 01:00:05 1970', 'Thu Jan 1 01:00:08 1970', 'Thu Jan 1 01:00:11 1970' ];
Format uses POSIX function b<strftime> Refer to POSIX for more information on time formating.
example 2: formatting to print to three decimal places using sprintf
$tt->Format([1.123,2.1234,3.12345,4.123456],'sprintf','%.3f');
example 3: formatting to print a percent sign
$tt->Format([1.123,2.1234,3.12345,4.123456],'sprintf','%%');
assign the definitions between the internal keys and the ids in the template at hand. This method need not be called as all IDs automatically get run through if the default IDs specified below are used.
To draw a graph, a template is required which contains two key datasets: a rectangle which will contain the inserted graph data and a group containing child group elements with the IDs expected by SVG::Template::Graph
The svg snippet below provides the required groups for the generation of the first trace (trace intex 0)
Because SVG uses the Painter's model, the image rendering order follows the XML document order. For the snippet below, the rendering order is the following:
data,grid,ticks, axes:x,y, axes values: x,y, axes text: x,y, axes titles: x,y
Trace generation snippet
<!-- trace insertion takes place here -->
<g id="group.trace.1" fill="none"
stroke="black" stroke-width="2">
<!-- draw the data below the gridlines -->
<g id="group.trace.data.1"
stroke="#333333" stroke-width="0.5"
fill="#411DA4"/>
<g id="group.trace.grid.1"
stroke="gray" stroke-width="1"/>
<g id="group.trace.tick.1"
stroke="black" stroke-width="1.5"/>
<g id="group.trace.axes.1"
stroke="none" fill="black">
<g id="group.trace.axes.x.1" stroke="gray"
fill="none" stroke-width="1"/>
<g id="group.trace.axes.y.1" stroke="gray"
fill="none" stroke-width="1"/>
<g id="group.trace.axes.values.x.1" text-anchor="middle"
stroke="none" fill="black" transform="translate(0,265)"/>
<g id="group.trace.axes.values.y.1" stroke="none"
fill="black" text-anchor="start" transform="translate(-40,0)"/>
<g id="group.trace.axes.title.x.1" text-anchor="middle"
transform="translate(365,370)" font-size="12"
fill="#411DA4" font-weight="Bold"/>
<g id="group.trace.axes.title.y.1.c" text-anchor="end"
transform="translate(40,200)">
<g id="group.trace.axes.title.y.1"
transform="rotate(-90)" font-weight="Bold"
font-size="12" fill="#411DA4"/>
</g>
</g>
</g>
<!-- end of trace insertion 1 -->
In order to show the trace in front of the gridlines, the above snippet changes to:
<!-- trace insertion takes place here -->
<g id="group.trace.1" fill="none"
stroke="black" stroke-width="2">
<g id="group.trace.grid.1"
stroke="gray" stroke-width="1"/>
<g id="group.trace.tick.1"
stroke="black" stroke-width="1.5"/>
<g id="group.trace.axes.1"
stroke="none" fill="black">
<g id="group.trace.axes.x.1" stroke="gray"
fill="none" stroke-width="1"/>
<g id="group.trace.axes.y.1" stroke="gray"
fill="none" stroke-width="1"/>
<g id="group.trace.axes.values.x.1" text-anchor="middle"
stroke="none" fill="black" transform="translate(0,265)"/>
<g id="group.trace.axes.values.y.1" stroke="none"
fill="black" text-anchor="start" transform="translate(-40,0)"/>
<g id="group.trace.axes.title.x.1" text-anchor="middle"
transform="translate(365,370)" font-size="12"
fill="#411DA4" font-weight="Bold"/>
<g id="group.trace.axes.title.y.1.c" text-anchor="end"
transform="translate(40,200)">
<g id="group.trace.axes.title.y.1"
transform="rotate(-90)" font-weight="Bold"
font-size="12" fill="#411DA4"/>
</g>
</g>
<!-- draw the data on top of the gridlines -->
<g id="group.trace.data.1" stroke="#333333" stroke-width="0.5" fill="#411DA4"/>
</g>
<!-- end of trace insertion 1 -->
Refer to the examples directory inside this distribution for working examples.
SVG::Parser SVG::Manual Expat SAX SVG::TT:Graph Tramsform::Canvas http://www.roitsystems.com http://www.roitsystems.com
Ronan Oger, <ronan.oger@roitsystems.com<gt> http://www.roitsystems.com http://www.roitsystems.com
This library was developed and written by Ronan Oger, ROIT Systems Gmbh, under contract to Digital Craftsmen.
Copyright (C) 2004 by Ronan Oger, ROIT Systems GmbH, Zurich, Switzerland
Copyright (C) 2004 by Digital Craftsmen Ltd, London, UK
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.3 or, at your option, any later version of Perl 5 you may have available.
| SVG-Template-Graph documentation | Contained in the SVG-Template-Graph distribution. |
package SVG::Template::Graph; use 5.006; use strict; use warnings; use Carp; use SVG::Parser; use Exporter; use Transform::Canvas; use POSIX qw(strftime); our $VERSION = '1.0'; use vars qw($VERSION @ISA ); #$AUTOLOAD); our @ISA = qw(SVG::Parser Exporter );
# Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use SVG::Template::Graph ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. #our %EXPORT_TAGS = ( 'all' => [ qw() ] ); #our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); #our @EXPORT = qw( #); $VERSION = eval $VERSION; # see L<perlmodstyle>
sub new ($;$;@) { my ( $proto, $file, %attrs ) = @_; my $class = ref $proto || $proto; my $self; my %default_attributes = %SUPER::default_attributes; $self->{_config_} = {}; $self->{_svgTree_} = {}; $self->{_IDCACHE_} = {}; $self->{graphTarget} = undef; # establish defaults for unspecified attributes $self = $class->SUPER::new(); $self->{_svgTree_} = $self->parse_uri($file); $self->{_IDMap_} = {}; return $self; }
sub burn ($;@) { my ( $self, %attrs ) = @_; return $self->D()->xmlify(%attrs); }
sub setGraphTitle ($$;@) { my $self = shift; my $text = shift; return $self->_setAxisText($self->mapTemplateId("group.graph.title"), $text, @_ ); }
sub setTraceTitle ($$$;@) { my $self = shift; my $ti = shift; my $text = shift; return $self->_setAxisText($self->mapTemplateId("group.trace.title.$ti"), $text, @_ ); }
sub setXAxisTitle ($$$;@) { my $self = shift; #the trace index my $ti = shift; my $text = shift; return $self->_setAxisText($self->mapTemplateId("group.trace.axes.title.x.$ti"), $text, @_ ); }
sub setYAxisTitle ($$$;@) { my $self = shift; #the trace index my $ti = shift; my $text = shift; return $self->_setAxisText($self->mapTemplateId("group.trace.axes.title.y.$ti"), $text, @_ ); }
sub _gg ($$;@) { my $self = shift; my $id = shift || 'abc'; my $type = shift || 'group'; my %attrs = @_; return $self->{_IDCACHE_}->{$id} if $self->{_IDCACHE_}->{$id}; my $d = $self->D() || confess("E1000: Supplied SVG input not properly parsed!!"); my $tg = $d->getElementByID($id); unless ($tg) { carp("element $type with id '$id' not found in document when setting a text field."); $d->getRootElement()->comment("missing group $id added by pid $$ $0"); $tg = $d->getRootElement()->element( $type, id => $id, %attrs ); # carp("element $type with id '$id' created under root element "); } $self->{_IDCACHE_}->{$id} = $tg; #print STDERR "Saving $tg to IDCACHE $id\n"; return $tg; }
sub _setAxisText ($$$;@) { my $self = shift; my $id = shift; my $text = shift; my %attrs; %attrs = @_; #check if we have an array of strings $text = [$text] unless ( ref($text) eq 'ARRAY' ); carp( "No text was supplied. Either supply a string or an array reference containing strings" ) unless scalar @$text; my $tg = $self->_gg($id); my $to = $tg->text(%attrs) || carp("Failed to generate an Axis text element within group '$id'."); my %args = ( x => 0 ); foreach my $line (@$text) { $to->tspan(%args)->cdata($line) || carp( "Failed to generate an Axis tspan element within group '$id'."); #set the dy spacing for multi-line text $args{dy} = '1em'; } return $to; }
sub getCanvasBoxBoundaries ($) { my $self = shift; my $id = $self->getGraphTarget; unless ( defined $self->{_config_}->{xmin_p} ) { if ( ref($id) eq 'ARRAY' ) { ( $self->{_config_}->{xmin_p}, $self->{_config_}->{ymin_p}, $self->{_config_}->{xmax_p}, $self->{_config_}->{ymax_p} ) = @$id; } elsif ( ref($id) eq 'HASH' ) { croak('getCanvasBoxBoundaries does not yet support hashes'); } else { my $r = $self->D()->getElementByID($id) || croak("Could not find rectangle with id='$id'"); #define the configuration data for the paperspace. $self->{_config_}->{xmin_p} = $r->getAttribute('x'); $self->{_config_}->{xmax_p} = $self->{_config_}->{xmin_p} + $r->getAttribute('width'); $self->{_config_}->{ymin_p} = $r->getAttribute('y'); $self->{_config_}->{ymax_p} = $self->{_config_}->{ymin_p} + $r->getAttribute('height'); } } #return the reference to the array as expected by Transform::Canvas return [ $self->{_config_}->{xmin_p}, $self->{_config_}->{ymin_p}, $self->{_config_}->{xmax_p}, $self->{_config_}->{ymax_p}, ]; }
sub getDataBoxBoundaries ($$) { my $self = shift; my $p = shift; return [ $p->{format}->{x_min}, $p->{format}->{y_min}, $p->{format}->{x_max}, $p->{format}->{y_max}, ]; }
sub drawAxis ($$$) { my $self = shift; my $id = shift; #anchor id my $o = shift || ''; #orientation my $g; if ( !$o || ( $o eq 'x' || $o eq 'y' ) ) { $g = $self->_gg($id); #draw the x-axis if it appears in the canvas window $g->line( id => "x axis zero line", x1 => $self->T->mapX(0), y1 => $self->T->mapY( $self->T->dy0 ), x2 => $self->T->mapX(0), y2 => $self->T->mapY( $self->T->dy1 ), ) if $o eq 'x'; $g->line( id => "y axis zero line", x1 => $self->T->mapX( $self->T->dx0 ), y1 => $self->T->mapY(0), x2 => $self->T->mapX( $self->T->dx1 ), y2 => $self->T->mapY(0), ) if $o eq 'y'; return $g; } return undef; }
sub drawTraces ($$) { my $self = shift; my $struct = shift; my $insert_anchor_id = shift; unless ($insert_anchor_id) { $self->setGraphTarget; $insert_anchor_id = $self->getGraphTarget; } #make sure we got an array ref for $struct croak( "drawTrace error: expected an array at drawTraces but got something called '" . ref($struct) . "' with content $struct." ) unless ref($struct) eq 'ARRAY'; #take the hash struct for each trace and run it through the grinder. #past this point, the code goes to hell as of 2004.09.21. #but this is the way to go... my $ti = 0; foreach my $trace (@$struct) { $ti++; $self->getTracePointMap( $ti, 'path', $trace, $insert_anchor_id ) || croak("Failed to build trace $ti"); } return $ti; }
sub getTracePointMap ($$$$;@) { my $self = shift; my $ti = shift; my $type = shift; my $p = shift; my $insert_anchor_id = shift; my %args = @_; my $canvas = $self->getCanvasBoxBoundaries; #assign a default line drawing type $p->{lineGraph} = 1 unless $p->{barGraph}; $self->{map} = Transform::Canvas->new( #the canvas extents limits canvas => $canvas, #the data scape mapping to the above canvas geometry data => $self->getDataBoxBoundaries($p), ); confess("Failed to create Canvas Transform object") unless ref( $self->{map} ) eq 'Transform::Canvas'; my @pr = $self->{map}->map( $p->{data}->{x_val}, $p->{data}->{y_val} ); #draw the gridlines $self->drawGridLines( $ti, $p->{format} ) || warn("Error building the gridlines"); $self->lineGraph( $ti, $type, \@pr, $canvas, %args ) if $p->{lineGraph}; delete $args{closed} if $args{closed}; if ( $p->{barGraph} ) { $p->{data}->{x_val} = ref $p->{data}->{x_val} ? $p->{data}->{x_val} : [ $p->{data}->{x_val} ]; $p->{data}->{y_val} = ref $p->{data}->{y_val} ? $p->{data}->{y_val} : [ $p->{data}->{y_val} ]; $self->barGraph( $ti, [ $p->{data}->{x_val}, $p->{data}->{y_val} ], $p->{barSpace}, %args ); } $self->drawAxis($self->mapTemplateId("group.trace.axes.x.$ti"), 'x' ) if $p->{format}->{x_axis}; $self->drawAxis($self->mapTemplateId("group.trace.axes.y.$ti"), 'y' ) if $p->{format}->{y_axis}; #set the trace titles if they are specified $self->setXAxisTitle( $ti, $p->{format}->{x_title} ) if defined $p->{format}->{x_title}; $self->setYAxisTitle( $ti, $p->{format}->{y_title} ) if defined $p->{format}->{y_title}; $self->setTraceTitle( $ti, $p->{title} ) if defined $p->{title}; return 1; }
#draw the line graph sub lineGraph ($$$$@) { my $self = shift; my $ti = shift; my $type = shift; my $points = shift; my $canvas = shift; my %args = @_; $type = 'path' unless ( $type eq 'polyline' || $type eq 'polygon' || $type eq 'scatter' ); my %closed = (); %closed = ( '-closed' => 'true' ) if ( lc( $args{closed} ) eq 'true' ); delete $args{closed} if $args{closed}; #draw the trace in the container my $c_points = $self->D()->get_path( x => $points->[0], y => $points->[1], -type => $type, %closed, ); #invoke the transformation from data space to canvas space my ( $min_x, $min_y, $max_x, $max_y ) = @$canvas; my $id_string =$self->mapTemplateId("group.trace.data.$ti"); my $traceBase = $self->_gg($id_string) || confess( "Failed to find required element id '$id_string'" ); return $traceBase->$type( %$c_points, id => "trace.$ti", %args ); } #draw the bar graph
sub barGraph ($$$$@) { my $self = shift; my $ti = shift; #the raw points. my $points = shift; my $barSpace = shift; my %args = @_; my $out = $self->getCanvasBoxBoundaries; my ( $min_x, $min_y, $max_x, $max_y ) = @$out; my $Points = scalar @{ $points->[0] }; my $traceBase = $self->_gg("group.trace.data.$ti") || confess("Failed to find required element id group.trace.data.$ti"); #we do a simple width linerization which falls down #when the bars do not span the image. $barSpace = $barSpace ? $barSpace : $Points + 1; my $width = ( $max_x - $min_x ) / $barSpace; #draw a rectangle for each point, with the #rectangle centered on the point at the X midpoint #of the rectangle. # #watch out for paper space inversion tricks. #min_y is the bottom of the drawing (max_canvas_value) #we are working in paper space foreach my $index ( 0 .. $Points - 1 ) { my $x = $self->T->mapX( $points->[0]->[$index] ) - 0.5 * $width; #my $y = $self->T->mapY($points->[1]->[$index]); my $y = $self->T->mapY(0); my $height = $self->T->mapY( $points->[1]->[$index] ) - $self->T->mapY(0); #handle negative rectangle height if ( $height < 0 ) { $height = -$height; $y = $y - $height; } $traceBase->rect( x => $x, y => $y, width => $width, height => $height, %args ); #this is the correct top position #zeros position. do not mess with this!! } }
sub drawGridLines ($$$) { my $self = shift; my $ti = shift; my $f = shift; #formatting data structure ($in->{format}) my $gid = undef; $gid =$self->mapTemplateId("group.trace.axes.x.$ti"); my $g_x = $self->_gg($gid) || confess("Failed to find required element ID '$gid'"); $gid =$self->mapTemplateId("group.trace.axes.y.$ti"); my $g_y = $self->_gg($gid) || confess("Failed to find required element ID '$gid'"); $gid =$self->mapTemplateId("group.trace.axes.values.x.$ti"); my $t_x = $self->_gg($gid) || confess("Failed to find required element ID '$gid'"); $gid =$self->mapTemplateId("group.trace.axes.values.y.$ti"); my $t_y = $self->_gg($gid) || confess("Failed to find required element ID '$gid'"); $gid =$self->mapTemplateId("group.trace.tick.$ti"); my $tk_y = $self->_gg($gid) || confess("Failed to find required element ID '$gid'"); my $tk_x = $self->_gg($gid) || confess("Failed to find required element ID '$gid'"); croak("Format not correctly passed to drawGrid: not a hash reference") unless ref($f) eq 'HASH'; # #handle labels if we have a gridlines label if ( defined $f->{labels} ) { #Grid positions $self->{grid}->{y_p} = $f->{labels}->{y_ticks}->{position} || []; $self->{grid}->{x_p} = $f->{labels}->{x_ticks}->{position} || []; #grid label units $self->{grid}->{x_u} = $f->{labels}->{x_ticks}->{unit} || ''; $self->{grid}->{y_u} = $f->{labels}->{y_ticks}->{unit} || ''; #grid axes labels $self->{grid}->{y_l} = $f->{labels}->{y_ticks}->{label} || []; $self->{grid}->{x_l} = $f->{labels}->{x_ticks}->{label} || []; #grid count $self->{grid}->{y_c} = scalar @{ $self->{grid}->{y_p} } || 0; $self->{grid}->{x_c} = scalar @{ $self->{grid}->{x_p} } || 0; $self->handleFurnishings( 'x', $f, { line => $g_x, label => $t_x, tick => $tk_x } ); #cut line here $self->handleFurnishings( 'y', $f, { line => $g_y, label => $t_y, tick => $tk_y } ); #cut line here } return 1; }
sub handleFurnishings ($$$$$) { my $self = shift; my $o = shift; my $f = shift; my $anchor = shift; croak "Orientation should be 'x' or 'y'" unless ( $o eq 'x' or $o eq 'y' ); croak "gridPosition entry '" . $self->{grid}->{"${o}_p"} . "' is not an array reference" unless ref $self->{grid}->{"${o}_p"} eq 'ARRAY'; croak "anchor entry is not a hash reference" unless ref $anchor eq 'HASH'; croak "formatting hash reference is not a hash reference" unless ref $f eq 'HASH'; croak "gridCount '" . $self->{grid}->{"${o}_c"} . "' should be an integer" if $self->{grid}->{"${o}_c"} =~ /\D+/; #handle each grid position one at a time my $i = 0; my $tick = $self->getTick( $f->{labels}, $o ); foreach $i ( 0 .. $self->{grid}->{"${o}_c"} - 1 ) { #we only draw if the position data is defined last unless defined $self->{grid}->{"${o}_p"}->[$i]; #y-value of the constant-y grid line $anchor->{line}->comment("Gridlines $i for $o"); $self->drawGridLine( $o, $i, { anchor => $anchor->{line}, }, ); $anchor->{label}->comment("Labels $i for $o"); $self->drawGridLabel( $o, $i, { anchor => $anchor->{label}, }, ); $anchor->{tick}->comment("Tick marks $i for $o"); $self->drawTick( $o, $i, $tick, { anchor => $anchor->{tick}, }, ); } return $i; }
sub drawGridLine ($$$$) { my $self = shift; my $o = shift || 'x'; my $i = shift; my $args = shift; #set the data values into the object for future use # $self->{grid}->{line}->{dx} # $self->{grid}->{line}->{dy} $self->{grid}->{line}->{"d$o"} = defined $self->{grid}->{"${o}_p"} ? $self->{grid}->{"${o}_p"}->[$i] : ( $self->T->dy1 - $self->T->dy0 ) * $i / $self->{grid}->{"${o}_c"}; #convert to canvas values and define: # $self->{grid}->{line}->{cx} # $self->{grid}->{line}->{cy} $self->{grid}->{line}->{"c$o"} = $o eq 'y' ? $self->T->mapY( $self->{grid}->{line}->{"d$o"} ) : $self->T->mapX( $self->{grid}->{line}->{"d$o"} ); $args->{anchor}->line( y1 => $o eq 'y' ? $self->{grid}->{line}->{cy} : $self->T->cy0, x1 => $o eq 'y' ? $self->T->cx0 : $self->{grid}->{line}->{cx}, y2 => $o eq 'y' ? $self->{grid}->{line}->{cy} : $self->T->cy1, x2 => $o eq 'y' ? $self->T->cx1 : $self->{grid}->{line}->{cx}, ); }
sub drawTick ($$$$$;@) { my $self = shift; my $o = shift || 'x'; my $i = shift; my $tick = shift; my $args = shift; my %attrs = @_; #do nothing unless the tickmarks are required, return undef unless defined $tick; #$self->{grid}->{line}->{"d$o"} = defined $args->{GridPosition} ? # $self->{GridPosition}->[$i] : # ($t->dy1 - $t->dy0)*$i/$args->{GridCount}; #convert to canvas values #$self->{grid}->{line}->{"c$o"} = $o eq 'y' ? # $t->mapY($self->{grid}->{line}->{"d$o"} : # $t->mapX($x_line); #handle the front and back ticks my %hash; if ( $tick->[0] ) { my ( $x, $y ) = ( $self->T->cx0, $self->T->cy0 ); %hash = ( y1 => $o eq 'y' ? $self->{grid}->{line}->{cy} : $y, y2 => $o eq 'y' ? $self->{grid}->{line}->{cy} : $y - $tick->[0], x1 => $o eq 'y' ? $x : $self->{grid}->{line}->{cx}, x2 => $o eq 'y' ? $x - $tick->[0] : $self->{grid}->{line}->{cx}, ); $args->{anchor}->line( %hash, %attrs ); } if ( $tick->[1] ) { my ( $x, $y ) = ( $self->T->cx1, $self->T->cy1 ); %hash = ( y1 => $o eq 'y' ? $self->{grid}->{line}->{cy} : $y, y2 => $o eq 'y' ? $self->{grid}->{line}->{cy} : $y + $tick->[1], x1 => $o eq 'y' ? $x : $self->{grid}->{line}->{cx}, x2 => $o eq 'y' ? $x + $tick->[1] : $self->{grid}->{line}->{cx}, ); $args->{anchor}->line( %hash, %attrs ); } }
sub drawGridLabel ($$$$) { my $self = shift; my $o = shift; my $i = shift; my $args = shift; my $c = $self->{grid}->{line}->{"c$o"}; my $d = $self->{grid}->{line}->{"d$o"}; my $string = defined $args->{grid}->{"${o}_u"} ? $self->{grid}->{"${o}_l"}->[$i] . " " . $args->{grid}->{"${o}_u"} : $self->{grid}->{"${o}_l"}->[$i]; #decide what to print as a gridline label my $thistext = defined $self->{grid}->{"${o}_l"} ? #if labels are defined $string : $self->{grid}->{line}->{"d$o"}; #use data positions instead $thistext =~ s/\s$//; return $o eq 'y' ? $args->{anchor} ->text( y => $self->{grid}->{line}->{cy}, x => $self->T->cx0, ) ->cdata($thistext) : $args->{anchor} ->text( x => $self->{grid}->{line}->{cx}, y => $self->T->cy0, ) ->cdata($thistext); # $args->{anchor}->line( # y1=> $o eq 'y' ? $self->{grid}->{line}->{cy} : $t->cy0, # x1=> $o eq 'y' ? $t->cx0 : $self->{grid}->{line}->{cx}, # y2=> $o eq 'y' ? $self->{grid}->{line}->{cy} : $t->cy1, # x2=> $o eq 'y' ? $t->cx1 : $self->{grid}->{line}->{y}, # ); }
sub getTick($$$) { my $self = shift; my $l = shift; my $o = shift; $o = lc($o); $o = 'x' unless $o eq 'y'; my $extender = [ 0, 0 ]; return undef unless defined $l->{"${o}_ticks"}->{style}; my %map = ( x => [ 'top', 'bottom' ], y => [ 'left', 'right' ] ); my $one = $map{$o}->[0]; my $two = $map{$o}->[1]; #handle constant-x (vertical) gridlines $extender->[0] = defined $l->{"${o}_ticks"}->{style}->{$one} ? $l->{"${o}_ticks"}->{style}->{$one} : 0; $extender->[1] = defined $l->{"${o}_ticks"}->{style}->{$two} ? $l->{"${o}_ticks"}->{style}->{$two} : 0; return $extender; }
sub D ($) { my $self = shift; return $self->{_svgTree_}; }
sub T ($;$) { my $self = shift; my $name = shift; return $self->{maps}->{$name} if defined $name; return $self->{map}; }
sub setGraphTarget ($$;@) { my $self = shift; $self->{graphTarget} = shift || $self->mapTemplateId('rectangle.graph.data.space'); my $type = shift || 'rect'; return $self->_gg( $self->{graphTarget}, $type, @_ ); }
sub getGraphTarget ($) { my $self = shift; return $self->{graphTarget}; }
sub autoGrid ($$$) { my $self = shift; my ( $min, $max, $count ) = @_; my @array = ( 0 .. $count ); map { $_ = ( $max - $min ) / ($count) * $_ } @array; #print STDERR Dumper \@array; return \@array; }
sub Format ($$$;@) { my $self = shift; my $array = shift; my $format = shift || 'sprintf'; my $fmt = shift || '%.3f'; my @attrs = @_; if ( $format eq 'time' ) { map { $_ = strftime( $fmt, _getLocalTime($_) ) } @$array; } elsif ( $format eq 'printf' ) { map { $_ = printf( $fmt, $_ ) } @$array; } elsif ( $format eq 'sprintf' ) { map { $_ = sprintf( $fmt, $_ ) } @$array; } else { print STDERR "processing default\n"; } return $array; } sub _getLocalTime ($) { my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime(shift @_); return ( $sec, $min, $hour, $mday, $mon, $year , $wday, $yday, $isdst ); }
sub mapTemplateId ($$) { my $self = shift; my $myid = shift; unless (defined $myid) { carp "undefined xml ID! Setting to 'unknown'" . join (":",caller()) ; $myid = 'unknown'; $self->{_IDMap_}->{$myid} = $myid; } unless ($self->{_IDMap_}->{$myid}) { carp "Undeclared xml ID $myid! Adding to ID list"; $self->{_IDMap_}->{$myid} = $myid; } # getIdMap unless $self->{_IDMap_}; print STDERR "$myid = $self->{_IDMap_}->{$myid}\n"; return $self->{_IDMap_}->{$myid} || $myid; }
sub setTemplateIdMap ($@) { my $self = shift; my %TemplateMap = @_; $self->{_IDMap_} = \%TemplateMap; }
sub simpleGraph ($$$@) { my $self = shift; my $id = shift; my $type = shift; my %attrs = @_; my $d = $self->D; my $g = $d->group( id => $id, 'text-anchor' => 'middle' ); $g->comment( "drawing element which defines the graph boundaries for graph $id"); my $graph = $g->rect(%attrs); my $cy = $graph->getAttribute('x') + $graph->getAttribute('width') / 2; my $cx = $graph->getAttribute('y') / 2; $g->group( id =>$self->mapTemplateId("group.graph.title"), class =>$self->mapTemplateId("group.graph.title"), transform => "translate($cx,$cy)", )->comment("the graph title"); my $t = $g->group( id =>$self->mapTemplateId("group.trace"), ); $t->comment("the trace group"); $t->group( id =>$self->mapTemplateId("group.trace.1"), class =>$self->mapTemplateId("group.trace"), transform => "translate($cx,$cy)", )->comment("trace 1"); $t->group( id =>$self->mapTemplateId("group.trace.title.1"), class =>$self->mapTemplateId("group.trace.title"), )->comment("the trace title"); $t->group( id =>$self->mapTemplateId("group.trace.tick.1"), class =>$self->mapTemplateId("group.trace.tick"), )->comment("the trace ticks"); my $a = $t->group( id =>$self->mapTemplateId("group.trace.axes.1"), class =>$self->mapTemplateId("group.trace.axes"), ); $a->group( id =>$self->mapTemplateId("group.trace.axes.x.1"), class =>$self->mapTemplateId("group.trace.axes.x"), )->comment("the trace x axes"); $a->group( id =>$self->mapTemplateId("group.trace.axes.y.1"), class =>$self->mapTemplateId("group.trace.axes.y"), )->comment("the trace y axes"); $a->group( id =>$self->mapTemplateId("group.trace.axes.values.x.1"), class =>$self->mapTemplateId("group.trace.axes.values.x"), )->comment("the trace axes values in y axis"); $a->group( id =>$self->mapTemplateId("group.trace.axes.values.y.1"), class =>$self->mapTemplateId("group.trace.axes.values.y"), )->comment("the trace axes values in y axis"); $a->group( id =>$self->mapTemplateId("group.trace.axes.titles.x.1"), class =>$self->mapTemplateId("group.trace.axes.titles.x"), )->comment("the trace axes titles in x axis"); $a->group( id =>$self->mapTemplateId("group.trace.axes.titles.y.1"), class =>$self->mapTemplateId("group.trace.axes.titles.y"), )->comment("the trace axes titles in y axis"); } my $dump = qq {<rect "/> <group id="group.graph.title"/> <!-- trace insertion takes place here --> <g id="group.trace.1" fill="none" stroke="black" stroke-width="2"> <group id="group.trace.title.1"/> <!-- draw the data below the gridlines --> <g id="group.trace.data.1" stroke="#333333" stroke-width="0.5" fill="#411DA4"/> <g id="group.trace.grid.1" stroke="gray" stroke-width="1"/> <g id="group.trace.tick.1" stroke="black" stroke-width="1.5"/> <g id="group.trace.axes.1" stroke="none" fill="black"> <g id="group.trace.axes.x.1" stroke="gray" fill="none" stroke-width="1"/> <g id="group.trace.axes.y.1" stroke="gray" fill="none" stroke-width="1"/> <g id="group.trace.axes.values.x.1" text-anchor="middle" stroke="none" fill="black" transform="translate(0,265)"/> <g id="group.trace.axes.values.y.1" stroke="none" fill="black" text-anchor="start" transform="translate(-40,0)"/> <g id="group.trace.axes.title.x.1" text-anchor="middle" transform="translate(365,370)" font-size="12" fill="#411DA4" font-weight="Bold"/> <g id="group.trace.axes.title.y.1.c" text-anchor="end" transform="translate(40,200)"> <g id="group.trace.axes.title.y.1" transform="rotate(-90)" font-weight="Bold" font-size="12" fill="#411DA4"/> </g> </g> </g> <!-- end of trace insertion 1 --> }; #module placeholder 1;
1; __END__