File Coverage

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