File Coverage

blib/lib/SVG/TT/Graph/BarHorizontal.pm
Criterion Covered Total %
statement 20 21 95.2
branch 2 2 100.0
condition 4 6 66.6
subroutine 7 8 87.5
pod n/a
total 33 37 89.1


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