File Coverage

blib/lib/SVG/TT/Graph/Line.pm
Criterion Covered Total %
statement 21 21 100.0
branch 2 2 100.0
condition 4 6 66.6
subroutine 8 8 100.0
pod n/a
total 35 37 94.5


line stmt bran cond sub pod time code
1             package SVG::TT::Graph::Line;
2              
3 3     3   3070 use strict;
  3         6  
  3         81  
4 3     3   13 use Carp;
  3         5  
  3         147  
5 3     3   15 use SVG::TT::Graph;
  3         5  
  3         59  
6 3     3   12 use base qw(SVG::TT::Graph);
  3         4  
  3         1306  
7              
8             our $VERSION = $SVG::TT::Graph::VERSION;
9             our $TEMPLATE_FH = \*DATA;
10              
11             =head1 NAME
12              
13             SVG::TT::Graph::Line - Create presentation quality SVG line graphs easily
14              
15             =head1 SYNOPSIS
16              
17             use SVG::TT::Graph::Line;
18              
19             my @fields = qw(Jan Feb Mar);
20             my @data_sales_02 = qw(12 45 21);
21             my @data_sales_03 = qw(15 30 40);
22              
23             my $graph = SVG::TT::Graph::Line->new({
24             'height' => '500',
25             'width' => '300',
26             'fields' => \@fields,
27             });
28              
29             $graph->add_data({
30             'data' => \@data_sales_02,
31             'title' => 'Sales 2002',
32             });
33              
34             $graph->add_data({
35             'data' => \@data_sales_03,
36             'title' => 'Sales 2003',
37             });
38              
39             print "Content-type: image/svg+xml\n\n";
40             print $graph->burn();
41              
42             =head1 DESCRIPTION
43              
44             This object aims to allow you to easily create high quality
45             SVG line graphs. You can either use the default style sheet
46             or supply your own. Either way there are many options which can
47             be configured to give you control over how the graph is
48             generated - with or without a key, data elements at each point,
49             title, subtitle etc.
50              
51             =head1 METHODS
52              
53             =head2 new()
54              
55             use SVG::TT::Graph::Line;
56              
57             # Field names along the X axis
58             my @fields = qw(Jan Feb Mar);
59              
60             my $graph = SVG::TT::Graph::Line->new({
61             # Required
62             'fields' => \@fields,
63              
64             # Optional - defaults shown
65             'height' => '500',
66             'width' => '300',
67              
68             'show_data_points' => 1,
69             'show_data_values' => 1,
70             'stacked' => 0,
71              
72             'min_scale_value' => '0',
73             'max_scale_value' => undef,
74             'area_fill' => 0,
75             'show_x_labels' => 1,
76             'stagger_x_labels' => 0,
77             'rotate_x_labels' => 0,
78             'show_y_labels' => 1,
79             'scale_integers' => 0,
80             'scale_divisions' => '20',
81             'y_label_formatter' => sub { return @_ },
82             'x_label_formatter' => sub { return @_ },
83              
84             'show_x_title' => 0,
85             'x_title' => 'X Field names',
86              
87             'show_y_title' => 0,
88             'y_title_text_direction' => 'bt',
89             'y_title' => 'Y Scale',
90              
91             'show_graph_title' => 0,
92             'graph_title' => 'Graph Title',
93             'show_graph_subtitle' => 0,
94             'graph_subtitle' => 'Graph Sub Title',
95             'key' => 0,
96             'key_position' => 'right',
97              
98             # Stylesheet defaults
99             'style_sheet' => '/includes/graph.css', # internal stylesheet
100             'random_colors' => 0,
101             });
102              
103             The constructor takes a hash reference, fields (the names for each
104             field on the X axis) MUST be set, all other values are defaulted to those
105             shown above - with the exception of style_sheet which defaults
106             to using the internal style sheet.
107              
108             =head2 add_data()
109              
110             my @data_sales_02 = qw(12 45 21);
111              
112             $graph->add_data({
113             'data' => \@data_sales_02,
114             'title' => 'Sales 2002',
115             });
116              
117             This method allows you to add data to the graph object.
118             It can be called several times to add more data sets in.
119              
120             =head2 clear_data()
121              
122             my $graph->clear_data();
123              
124             This method removes all data from the object so that you can
125             reuse it to create a new graph but with the same config options.
126              
127             =head2 burn()
128              
129             print $graph->burn();
130              
131             This method processes the template with the data and
132             config which has been set and returns the resulting SVG.
133              
134             This method will croak unless at least one data set has
135             been added to the graph object.
136              
137             =head2 config methods
138              
139             my $value = $graph->method();
140             my $confirmed_new_value = $graph->method($value);
141              
142             The following is a list of the methods which are available
143             to change the config of the graph object after it has been
144             created.
145              
146             =over 4
147              
148             =item height()
149              
150             Set the height of the graph box, this is the total height
151             of the SVG box created - not the graph it self which auto
152             scales to fix the space.
153              
154             =item width()
155              
156             Set the width of the graph box, this is the total height
157             of the SVG box created - not the graph it self which auto
158             scales to fix the space.
159              
160             =item compress()
161              
162             Whether or not to compress the content of the SVG file (Compress::Zlib required).
163              
164             =item tidy()
165              
166             Whether or not to tidy the content of the SVG file (XML::Tidy required).
167              
168             =item style_sheet()
169              
170             Set the path to an external stylesheet, set to '' if
171             you want to revert back to using the defaut internal version.
172              
173             Set to "inline:<style>...</style>" with your CSS in between the tags.
174             You can thus override the default style without requireing an external URL.
175              
176             The default stylesheet handles up to 12 data sets. All data series over
177             the 12th will have no style and be in black. If you have over 12 data
178             sets you can assign them all random colors (see the random_color()
179             method) or create your own stylesheet and add the additional settings
180             for the extra data sets.
181              
182             To create an external stylesheet create a graph using the
183             default internal version and copy the stylesheet section to
184             an external file and edit from there.
185              
186             =item random_colors()
187              
188             Use random colors in the internal stylesheet
189              
190             =item show_data_values()
191              
192             Show the value of each element of data on the graph
193              
194             =item show_data_points()
195              
196             Show a small circle on the graph where the line
197             goes from one point to the next.
198              
199             =item stacked()
200              
201             Accumulates each data set. (i.e. Each point increased by
202             sum of all previous series at same point). Default is 0,
203             set to '1' to show.
204              
205             =item min_scale_value()
206              
207             The point at which the Y axis starts, defaults to '0',
208             if set to '' it will default to the minimum data value.
209              
210             =item max_scale_value()
211              
212             The maximum value for the Y axis. If set to '', it will
213             default to the maximum data value.
214              
215             =item show_x_labels()
216              
217             Whether to show labels on the X axis or not, defaults
218             to 1, set to '0' if you want to turn them off.
219              
220             =item show_y_labels()
221              
222             Whether to show labels on the Y axis or not, defaults
223             to 1, set to '0' if you want to turn them off.
224              
225             =item scale_integers()
226              
227             Ensures only whole numbers are used as the scale divisions.
228             Default it '0', to turn on set to '1'. This has no effect if
229             scale divisions are less than 1.
230              
231             =item scale_divisions()
232              
233             This defines the gap between markers on the Y axis,
234             default is a 10th of the max_value, e.g. you will have
235             10 markers on the Y axis. NOTE: do not set this too
236             low - you are limited to 999 markers, after that the
237             graph won't generate.
238              
239             =item stagger_x_labels()
240              
241             This puts the labels at alternative levels so if they
242             are long field names they will not overlap so easily.
243             Default it '0', to turn on set to '1'.
244              
245             =item rotate_x_labels()
246              
247             This turns the X axis labels by 90 degrees.
248             Default it '0', to turn on set to '1'.
249              
250             =item show_x_title()
251              
252             Whether to show the title under the X axis labels,
253             default is 0, set to '1' to show.
254              
255             =item x_title()
256              
257             What the title under X axis should be, e.g. 'Months'.
258              
259             =item show_y_title()
260              
261             Whether to show the title under the Y axis labels,
262             default is 0, set to '1' to show.
263              
264             =item y_title_text_direction()
265              
266             Aligns writing mode for Y axis label. Defaults to 'bt' (Bottom to Top).
267             Change to 'tb' (Top to Bottom) to reverse.
268              
269             =item y_title()
270              
271             What the title under Y axis should be, e.g. 'Sales in thousands'.
272              
273             =item show_graph_title()
274              
275             Whether to show a title on the graph,
276             default is 0, set to '1' to show.
277              
278             =item graph_title()
279              
280             What the title on the graph should be.
281              
282             =item show_graph_subtitle()
283              
284             Whether to show a subtitle on the graph,
285             default is 0, set to '1' to show.
286              
287             =item graph_subtitle()
288              
289             What the subtitle on the graph should be.
290              
291             =item key()
292              
293             Whether to show a key, defaults to 0, set to
294             '1' if you want to show it.
295              
296             =item key_position()
297              
298             Where the key should be positioned, defaults to
299             'right', set to 'bottom' if you want to move it.
300              
301             =item x_label_formatter ()
302              
303             A callback subroutine which will format a label on the x axis. For example:
304              
305             $graph->x_label_formatter( sub { return '$' . $_[0] } );
306              
307             =item y_label_formatter()
308              
309             A callback subroutine which will format a label on the y axis. For example:
310              
311             $graph->y_label_formatter( sub { return '$' . $_[0] } );
312              
313             =back
314              
315             =head1 EXAMPLES
316              
317             For examples look at the project home page
318             http://leo.cuckoo.org/projects/SVG-TT-Graph/
319              
320             =head1 EXPORT
321              
322             None by default.
323              
324             =head1 SEE ALSO
325              
326             L<SVG::TT::Graph>,
327             L<SVG::TT::Graph::Bar>,
328             L<SVG::TT::Graph::BarHorizontal>,
329             L<SVG::TT::Graph::BarLine>,
330             L<SVG::TT::Graph::Pie>,
331             L<SVG::TT::Graph::TimeSeries>,
332             L<SVG::TT::Graph::XY>,
333             L<Compress::Zlib>,
334             L<XML::Tidy>
335              
336             =cut
337              
338             sub _init {
339 3     3   8 my $self = shift;
340             croak "fields was not supplied or is empty"
341             unless defined $self->{'config'}->{fields}
342             && ref($self->{'config'}->{fields}) eq 'ARRAY'
343 3 100 66     262 && scalar(@{$self->{'config'}->{fields}}) > 0;
  2   66     10  
344             }
345              
346             sub _set_defaults {
347 3     3   6 my $self = shift;
348              
349             my %default = (
350             'width' => '500',
351             'height' => '300',
352              
353             'style_sheet' => '',
354             'random_colors' => 0,
355              
356             'show_data_points' => 1,
357             'show_data_values' => 1,
358             'stacked' => 0,
359              
360             'min_scale_value' => '0',
361             'max_scale_value' => '',
362             'area_fill' => 0,
363             'show_x_labels' => 1,
364             'stagger_x_labels' => 0,
365             'rotate_x_labels' => 0,
366             'show_y_labels' => 1,
367             'scale_integers' => 0,
368             'scale_divisions' => '',
369 3     3   2041 'x_label_formatter' => sub { return @_ },
370 11     11   275 'y_label_formatter' => sub { return @_ },
371              
372 3         76 'show_x_title' => 0,
373             'x_title' => 'X Field names',
374              
375             'show_y_title' => 0,
376             'y_title_text_direction' => 'bt',
377             'y_title' => 'Y Scale',
378              
379             'show_graph_title' => 0,
380             'graph_title' => 'Graph Title',
381             'show_graph_subtitle' => 0,
382             'graph_subtitle' => 'Graph Sub Title',
383             'key' => 0,
384             'key_position' => 'right', # bottom or right
385             );
386              
387 3         18 while( my ($key,$value) = each %default ) {
388 87         213 $self->{config}->{$key} = $value;
389             }
390             }
391              
392             1;
393             __DATA__
394             <?xml version="1.0"?>
395             <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
396             "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
397             [% stylesheet = 'included' %]
398              
399             [% IF config.style_sheet && config.style_sheet != '' && config.style_sheet.substr(0,7) != 'inline:' %]
400             <?xml-stylesheet href="[% config.style_sheet %]" type="text/css"?>
401             [% ELSIF config.style_sheet && config.style_sheet.substr(0,7) == 'inline:'%]
402             [% stylesheet = 'inline'
403             style_inline = config.style_sheet.substr(7) %]
404             [% ELSE %]
405             [% stylesheet = 'excluded' %]
406             [% END %]
407              
408             <svg width="[% config.width %]" height="[% config.height %]" viewBox="0 0 [% config.width %] [% config.height %]" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
409              
410             <!-- \\\\\\\\\\\\\\\\\\\\\\\\\\\\ -->
411             <!-- Created with SVG::TT::Graph -->
412             <!-- Stephen Morgan / Leo Lapworth -->
413             <!-- //////////////////////////// -->
414              
415             [% IF stylesheet == 'inline' %]
416             [% style_inline %]
417             [% ELSIF stylesheet == 'excluded' %]
418             <!-- include default stylesheet if none specified -->
419             <defs>
420             <style type="text/css">
421             <![CDATA[
422             /* Copy from here for external style sheet */
423             .svgBackground{
424             fill:#ffffff;
425             }
426             .graphBackground{
427             fill:#f0f0f0;
428             }
429              
430             /* graphs titles */
431             .mainTitle{
432             text-anchor: middle;
433             fill: #000000;
434             font-size: 14px;
435             font-family: "Arial", sans-serif;
436             font-weight: normal;
437             }
438             .subTitle{
439             text-anchor: middle;
440             fill: #999999;
441             font-size: 12px;
442             font-family: "Arial", sans-serif;
443             font-weight: normal;
444             }
445              
446             .axis{
447             stroke: #000000;
448             stroke-width: 1px;
449             }
450              
451             .guideLines{
452             stroke: #666666;
453             stroke-width: 1px;
454             stroke-dasharray: 5 5;
455             }
456              
457             .xAxisLabels{
458             text-anchor: middle;
459             fill: #000000;
460             font-size: 12px;
461             font-family: "Arial", sans-serif;
462             font-weight: normal;
463             }
464              
465             .yAxisLabels{
466             text-anchor: end;
467             fill: #000000;
468             font-size: 12px;
469             font-family: "Arial", sans-serif;
470             font-weight: normal;
471             }
472              
473             .xAxisTitle{
474             text-anchor: middle;
475             fill: #ff0000;
476             font-size: 14px;
477             font-family: "Arial", sans-serif;
478             font-weight: normal;
479             }
480              
481             .yAxisTitle{
482             fill: #ff0000;
483             text-anchor: middle;
484             font-size: 14px;
485             font-family: "Arial", sans-serif;
486             font-weight: normal;
487             }
488              
489             .dataPointLabel{
490             fill: #000000;
491             text-anchor:middle;
492             font-size: 10px;
493             font-family: "Arial", sans-serif;
494             font-weight: normal;
495             }
496             .staggerGuideLine{
497             fill: none;
498             stroke: #000000;
499             stroke-width: 0.5px;
500             }
501              
502             [% FOREACH dataset = data %]
503             [% color = '' %]
504             [% IF config.random_colors %]
505             [% color = random_color() %]
506             [% ELSE %]
507             [% color = predefined_color(loop.count) %]
508             [% END %]
509              
510             .line[% loop.count %]{
511             fill: none;
512             stroke: [% color %];
513             stroke-width: 1px;
514             }
515              
516             .key[% loop.count %],.fill[% loop.count %]{
517             fill: [% color %];
518             stroke: none;
519             stroke-width: 1px;
520             }
521              
522             [% LAST IF (config.random_colors == 0 && loop.count == 12) %]
523             [% END %]
524              
525             .keyText{
526             fill: #000000;
527             text-anchor:start;
528             font-size: 10px;
529             font-family: "Arial", sans-serif;
530             font-weight: normal;
531             }
532             /* End copy for external style sheet */
533             ]]>
534             </style>
535             </defs>
536             [% END %]
537             <!-- svg bg -->
538             <rect x="0" y="0" width="[% config.width %]" height="[% config.height %]" class="svgBackground"/>
539              
540              
541             <!-- ///////////////// CALCULATE GRAPH AREA AND BOUNDARIES //////////////// -->
542             <!-- get dimensions of actual graph area (NOT SVG area) -->
543             [% w = config.width %]
544             [% h = config.height %]
545              
546             <!-- set start/default coords of graph -->
547             [% x = 0 %]
548             [% y = 0 %]
549              
550             [% char_width = 9 %]
551             [% half_char_height = 2.5 %]
552              
553             [% IF config.stacked %]
554             <!-- pre-stack the data -->
555             [% FOREACH field = config.fields %]
556             [% cumulative = 0 %]
557             [% FOREACH dataset = data %]
558             [% IF dataset.data.$field != '' %]
559             [% cumulative = cumulative + dataset.data.$field %]
560             [% END %]
561             [% dataset.data.$field = cumulative %]
562             [% END %]
563             [% END %]
564             [% END %]
565              
566             <!-- calc min and max values -->
567             [% min_value = 99999999999 %]
568             [% max_value = 0 %]
569             [% max_key_size = 0 %]
570             [% max_x_label_size = 0 %]
571             <!-- find largest labels -->
572             [% FOREACH field = config.fields %]
573             [% IF max_x_label_size < field.length %]
574             [% max_x_label_size = field.length %]
575             [% END %]
576              
577             [% FOREACH dataset = data %]
578             [% IF min_value > dataset.data.$field && dataset.data.$field != '' %]
579             [% min_value = dataset.data.$field %]
580             [% END %]
581              
582             [% IF max_value < dataset.data.$field && dataset.data.$field != '' %]
583             [% max_value = dataset.data.$field %]
584             [% END %]
585              
586             [% IF dataset.title %]
587             [% IF max_key_size < dataset.title.length %]
588             [% max_key_size = dataset.title.length %]
589             [% END %]
590             [% END %]
591             [% END %]
592             [% END %]
593              
594              
595             <!-- CALC HEIGHT AND Y COORD DIMENSIONS -->
596             <!-- reduce height of graph area if there is labelling on x axis -->
597             [% IF config.show_x_labels %][% h = h - 20 %][% END %]
598              
599             <!-- reduce height if x labels are rotated -->
600             [% max_x_label_length = 0 %]
601             [% IF config.rotate_x_labels %]
602             [% max_x_label_length = max_x_label_size * char_width %]
603             [% h = h - max_x_label_length %]
604             [% END %]
605              
606             <!-- stagger x labels if overlapping occurs -->
607             [% stagger = 0 %]
608             [% IF config.show_x_labels && config.stagger_x_labels %]
609             [% stagger = 17 %]
610             [% h = h - stagger %]
611             [% END %]
612              
613             [% IF config.show_x_title %][% h = h - 25 - stagger %][% END %]
614              
615             <!-- pad top of graph if y axis has data labels so labels do not get chopped off -->
616             [% IF config.show_y_labels %][% h = h - 10 %][% y = y + 10 %][% END %]
617              
618             <!-- reduce height if graph has title or subtitle -->
619             [% IF config.show_graph_title %][% h = h - 25 %][% y = y + 25 %][% END %]
620             [% IF config.show_graph_subtitle %][% h = h - 10 %][% y = y + 10 %][% END %]
621              
622              
623             <!-- reduce graph dimensions if there is a KEY -->
624             [% key_box_size = 12 %]
625             [% key_padding = 5 %]
626              
627             [% IF config.key && config.key_position == 'right' %]
628             [% w = w - (max_key_size * (char_width - 1)) - (key_box_size * 3 ) %]
629             [% ELSIF config.key && config.key_position == 'bottom' %]
630             [% IF data.size < 4 %]
631             [% h = h - ((data.size + 1) * (key_box_size + key_padding))%]
632             [% ELSE %]
633             [% h = h - (4 * (key_box_size + key_padding))%]
634             [% END %]
635             [% END %]
636              
637             <!-- find start value for scale on y axis -->
638             [% IF config.min_scale_value || config.min_scale_value == '0' %]
639             [% min_scale_value = config.min_scale_value %]
640             [% ELSE %]
641             <!-- setting lowest value to be min_value as no min_scale_value defined -->
642             [% min_scale_value = min_value %]
643             [% END %]
644              
645             <!-- find ending value for scale on y axis -->
646             [% IF config.max_scale_value || config.max_scale_value == '0' %]
647             [% max_scale_value = config.max_scale_value %]
648             [% ELSE %]
649             <!-- setting highest value to be max_value as no max_scale_value defined -->
650             [% max_scale_value = max_value %]
651             [% END %]
652              
653             <!-- base line -->
654             [% base_line = h + y %]
655              
656             <!-- how much padding between largest bar and top of graph -->
657             [% IF (max_scale_value - min_scale_value) == 0 %]
658             [% top_pad = 10 %]
659             [% ELSE %]
660             [% top_pad = (max_scale_value - min_scale_value) / 20 %]
661             [% END %]
662              
663             [% scale_range = (max_scale_value + top_pad) - min_scale_value %]
664              
665             <!-- default to 10 scale_divisions if none have been set -->
666             [% IF config.scale_divisions %]
667             [% scale_division = config.scale_divisions %]
668             [% ELSE %]
669             [% scale_division = scale_range / 10 FILTER format('%2.01f') %]
670             [% END %]
671              
672             [% IF config.scale_integers %]
673             [% IF scale_division < 1 %]
674             [% scale_division = 1 %]
675             [% ELSIF scale_division.match('.') %]
676             [% scale_division = scale_division FILTER format('%2.0f') %]
677             [% END %]
678             [% END %]
679              
680             <!-- find the string length of max value -->
681             [% max_value_length = max_value.length %]
682              
683             <!-- label width in pixels -->
684             [% max_value_length_px = max_value_length * char_width %]
685             <!-- If the y labels are shown but the size of the x labels are small, pad for y labels -->
686              
687             <!-- CALC WIDTH AND X COORD DIMENSIONS -->
688             <!-- reduce width of graph area if there is large labelling on x axis -->
689             [% space_b4_y_axis = (config.fields.0.length / 2) * char_width %]
690              
691             [% IF config.show_x_labels %]
692             [% IF config.key && config.key_position == 'right' %]
693             [% w = w - space_b4_y_axis %]
694             [% ELSE %]
695             <!-- pad both sides -->
696             [% w = w - (space_b4_y_axis * 2) %]
697             [% END %]
698             [% x = x + space_b4_y_axis %]
699             [% ELSIF config.show_data_values %]
700             [% w = w - (max_value_length_px * 2) %]
701             [% x = x + max_value_length_px %]
702             [% END %]
703              
704              
705             [% IF config.show_y_labels && space_b4_y_axis < max_value_length_px %]
706             <!-- allow slightly more padding if small labels -->
707             [% IF max_value_length < 2 %]
708             [% w = w - (max_value_length * (char_width * 2)) %]
709             [% x = x + (max_value_length * (char_width * 2)) %]
710             [% ELSE %]
711             [% w = w - max_value_length_px %]
712             [% x = x + max_value_length_px %]
713             [% END %]
714             [% ELSIF config.show_y_labels && !config.show_x_labels %]
715             [% w = w - max_value_length_px %]
716             [% x = x + max_value_length_px %]
717             [% END %]
718              
719             [% IF config.show_y_title %]
720             [% w = w - 25 %]
721             [% x = x + 25 %]
722             [% END %]
723              
724              
725             <!-- ////////////////////////////// BUILD GRAPH AREA ////////////////////////////// -->
726             <!-- graph bg -->
727             <rect x="[% x %]" y="[% y %]" width="[% w %]" height="[% h %]" class="graphBackground"/>
728              
729             <!-- axis -->
730             <path d="M[% x %] [% base_line %] h[% w %]" class="axis" id="xAxis"/>
731             <path d="M[% x %] [% y %] v[% h %]" class="axis" id="yAxis"/>
732              
733             <!-- ////////////////////////////// AXIS DISTRIBUTIONS //////////////////////////// -->
734             <!-- get number of data points on x scale -->
735             [% dx = config.fields.size %]
736             <!-- ensure x_data_points butt up to edge of graph -->
737             [% dx = dx - 1 %]
738             [% IF dx == 0 %]
739             [% dx = 1 %]
740             [% END %]
741              
742             <!-- get distribution width on x axis -->
743             [% data_widths_x = w / dx %]
744             [% dw = data_widths_x FILTER format('%2.02f') %]
745              
746             [% i = dw %]
747             [% count = 0 %]
748             [% stagger_count = 0 %]
749              
750             <!-- x axis labels -->
751             [% IF config.show_x_labels %]
752             [% FOREACH field = config.fields %]
753             [% field_txt = config.x_label_formatter(field) %]
754             [% IF count == 0 %]
755             <text x="[% x %]" y="[% base_line + 15 %]" [% IF config.rotate_x_labels %]transform="rotate(90 [% x - half_char_height %] [% base_line + 15 %])" style="text-anchor: start" [% END %]class="xAxisLabels">[% field_txt %]</text>
756             [% i = i - dw %]
757             [% ELSE %]
758             [% IF stagger_count == 2 %]
759             <text x="[% x + i %]" y="[% base_line + 15 %]" [% IF config.rotate_x_labels %]transform="rotate(90 [% x + i - half_char_height %] [% base_line + 15 %])" style="text-anchor: start" [% END %]class="xAxisLabels">[% field_txt %]</text>
760             [% stagger_count = 0 %]
761             [% ELSE %]
762             <text x="[% x + i %]" y="[% base_line + 15 + stagger %]" [% IF config.rotate_x_labels %]transform="rotate(90 [% x + i - half_char_height %] [% base_line + 15 + stagger %])" style="text-anchor: start" [% END %]class="xAxisLabels">[% field_txt %]</text>
763             <path d="M[% x + i %] [% base_line %] v[% stagger %]" class="staggerGuideLine"/>
764             [% END %]
765             [% END %]
766             [% i = i + dw %]
767             [% count = count + 1 %]
768             [% stagger_count = stagger_count + 1 %]
769             [% END %]
770             [% END %]
771              
772              
773             <!-- distribute Y scale -->
774             [% dy = scale_range / scale_division %]
775             [% IF dy == 0 %]
776             [% dy = 1 %]
777             [% END %]
778             <!-- ensure y_data_points butt up to edge of graph -->
779             [% y_marker_height = h / dy %]
780             [% dy = y_marker_height.match('(\d+[\.\d\d])').0 %]
781             [% count = 0 %]
782             [% y_value = min_scale_value %]
783              
784             [% IF ((min_scale_value > max_scale_value) && (scale_division > 0)) %]
785             <!-- Reversed y range -->
786             [% scale_division = -1 * scale_division %]
787             [% END %]
788              
789             [% IF config.show_y_labels %]
790             [% WHILE (dy * count) < h %]
791             [% y_value_txt = config.y_label_formatter(y_value) %]
792             [% IF count == 0 %]
793             <!-- no stroke for first line -->
794             <text x="[% x - 5 %]" y="[% base_line - (dy * count) %]" class="yAxisLabels">[% y_value_txt %]</text>
795             [% ELSE %]
796             <text x="[% x - 5 %]" y="[% base_line - (dy * count) %]" class="yAxisLabels">[% y_value_txt %]</text>
797             <path d="M[% x %] [% base_line - (dy * count) %] h[% w %]" class="guideLines"/>
798             [% END %]
799             [% y_value = y_value + scale_division %]
800             [% count = count + 1 %]
801             [% END %]
802             [% END %]
803              
804              
805             <!-- ////////////////////////////// AXIS TITLES ////////////////////////////// -->
806              
807             <!-- x axis title -->
808             [% IF config.show_x_title %]
809             [% IF !config.show_x_labels %]
810             [% y_xtitle = 15 %]
811             [% ELSE %]
812             [% y_xtitle = 35 %]
813             [% END %]
814             <text x="[% (w / 2) + x %]" y="[% h + y + y_xtitle + stagger + max_x_label_length %]" class="xAxisTitle">[% config.x_title %]</text>
815             [% END %]
816              
817             <!-- y axis title -->
818             [% IF config.show_y_title %]
819             [% IF config.y_title_text_direction == 'tb' %]
820             <text x="11" y="[% (h / 2) + y %]" class="yAxisTitle" style="writing-mode:tb;">[% config.y_title %]</text>
821             [% ELSE %]
822             <text class="yAxisTitle" transform="translate(15,[% (h / 2) + y %]) rotate(270)">[% config.y_title %]</text>
823             [% END %]
824             [% END %]
825              
826              
827              
828              
829             <!-- ////////////////////////////// SHOW DATA ////////////////////////////// -->
830              
831             [% divider = dy / scale_division %]
832             [% line = data.size %]
833             [% FOREACH dataset = data.reverse %]
834              
835             [% IF config.area_fill %]
836             <!--- create alternate fill first (so line can overwrite if necessary) -->
837             <path d="M[% x %] [% base_line %] L
838             [% xcount = 0 %]
839             [% FOREACH field = config.fields %]
840             [% (dw * xcount) + x %] [% base_line - ((dataset.data.$field - min_scale_value) * divider) %]
841             [% xcount = xcount + 1 %]
842             [% END %]
843             [% (dw * (xcount - 1)) + x %] [% base_line %] Z" class="fill[% line %]"/>
844             [% END %]
845              
846             <!--- create line -->
847             <path d="M
848             [% xcount = 0 %]
849             [% FOREACH field = config.fields %]
850             [% IF xcount == 1 %] L [% END %]
851             [% (dw * xcount) + x %] [% base_line - ((dataset.data.$field - min_scale_value) * divider) %]
852             [% xcount = xcount + 1 %]
853             [% END %]" class="line[% line %]"/>
854              
855             [% IF config.show_data_points || config.show_data_values%]
856             [% xcount = 0 %]
857             [% FOREACH field = config.fields %]
858             [% IF config.show_data_points %]
859             <!-- datapoint shown -->
860             <circle cx="[% (dw * xcount) + x %]" cy="[% base_line - ((dataset.data.$field - min_scale_value) * divider) %]" r="2.5" class="dataPoint[% line %]"/>
861             [% END %]
862              
863             [% IF config.show_data_values %]
864             <!-- datavalue shown -->
865             <text x="[% (dw * xcount) + x %]" y="[% base_line - ((dataset.data.$field - min_scale_value) * divider) - 6 %]" class="dataPointLabel">[% dataset.data.$field %]</text>
866             [% END %]
867             [% xcount = xcount + 1 %]
868             [% END %]
869             [% END %]
870              
871             [% line = line - 1 %]
872             [% END %]
873              
874             <!-- /////////////////////////////////// KEY /////////////////////////////// -->
875             [% key_count = 1 %]
876             [% IF config.key && config.key_position == 'right' %]
877             [% FOREACH dataset = data %]
878             <rect x="[% x + w + 20 %]" y="[% y + (key_box_size * key_count) + (key_count * key_padding) %]" width="[% key_box_size %]" height="[% key_box_size %]" class="key[% key_count %]"/>
879             <text x="[% x + w + 20 + key_box_size + key_padding %]" y="[% y + (key_box_size * key_count) + (key_count * key_padding) + key_box_size %]" class="keyText">[% dataset.title %]</text>
880             [% key_count = key_count + 1 %]
881             [% END %]
882             [% ELSIF config.key && config.key_position == 'bottom' %]
883             <!-- calc y position of start of key -->
884             [% y_key = base_line %]
885             [% IF config.show_x_title %]
886             [% y_key = y_key + 25 %]
887             [% END %]
888             [% IF config.rotate_x_labels && config.show_x_labels %]
889             [% y_key = y_key + max_x_label_length %]
890             [% ELSIF config.show_x_labels && stagger < 1 %]
891             [% y_key = y_key + 20 %]
892             [% END %]
893              
894             [% y_key_start = y_key %]
895             [% x_key = x %]
896             [% FOREACH dataset = data %]
897             [% IF key_count == 4 || key_count == 7 || key_count == 10 %]
898             <!-- wrap key every 3 entries -->
899             [% x_key = x_key + 200 %]
900             [% y_key = y_key - (key_box_size * 4) - 2 %]
901             [% END %]
902             <rect x="[% x_key %]" y="[% y_key + (key_box_size * key_count) + (key_count * key_padding) + stagger %]" width="[% key_box_size %]" height="[% key_box_size %]" class="key[% key_count %]"/>
903              
904             <text x="[% x_key + key_box_size + key_padding %]" y="[% y_key + (key_box_size * key_count) + (key_count * key_padding) + key_box_size + stagger %]" class="keyText">[% dataset.title %]</text>
905             [% key_count = key_count + 1 %]
906             [% END %]
907              
908             [% END %]
909              
910             <!-- //////////////////////////////// MAIN TITLES ////////////////////////// -->
911              
912             <!-- main graph title -->
913             [% IF config.show_graph_title %]
914             <text x="[% config.width / 2 %]" y="15" class="mainTitle">[% config.graph_title %]</text>
915             [% END %]
916              
917             <!-- graph sub title -->
918             [% IF config.show_graph_subtitle %]
919             [% IF config.show_graph_title %]
920             [% y_subtitle = 30 %]
921             [% ELSE %]
922             [% y_subtitle = 15 %]
923             [% END %]
924             <text x="[% config.width / 2 %]" y="[% y_subtitle %]" class="subTitle">[% config.graph_subtitle %]</text>
925             [% END %]
926             </svg>