File Coverage

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