File Coverage

blib/lib/PostScript/Graph/Bar.pm
Criterion Covered Total %
statement 210 234 89.7
branch 90 120 75.0
condition 8 9 88.8
subroutine 12 19 63.1
pod 12 12 100.0
total 332 394 84.2


line stmt bran cond sub pod time code
1             package PostScript::Graph::Bar;
2             our $VERSION = 0.03;
3 9     9   657287 use strict;
  9         24  
  9         422  
4 9     9   59 use warnings;
  9         20  
  9         315  
5 9     9   13034 use Text::CSV_XS;
  9         173265  
  9         1126  
6 9     9   115 use PostScript::File 1.00 qw(check_file str);
  9         368  
  9         842  
7 9     9   15044 use PostScript::Graph::Key 1.00;
  9         250  
  9         1157  
8 9     9   119 use PostScript::Graph::Paper 1.00;
  9         162  
  9         244  
9 9     9   13904 use PostScript::Graph::Style 1.00;
  9         313  
  9         69733  
10              
11             =head1 NAME
12              
13             PostScript::Graph::Bar - draw a bar chart on a postscript file
14              
15             =head1 SYNOPSIS
16              
17             =head2 Simplest
18              
19             Take labels and values from a csv file and output as a bar chart on a postscript file.
20              
21             use PostScript::Graph::Bar;
22            
23             my $bar = new PostScript::Graph::Bar();
24             $bar->build_chart("survey.csv");
25             $bar->output("survey");
26              
27             =head2 Typical
28              
29             use PostScript::Graph::Bar;
30              
31             my $bar = new PostScript::Graph::Bar(
32             file => {
33             paper => 'A4',
34             landscape => 1,
35             },
36             layout => {
37             background => [1, 1, 0.9],
38             heading => 'Test results',
39             },
40             y_axis => {
41             smallest => 4,
42             },
43             style => {
44             auto => [qw(green blue red)],
45             }
46             );
47              
48             $bar->series_from_file( 'data.csv' );
49             $bar->build_chart();
50             $bar->output( 'results' );
51              
52             The file 'data.csv' has a row of headings followed by 4 rows of 10 items. This
53             produces a bar chart with four groups of ten bars each. The groups are labelled
54             with the first value in each row. The bars in each group are coloured ranging
55             from brown through green and then shades of blue. A Key links the row of
56             headings to each colour. In addition, the background is beige, a heading is
57             placed above the chart and the y axis is not too crowded.
58              
59             =head2 All options
60              
61             use PostScript::Graph::Bar;
62              
63             my $bar = new PostScript::Graph::Bar(
64             file => {
65             # Paper size, orientation etc
66             # See PostScript::File
67             },
68             layout => {
69             # General proportions, headings
70             # See PostScript::Graph::Paper
71             },
72             x_axis => {
73             # All settings for X axis
74             # See PostScript::Graph::Paper
75             },
76             y_axis => {
77             # All settings for Y axis
78             # See PostScript::Graph::Paper
79             },
80             style => {
81             # Appearance of bars
82             # See PostScript::Graph::Style
83             },
84             key => {
85             # Settings for any Key area
86             # See PostScript::Graph::Key
87             },
88             show_key => 1,
89             labels_row => 1,
90             );
91            
92             =head1 DESCRIPTION
93              
94             This is a top level module in the PostScript::Graph series. It produces bar charts from CSV files. There are
95             three basic variants, depending on the structure of the data.
96              
97             =head2 Independent values
98              
99             A CSV file with just a label and a single value on each line produces the most basic form of bar chart. All the
100             bars are the same colour, all standing alone.
101              
102             File 1
103              
104             Months, Sales
105             March, 671
106             April, 944
107             May, 867
108             June, 851
109              
110             If the first entry in the second column cannot be interpreted as a number it is assumed that the first line of
111             data contains headings for each column. The first column heading becomes the X axis title and the second column
112             heading goes into the Key box alongside the colour of the bars.
113              
114             =head2 Multiple series
115              
116             The CSV file can have more than one column of values. The columns for each row are shown as different coloured
117             bars next to each other. There is then a gap and data for the next row is displayed using the same sequence of
118             coloured bars. The following data would be shown as four groups with two bars in each.
119              
120             File 2
121              
122             Months, Joe, Jim
123             March, 344, 327
124             April, 489, 455
125             May, 437, 430
126             June, 369, 482
127              
128             The groups (months) are labelled across the X axis and each comprise a column for Joe and a column for Jim. A Key
129             shows which coloured bar represents each salesman.
130              
131             =head2 Single series
132              
133             If the CSV file is just a row of numbers, this is interpreted as a single series, so each number is
134             represented as a different coloured bar, and the bars are adjacent to each other. A Key shows the labels
135             associated with each colour. These can either come from the first line of data or passed as a seperate array.
136              
137             File 3
138              
139             Months, March, April, May, June
140             Sales, 671, 1044, 867, 2851
141              
142             =head2 Additional data
143              
144             =head3 Series
145              
146             It is possible to have several B calls, each adding more data. The simplest case is where the
147             labels shown across the X axis (rows in the file) are the same for all data sets. Each new set just adds an
148             additional series to the labels.
149              
150             Example 1
151              
152             use PostScript::Graph::Bar;
153             my $b = new PostScript::Graph::Bar();
154              
155             $b->series_from_file( "joe_sales.csv" );
156             $b->series_from_file( "jim_sales.csv" );
157            
158             Each file here would be just two columns like File 1. But the end result would be the same as for File 2.
159              
160             =head3 Labels
161              
162             Data can also be extended by adding more items (labels) within a series. Care should be taken with this, as any
163             duplicate data will be overwritten.
164              
165             File 4
166              
167             Months, Joe, Jim
168             July, 392, 404
169             August, 401, 438
170              
171             Example 2
172              
173             my $b = new PostScript::Graph::Bar();
174              
175             $b->series_from_file( "file_2.csv" );
176             $b->series_from_file( "file_4.csv", 0 );
177            
178             The sales data for both Joe and Jim would now cover months March to August. Note that the C flag must
179             be set to '0' for this behaviour.
180              
181             =head3 Both
182              
183             If the C flag is not 0, the new data is added as new series, regardless of whether series with the
184             same name already exist. It is possible for new labels to be added at the same time. Of course this means that
185             some data slots will have no value and these are just set to zero.
186              
187             File 5
188              
189             Months, Fred
190             June, 288
191             July, 302
192             August, 378
193             Sept, 421
194              
195             Example 3
196              
197             my $b = new PostScript::Graph::Bar();
198              
199             $b->series_from_file( "file_2.csv" );
200             $b->series_from_file( "file_4.csv", 0 );
201             $b->series_from_file( "file_5.csv" );
202              
203             The data would now be as follows:
204              
205             Months, Joe, Jim, Fred
206             March, 344, 327, 0
207             April, 489, 455, 0
208             May, 437, 430, 0
209             June, 369, 482, 288
210             July, 392, 404, 302
211             August, 401, 438, 378
212             Sept, 0, 0, 421
213              
214             =head2 Styles
215              
216             Each series has a PostScript::Graph::Style object associated with it, managing the colour, outline and so on.
217             These objects are created with different values by default, and how these vary is controlled by a StyleSequence.
218             This allows the colours to be controlled as closely (or as loosely) as you like. Each colour could be set up
219             seperately as in Example 4, or they could be generated as in Example 5. See L
220             for all the style settings.
221              
222             Example 4
223              
224             use PostScript::Graph::Style;
225             my $s1 = new PostScript::Graph::Style(
226             auto => 'none',
227             bar => {
228             color => [0.5, 0.2, 0.3],
229             },
230             );
231              
232             Example 5
233              
234             use PostScript::Graph::Style;
235             my $seq = new StyleSequence;
236             $seq->setup('red', [0.5, 0.9]);
237             $seq->setup('green', [0.2, 0.8]);
238             $seq->setup('blue', [0.3]);
239            
240             my $opts = { sequence => $seq,
241             auto => [ 'red', 'green' ],
242             bar => {} };
243            
244             my $s1 = new PostScript::Graph::Style($opts);
245             my $s2 = new PostScript::Graph::Style($opts);
246             my $s3 = new PostScript::Graph::Style($opts);
247             my $s4 = new PostScript::Graph::Style($opts);
248              
249             The fill colour for each style would be as follows.
250              
251             $s1 [0.5, 0.2, 0.3] a dull dark red
252             $s2 [0.9, 0.2, 0.3] a bright red
253             $s3 [0.5, 0.8, 0.3] an orange yellow
254             $s4 [0.9, 0.8, 0.3] light orange cream
255              
256             These could then be used to specify colours for each data series added to the chart.
257              
258             Example 6
259              
260             my $b = new PostScript::Graph::Bar();
261              
262             $b->series_from_file( "joe.csv", $s1 );
263             $b->series_from_file( "jim.csv", $s2 );
264             $b->series_from_file( "fred.csv", $s3 );
265             $b->series_from_file( "alan.csv", $s4 );
266              
267             =head1 CONSTRUCTOR
268              
269             =cut
270              
271             sub new {
272 9     9 1 4859 my $class = shift;
273 9         25 my $opt = {};
274 9 50       53 if (@_ == 1) { $opt = $_[0]; } else { %$opt = @_; }
  0         0  
  9         65  
275            
276 9         23 my $o = {};
277 9         27 bless( $o, $class );
278              
279 9         73 $o->{opt} = $opt;
280 9 100       53 $opt->{x_axis} = {} unless (defined $opt->{x_axis});
281 9 100       47 $opt->{y_axis} = {} unless (defined $opt->{y_axis});
282              
283 9         29 my $ch = $opt->{chart};
284 9 100       65 if (not defined($opt->{style})) {
285 1         11 $opt->{style} = {
286             sequence => new PostScript::Graph::Sequence,
287             auto => [qw(red green blue)],
288             bar => {},
289             }
290             }
291 9 100       58 $opt->{style}{bar} = {} unless (defined $opt->{style}{bar});
292            
293 9 50       46 $o->{key} = defined($opt->{show_key}) ? $opt->{show_key} : 1;
294 9 50       47 $o->{line1} = defined($opt->{labels_row}) ? $opt->{labels_row} : undef;
295              
296 9 50       43 $o->build_chart($ch->{data}, $opt->{style}, 1, 1, $ch->{labels}) if ($o->{data});
297            
298 9         34 return $o;
299             }
300              
301             =head2 new( [options] )
302              
303             C can either be a list of hash keys and values or a hash reference, or omitted altogether. In either
304             case, the hash is expected to have the same structure. A couple of the primary keys are simple values but most
305             point to sub-hashes which hold options or groups themselves. See the B section of L for
306             the complete structure.
307              
308             All color options can take either monochrome or colour format values. If a single number from 0 to 1.0 inclusive,
309             this is interpreted as a shade of grey, with 0 being black and 1 being white. Alternatively an array ref holding
310             three such values is read as holding red, green and blue values - again 1 is the brightest possible value.
311              
312             Value Interpretation
313             ===== ==============
314             0 black
315             0.5 grey
316             1 white
317             [1, 0, 0] red
318             [0, 1, 0] green
319             [0, 0, 1] blue
320             [1, 1, 0.9] light cream
321             [0, 0.8, 0.6] turquoise
322              
323             Other numbers are floating point values in PostScript native units (72 per inch).
324              
325             =head3 file
326              
327             This may be either a PostScript::File object or a hash ref holding options for it. See
328             L for details. Options within this group include the paper size, orientation, debugging
329             features and whether it is an EPS or a normal PostScript file.
330              
331             =head3 labels_row
332              
333             Although an attempt is made to automatically detect labels in the top row of each CSV file, it sometimes fails.
334             Giving a value here forces the module to either use (1) or not use (0) the first row for labels instead of data.
335             (Default: undefined)
336              
337             =head3 layout
338              
339             See L for the options controlling how the various parts of the chart are laid out.
340              
341             =head3 x_axis
342              
343             See L for configuring the X axis sizes, font etc.
344              
345             =head3 y_axis
346              
347             See L for configuring the appearance of the Y axis.
348              
349             =head3 key
350              
351             See L for configuring the appearance of the Key showing what the colours mean.
352              
353             =head3 show_key
354              
355             If set to 0, the Key is hidden. (Default: 1)
356              
357             =head3 style
358              
359             The settings given here control how the colours for each series are generated. See L and
360             L for further information.
361              
362             =head1 OBJECT METHODS
363              
364             =cut
365              
366             sub series_from_array {
367 11     11 1 890 my ($o, $data, @rest) = @_;
368 11         24 my ($styleopts, $newseries, $keynames);
369 11         36 foreach my $arg (@rest) {
370             CASE: {
371 3 50       3 if (ref($arg) eq "HASH") { $styleopts = $arg; last CASE; }
  3         8  
  0         0  
  0         0  
372 3 50       9 if (ref($arg) eq "ARRAY") { $keynames = $arg; last CASE; }
  0         0  
  0         0  
373 3         9 $newseries = ($arg != 0);
374             }
375             }
376 11 50       41 die "Array required\nStopped" unless (defined $data);
377 11 50       67 $styleopts = $o->{opt}{style} unless (defined $styleopts);
378 11 100       51 $newseries = 1 unless (defined $newseries);
379 11 100       68 $o->{series} = [] unless (defined $o->{series});
380 11 100       62 $o->{labels} = [] unless (defined $o->{labels});
381 11 100       50 $o->{data} = {} unless (defined $o->{data});
382            
383             ## extract keynames, if any
384 11         70 my $number = qr/^\s*[-+]?[0-9.]+(?:[Ee][-+]?[0-9.]+)?\s*$/;
385 11 50       40 unless (defined $keynames) {
386 11         26 my $use_line1 = $o->{line1};
387 11         84 my $is_alpha = ($data->[0][1] !~ $number);
388 11 50       39 if (defined $use_line1) {
389 0 0       0 if ($use_line1) {
390 0         0 $keynames = shift @$data;
391 0         0 my $xtitle = shift @$keynames;
392 0 0       0 $o->{opt}{x_axis}{title} = $xtitle unless (defined $o->{opt}{x_axis}{title});
393             }
394             } else {
395 11 50       42 if ($is_alpha) {
396 11         28 $keynames = shift @$data;
397 11         28 my $xtitle = shift @$keynames;
398 11 100       113 $o->{opt}{x_axis}{title} = $xtitle unless (defined $o->{opt}{x_axis}{title});
399             }
400             }
401             }
402 11 50       74 unless (defined $keynames) {
403 0         0 $keynames = [];
404 0         0 for (my $i=1; $i <= $#{$data->[0]}; $i++) {
  0         0  
405 0         0 push @$keynames, "";
406             }
407             }
408              
409             ## create a style for each series
410 11         19 my $nold = @{$o->{series}};
  11         30  
411 11 100       35 if ($newseries) {
412 10         57 for (my $i = 0; $i <= $#$keynames; $i++) {
413 204         1465 my $style = new PostScript::Graph::Style($styleopts);
414 204         280 push @{$o->{series}}, [ $style, $keynames->[$i] ];
  204         1689  
415             }
416             }
417 11         24 my $nnew = @{$o->{series}};
  11         26  
418            
419             ## put values into data
420 11         31 my $d = $o->{data};
421 11         25 my $l = $o->{labels};
422 11         19 my %idx;
423 11         38 for my $label (@{$o->{labels}}) {
  11         33  
424 8         12 $idx{$label}++;
425             }
426             ## add (ysa, ysb, ...) to $o->{data}{
427 11         29 foreach my $row (@$data) {
428 86         120 my $label = shift @$row;
429 86 100       240 push @$l, $label unless ($idx{$label});
430 86 100       149 if ($newseries) {
431 82 100       321 $d->{$label} = [] unless (defined $d->{$label});
432 82         130 my $x = $d->{$label};
433 82         188 while (@$x < $nold) { push @$x, 0; };
  0         0  
434 82         333 push @$x, @$row;
435             } else {
436 4         16 my $x = $d->{$label} = [];
437 4         9 push @$x, @$row;
438             }
439             }
440             ## fill up any labels without new data
441 11         23 foreach my $label (@{$o->{labels}}) {
  11         30  
442 90         127 my $x = $d->{$label};
443 90         417 while (@$x < $nnew) { push @$x, 0; }
  7         38  
444             }
445             }
446             ## Labels are across x axis, series are multiple bars within each label
447             # Case 1: $newseries = 1
448             # data is pushed onto existing data as new series
449             # new labels are filled with 0 for old series values
450             # Case 2: $newseries = 0
451             # data label overwritten if it already exists
452             # data contains all series values needed
453             #
454             # Data structure - multiple series bars within multiple x axis labels
455             # $o->{series} = [ [style0, slabel0],
456             # [style1, slabel1], ... ]
457             # $o->{labels} = [ xlabel0, xlabel1, ... ]
458             # $o->{data} = { xlabel0 => [ ys0, ys1, ... ],
459             # xlabel1 => [ ys0, ys1, ... ], }
460              
461             =head2 series_from_array( data [, style | labels | new_series ]... )
462              
463             =over 8
464              
465             =item data
466              
467             An array ref pointing to a list of array refs. Each sub-array hold the data for one CSV row - a list comprising
468             one label followed by one or more numbers.
469              
470             =item style
471              
472             An optional hash ref. This should contain settings for the PostScript::Graph::Style objects which will be
473             associated with each column of data. If present, the whole hash ref overrides any 'style' hash ref given to
474             B.
475              
476             =item labels
477              
478             An optional array ref. This list of series names will appear in the Key, replacing the column headings, if given, as
479             the first data line.
480              
481             =item new_series
482              
483             A flag indicating whether the columns constitute new series to be added. Set to 0 to force merging of data with
484             existing series of the same name. (Default: 1)
485              
486             =back
487              
488             Add one or more series of data to whatever is already collected. This can be used in place of
489             B, which is merely a useful front end for it.
490              
491             Example
492              
493             my $b = new PostScript::Graph::Bar();
494             $b->series_from_array(
495             [ [ March, 344, 327 ],
496             [ April, 489, 455 ],
497             [ May, 437, 430 ],
498             [ June, 369, 482 ], ],
499             { auto => ['red'] },
500             [ 'Joe', 'Jim' ],
501             );
502              
503             =cut
504              
505             sub series_from_file {
506 6     6 1 21 my ($o, $file, @rest) = @_;
507 6         40 my $filename = check_file($file);
508 6         2311 my @data;
509 6         220 my $csv = new Text::CSV_XS;
510 6 50       1131 open(INFILE, "<", $filename) or die "Unable to open \'$filename\': $!\nStopped";
511 6         192 while () {
512 74         112 chomp;
513 74         247 my $ok = $csv->parse($_);
514 74 50       1490 if ($ok) {
515 74         193 my @row = $csv->fields();
516 74 50       6858 push @data, [ @row ] if (@row);
517             }
518             }
519 6         108 close INFILE;
520              
521 6         42 $o->series_from_array( \@data, @rest );
522             }
523              
524             =head2 series_from_file( file [, style | labels | new_series ]... )
525              
526             Read in the named CSV file then pass it and any other arguments to B.
527              
528             =cut
529              
530             sub build_chart {
531 9     9 1 1975 my $o = shift;
532 9 100       53 if (@_) {
533 7 100       39 if(ref($_[0]) eq "ARRAY") {
534 1         5 $o->series_from_array(@_);
535             } else {
536 6         35 $o->series_from_file(@_);
537             }
538             }
539 9         55 my $oo = $o->{opt};
540            
541             ## Identify y axis range
542 9         20 my ($ymin, $ymax);
543 9         18 foreach my $label (@{$o->{labels}}) {
  9         26  
544 82         97 foreach my $y (@{$o->{data}{$label}}) {
  82         193  
545 356 100 100     1954 $ymin = $y if (not defined($ymin) or $y < $ymin);
546 356 100 100     1465 $ymax = $y if (not defined($ymax) or $y > $ymax);
547             }
548             }
549            
550             ## Find largest x label
551 9         22 my $xmaxlen = 0;
552 9         20 foreach my $label (@{$o->{labels}}) {
  9         26  
553 82         83 my $len = length($label);
554 82 100       185 $xmaxlen = $len if ($len > $xmaxlen);
555             }
556              
557             ## Find lagest series label for key
558 9         21 my $smaxlen = 0;
559 9         17 foreach my $series (@{$o->{series}}) {
  9         29  
560 204         265 my $len = length($series->[1]);
561 204 100       454 $smaxlen = $len if ($len > $smaxlen);
562             }
563            
564             ## Ensure PostScript::File exists
565 9 100       48 $oo->{file} = {} unless (defined $oo->{file});
566 9         33 my $of = $o->{opt}{file};
567 9 50       67 if (ref($of) eq "PostScript::File") {
568 0         0 $o->{ps} = $of;
569             } else {
570 9 100       57 $of->{left} = 36 unless (defined $of->{left});
571 9 100       50 $of->{right} = 36 unless (defined $of->{right});
572 9 100       62 $of->{top} = 36 unless (defined $of->{top});
573 9 100       74 $of->{bottom} = 36 unless (defined $of->{bottom});
574 9 100       44 $of->{errors} = 1 unless (defined $of->{errors});
575 9         871 $o->{ps} = new PostScript::File( $of );
576             }
577              
578             ## Calculate height of GraphPaper y axis
579             # used as max_height for GraphKey
580 9 100       3789 $oo->{layout} = {} unless (defined $oo->{layout});
581 9         32 my $oc = $o->{opt}{layout};
582 9         56 my @bbox = $o->{ps}->get_page_bounding_box();
583 9 100       151 my $bottom = defined($oc->{bottom_edge}) ? $oc->{bottom_edge} : $bbox[1]+1;
584 9 100       43 my $top = defined($oc->{top_edge}) ? $oc->{top_edge} : $bbox[3]-1;
585 9 50       67 my $spc = defined($oc->{spacing}) ? $oc->{spacing} : 0;
586 9         28 my $height = $top - $bottom - 2 * $spc;
587              
588             ## Ensure max_height and num_lines are set for GraphKey
589 9 100       61 $oo->{key} = {} unless (defined $oo->{key});
590 9         28 my $ok = $o->{opt}{key};
591 9 50       37 if (defined $ok->{max_height}) {
592 0 0       0 $ok->{max_height} = $height if ($ok->{max_height} > $height);
593             } else {
594 9         26 $ok->{max_height} = $height;
595             }
596 9         18 $ok->{num_items} = @{$o->{series}};
  9         29  
597 9 50       58 my $tsize = defined($ok->{text_size}) ? $ok->{text_size} : 10;
598 9         41 $ok->{text_width} = $smaxlen * $tsize * 0.7;
599 9         22 $ok->{icon_width} = $tsize;
600 9         21 $ok->{icon_height} = $tsize;
601 9         102 $o->{gk} = new PostScript::Graph::Key( $ok );
602            
603             ## Create GraphPaper now key width is known
604 9         23 my $ox = $oo->{x_axis};
605 9         18 $oo->{x_axis}{sub_divisions} = @{$o->{series}}+1;
  9         45  
606 9         20 $oo->{x_axis}{labels} = [ @{$o->{labels}}, "" ];
  9         46  
607 9 50       64 $oo->{y_axis}{low} = $ymin unless (defined $oo->{y_axis}{low});
608 9 100       48 $oo->{y_axis}{high} = $ymax unless (defined $oo->{y_axis}{high});;
609 9         34 $oo->{file} = $o->{ps};
610 9 50       71 $oc->{key_width} = $o->{key} ? $o->{gk}->width() : 0;
611 9         102 $o->{gp} = new PostScript::Graph::Paper( $oo );
612              
613             ## Build Key
614 9 50       62 if ($o->{key}) {
615 9         94 $o->{gk}->build_key( $o->{gp} );
616             } else {
617 0         0 $o->{gk}->ps_functions( $o->{ps} );
618             }
619 9         63100 $o->{ps}->add_to_page( <
620             gpaperdict begin
621             gstyledict begin
622             graphkeydict begin
623             END_INIT
624            
625             ## Add the bars
626 9         996 my $bar = 0;
627 9         23 my $laststyle = 0;
628 9         24 my $keydone = 0;
629 9         25 foreach my $label (@{$o->{labels}}) {
  9         40  
630             # draw one series
631 82         209 my $data = $o->{data}{$label};
632 82         102 my $i = 0;
633 82         104 foreach my $series (@{$o->{series}}) {
  82         177  
634 354         7980 my $style = $series->[0];
635 354         1395 $style->background( $o->{gp}->layout_background() );
636 354         1412 $style->write( $o->{ps} );
637 354         1986 $laststyle = $style;
638 354         731 my $y = $data->[$i++];
639 354         1663 my @bb = $o->{gp}->vertical_bar_area($bar++, $y);
640 354         1302 my $lwidth = $style->bar_outer_width()/2;
641 354         508 $bb[0] += $lwidth;
642 354         440 $bb[1] += $lwidth;
643 354         469 $bb[2] -= $lwidth;
644 354         420 $bb[3] -= $lwidth;
645 354         18699 $o->{ps}->add_to_page( <
646             $bb[0] $bb[1] $bb[2] $bb[3] bocolor bowidth drawbox
647             $bb[0] $bb[1] $bb[2] $bb[3] bicolor bicolor biwidth fillbox
648             END_BAR
649 354 100       99436 unless ($keydone) {
650 204         708 my $colour = str($style->bar_inner_color());
651 204 50       5615 $o->{gk}->add_key_item( $series->[1], <{key});
652             kix0 kiy0 kix1 kiy1 bocolor bowidth drawbox
653             kix0 kiy0 kix1 kiy1 bicolor bicolor biwidth fillbox
654             END_KEY_ITEM
655             }
656             }
657 82         462 $keydone = 1;
658 82         166 $bar++;
659             }
660              
661             ## Draw a line at y = 0
662 9         26 my $y;
663 9         70 my @gb = $o->{gp}->graph_area();
664 9         59 my $ylo = $o->{gp}->y_axis_low();
665 9         55 my $yhi = $o->{gp}->y_axis_high();
666 9         68 my $y0 = $o->{gp}->py(0);
667             CASE: {
668 9 100       21 if ($yhi <= 0) { $y = $gb[3]; last CASE; }
  9         58  
  1         3  
  1         4  
669 8 100 66     66 if ($ylo < 0 and $yhi > 0) { $y = $y0; last CASE; }
  3         5  
  3         11  
670 5 50       42 if ($ylo >= 0) { $y = $gb[1]; last CASE; }
  5         12  
  5         17  
671             }
672 9         109 $o->{ps}->add_to_page( <
673             newpath
674             gx0 $y moveto
675             gx1 $y lineto
676             yheavyc gpapercolor stroke
677             end end end
678             END_FINISHING
679             }
680              
681             =head2 build_chart([ data | file [, style | labels | new_series ]... ])
682              
683             The optional arguments are passed direct to B or B depending on whether the
684             first is an array ref. This just provides a convenient way of providing a single data set.
685              
686             If a PostScript::File object has not been given to B, it is created along with the
687             PostScript::Graph::Paper and PostScript::Graph::Key objects. The postscript code to draw these is generated with
688             bars and key entries superimposed.
689              
690             =head1 SUPPORTING METHODS
691              
692             =cut
693              
694             sub file {
695 0     0 1 0 return shift()->{ps};
696             }
697              
698             =head2 file
699              
700             Return the underlying PostScript::File object.
701              
702             =cut
703              
704             sub graph_key {
705 0     0 1 0 return shift()->{gk};
706             }
707              
708             =head2 graph_key
709              
710             Return the underlying PostScript::Graph::Key object. Only available after a call to B.
711              
712             =cut
713              
714             sub graph_paper {
715 0     0 1 0 return shift()->{gp};
716             }
717              
718             =head2 graph_paper
719              
720             Return the underlying PostScript::Graph::Paper object. Only available after a call to B.
721              
722             =cut
723              
724             sub sequence {
725 0     0 1 0 return shift()->{opt}{style}{sequence};
726             }
727              
728             =head2 sequence()
729              
730             Return the style sequence being used. This is only required when you wish to alter the ranges used by the auto
731             style feature.
732              
733             =cut
734              
735             sub output {
736 9     9 1 7796 shift()->{ps}->output(@_);
737             }
738              
739             =head2 output( file [, dir] )
740              
741             Output the chart as a file. See L.
742              
743             =cut
744              
745             sub newpage {
746 0     0 1   shift()->{ps}->newpage(@_);
747             }
748              
749             =head2 newpage( [page] )
750              
751             Start a new page in the underlying PostScript::File object. See L and
752             L.
753              
754             =cut
755              
756             sub add_function {
757 0     0 1   shift()->{ps}->add_function(@_);
758             }
759              
760             =head2 add_function( name, code )
761              
762             Add functions to the underlying PostScript::File object. See L for details.
763              
764             =cut
765              
766             sub add_to_page {
767 0     0 1   shift()->{ps}->add_to_page(@_);
768             }
769              
770             =head2 add_to_page( [page], code )
771              
772             Add postscript code to the underlying PostScript::File object. See L for details.
773              
774             =cut
775              
776             =head1 BUGS
777              
778             When reading from a CSV file, the first line is only recognized as a label line if both the first and SECOND
779             entries are unable to be read as a number. Putting quotes around them no longer works.
780              
781             =head1 AUTHOR
782              
783             Chris Willmot, chris@willmot.co.uk
784              
785             =head1 SEE ALSO
786              
787             L, L, L, L,
788             L, L.
789              
790             =cut
791              
792             1;