File Coverage

blib/lib/PostScript/Graph/Paper.pm
Criterion Covered Total %
statement 412 476 86.5
branch 180 250 72.0
condition 12 17 70.5
subroutine 76 104 73.0
pod 41 101 40.5
total 721 948 76.0


line stmt bran cond sub pod time code
1             package PostScript::Graph::Paper;
2             our $VERSION = 1.01;
3 17     17   268089 use strict;
  17         32  
  17         601  
4 17     17   90 use warnings;
  17         34  
  17         786  
5 17     17   1447 use PostScript::File 1.00 qw(check_file array_as_string str);
  17         42801  
  17         182514  
6              
7             # bit values for flags
8             our $fl_bar = 1;
9             our $fl_rotate = 2;
10             our $fl_center = 4;
11             our $fl_offset = 8;
12             our $fl_show = 16;
13              
14             =head1 NAME
15              
16             PostScript::Graph::Paper - prepare blank graph for a postscript file
17              
18             =head1 SYNOPSIS
19              
20             =head2 Simplest
21              
22             Let the module create its own postscript file:
23            
24             use PostScript::Graph::Paper;
25            
26             my $pg = new PostScript::Graph::Paper(
27             file => { landscape => 1 },
28             layout => { title => "Blank grid" } );
29            
30             $pg->output("testfile");
31              
32             =head2 Typical
33            
34             Add the chart to an existing postscript file:
35            
36             use PostScript::Graph::Paper;
37             use PostScript::File;
38            
39             my $ps = new PostScript::File(
40             left => 40,
41             right => 40,
42             top => 30,
43             bottom => 30,
44             landscape => 1,
45             errors => 1 );
46            
47             new PostScript::Graph::Paper(
48             file => $ps,
49             layout => { title =>
50             "Experimental results" },
51             x_axis => { high => 10,
52             title =>
53             "Control variable" },
54             y_axis => { low => 23.6,
55             high => 24.95,
56             title =>
57             "Dependent variable" });
58            
59             $ps->output("testfile");
60              
61             Create a bar chart layout:
62              
63             use PostScript::Graph::Paper;
64              
65             new PostScript::Graph::Paper(
66             layout => { title =>
67             "Survey" },
68             x_axis => { labels => [
69             "Men", "Women",
70             "Boys", "Girls", ], },
71             y_axis => { low => 8,
72             high => 37, } );
73            
74             $ps->output("testfile");
75              
76             =head2 All options
77            
78             new PostScript::Graph::Paper(
79             file => $ps_file,
80            
81             layout => {
82             bottom_edge => 30,
83             top_edge => 30,
84             left_edge => 30,
85             right_edge => 30,
86             spacing => 4,
87             top_margin => 10,
88             right_margin => 10,
89             key_width => 0,
90             sub_divisions => 4,
91             dots_per_inch => 600,
92             font => 'Helvetica',
93             font_color => 0,
94             font_size => 10,
95             heading => 'My Graph',
96             heading_font => 'Times-Bold',
97             heading_font_color => 0.9,
98             heading_font_size => 20,
99             heading_height => 30,
100             background => [ 0.9, 0.95, 0.85 ],
101             color => [ 0, 0, 0.7 ],
102             heavy_color => [0, 0, 0.4],
103             mid_color => [0.6, 0.6, 1],
104             light_color => 0.8,
105             heavy_width => 1,
106             mid_width => 0.8,
107             light_width => 0.25,
108             no_drawing => 0,
109             },
110              
111             x_axis => {
112             low => 74.25,
113             high => 74.9,
114             width => 200,
115             height => 450,
116             label_gap => 50,
117             labels => [qw(this that other)],
118             labels_req => 7,
119             font => 'Helvetica',
120             font_color => 0,
121             font_size => 10,
122             title => 'X axis',
123             color => 0.5,
124             heavy_color => [0, 0, 0.4],
125             mid_color => [0.6, 0.6, 1],
126             light_color => 0.8,
127             heavy_width => 1,
128             mid_width => 0.8,
129             light_width => 0.25,
130             mark_min => 2,
131             mark_max => 8,
132             smallest => 8,
133             center => 1,
134             offset => 1,
135             rotate => 1,
136             draw_fn => "myxdraw",
137             },
138              
139             y_axis => {
140             # as x_axis
141             },
142             );
143              
144             =head1 DESCRIPTION
145              
146             This module is designed as a supporting part of the PostScript::Graph suite. For top level modules that output
147             something useful, see
148              
149             PostScript::Graph::Bar
150             PostScript::Graph::Stock
151             PostScript::Graph::XY
152              
153             An area of graph paper is created on a postscript page. X and Y axes are labelled and there are facilities to add
154             a title and key. This is written to a PostScript::File object (automatically created if not supplied)
155             which can then be output. It is intended to be a static object - once the parameters are set there is little
156             point in changing them - so all options are set in the contructor.
157            
158             =head1 CONSTRUCTOR
159              
160             =cut
161              
162             sub new {
163 17     17 1 2479 my $class = shift;
164 17         51 my $opt = {};
165 17 100       126 if (@_ == 1) {
166 12         31 $opt = $_[0];
167             } else {
168 5         25 %$opt = @_;
169             }
170            
171 17         47 my $o = {};
172 17         55 bless( $o, $class );
173            
174             ## note or initialize PostScript::File object
175 17 100       120 if (ref($opt->{file}) eq "PostScript::File") {
176 13         95 $o->{ps} = $opt->{file};
177             } else {
178 4 100       19 my $fileopts = (defined $opt->{file}) ? $opt->{file} : {};
179 4 100       22 $fileopts->{left} = 36 unless (defined $fileopts->{left});
180 4 100       27 $fileopts->{right} = 36 unless (defined $fileopts->{right});
181 4 100       38 $fileopts->{top} = 36 unless (defined $fileopts->{top});
182 4 100       21 $fileopts->{bottom} = 36 unless (defined $fileopts->{bottom});
183 4 100       20 $fileopts->{errors} = 1 unless (defined $fileopts->{errors});
184 4         40 $o->{ps} = new PostScript::File($fileopts);
185             }
186              
187             ## handle options
188 17         1478 $o->init_layout($opt);
189 17         90 $o->init_scale_options("x", $opt->{x_axis});
190 17         80 $o->init_scale_options("y", $opt->{y_axis});
191            
192 17 100       86 if (defined $o->{x}{labels}) {
193 11         61 $o->init_bars("x", $opt->{x_axis});
194             } else {
195 6         37 $o->init_scale("x", $opt->{x_axis});
196             }
197 17 100       76 if (defined $o->{y}{labels}) {
198 1         9 $o->init_bars("y", $opt->{y_axis});
199             } else {
200 16         90 $o->init_scale("y", $opt->{y_axis});
201             }
202            
203 17         120 PostScript::Graph::Paper->ps_functions($o->{ps});
204 17 50       2896675 $o->draw_scales() unless ($opt->{layout}{no_drawing});
205            
206 17         28154 return $o;
207             }
208              
209             =head2 new( [options] )
210              
211             The labelling and layout of the graph is quite flexible, but that level of control inevitably requires many
212             options. If no options are given, graph paper labelled 0 to 100 along each axis fills an A4 page (apart from
213             a half-inch border all round). It is up to the user how much this is altered. Either labels or high and low
214             values will probably need to be given for each axis, with titles, a heading and perhaps some space for
215             a key.
216              
217             C can either be a list of hash keys and values or a hash reference. In either case, the hash is expected
218             to have the same structure. There are a few primary keys, each of which point to sub-hashes which hold options for
219             that group.
220              
221             For every option listed here there is a corresponding function returning its value. For example, the label
222             printed at the top of the y axis is set with the option C { title => '...' }>. C would
223             return the string given and the option would be documented as C.
224              
225             Example 1
226              
227             my $gp = new PostScript::Graph::Paper(
228             layout => {
229             title => "Bar chart",
230             right_edge => 500,
231             key_width => 100,
232             },
233             x_axis => {
234             labels => [ "First bar",
235             "Second bar",
236             "Third bar" ],
237             },
238             y_axis => {
239             low => 123,
240             high => 456.7,
241             title => "Readings",
242             },
243             );
244              
245             This would prepare graph paper for a
246             bar chart with 3 vertical bars and a key.
247              
248             Example 2
249            
250             my $gp = new PostScript::Graph::Paper(
251             file => {
252             landscape => 1,
253             errors => 1,
254             },
255             layout => {
256             font_color => 1,
257             heading_height => 0,
258             left_axis_font_size => 0,
259             bottom_axis_height => 0,
260             left_axis_width => 0,
261             mark_min => 0,
262             mark_max => 0,
263             },
264             x_axis => {
265             smallest => 72,
266             },
267             y_axis => {
268             smallest => 72,
269             },
270             );
271            
272             This fills an A4 page with a plain grid of
273             squares no smaller than 1 inch big, with
274             no axes, marks, labels, heading or key.
275              
276             =cut
277              
278             sub file {
279 13     13 1 77 return shift()->{ps};
280             }
281              
282             =head2 PostScript Options
283              
284             The PostScript::File object which recieves the grid may either be an existing one or the module can create one for
285             you. Use C to declare a pre-existing object, or C to control how the new one is created.
286              
287             =head3 file
288              
289             This may be either a PostScript::File object or a options in hash key/value format. If options are given, a new
290             PostScript::File object is created.
291              
292             Example 1
293              
294             $psf = new PostScript::File();
295            
296             $pg = new PostScript::Graph::Paper(
297             file => $psf );
298              
299             Then $psf == $pg->file();
300              
301             Example 2
302            
303             my $ch = new PostScript::Graph::Paper(
304             file => {
305             landscape => 1,
306             clipping => 1,
307             clipcmd => "stroke",
308             debug => 2,
309             errors => 1,
310             } );
311              
312             =cut
313              
314             ### Chart options
315              
316 2     2 1 82484 sub layout_left_edge { shift()->{ch}{left}; }
317 2     2 1 13 sub layout_bottom_edge { shift()->{ch}{bottom}; }
318 2     2 1 14 sub layout_right_edge { shift()->{ch}{right}; }
319 2     2 1 14 sub layout_top_edge { shift()->{ch}{top}; }
320 2     2 1 15 sub layout_right_margin { shift()->{ch}{rmargin}; }
321 2     2 1 12 sub layout_top_margin { shift()->{ch}{tmargin}; }
322 2     2 1 12 sub layout_spacing { shift()->{ch}{spc}; }
323 2     2 1 15 sub layout_dots_per_inch { shift()->{ch}{dpi}; }
324 2     2 1 13 sub layout_heading { shift()->{ch}{title}; }
325 2     2 1 13 sub layout_heading_height { shift()->{ch}{head}; }
326 3     3 1 471 sub layout_key_width { shift()->{ch}{keyw}; }
327 372     372 1 1188 sub layout_background { color_as_array( shift()->{ch}{bgnd} ); }
328 2     2 1 13 sub layout_color { color_as_array( shift()->{ch}{color} ); }
329 2     2 1 18 sub layout_heavy_color { color_as_array( shift()->{ch}{heavycol} ); }
330 2     2 1 12 sub layout_mid_color { color_as_array( shift()->{ch}{midcol} ); }
331 2     2 1 12 sub layout_light_color { color_as_array( shift()->{ch}{lightcol} ); }
332 2     2 1 11 sub layout_heavy_width { shift()->{ch}{heavyw}; }
333 2     2 1 17 sub layout_mid_width { shift()->{ch}{midw}; }
334 2     2 1 13 sub layout_light_width { shift()->{ch}{lightw}; }
335 2     2 1 14 sub layout_font { shift()->{ch}{font}; }
336 2     2 1 14 sub layout_font_size { shift()->{ch}{fontsize}; }
337 2     2 1 12 sub layout_font_color { color_as_array( shift()->{ch}{fontcol} ); }
338 2     2 1 13 sub layout_heading_font { shift()->{ch}{hfont}; }
339 2     2 1 13 sub layout_heading_font_size { shift()->{ch}{hsize}; }
340 2     2 1 9 sub layout_heading_font_color { color_as_array( shift()->{ch}{hcol} ); }
341              
342             =head2 Chart Options
343              
344             These are all set within a C option given to the constructor. Remove the initial C to get the
345             option name. All values are in PostScript native units (72 = 1 inch).
346              
347             Example
348              
349             $pg = new PostScript::Graph::Paper(
350             layout => { right_edge => 600,
351             heavy_color => [0, 0, 0.8],
352             light_color => 0.6,
353             font => "Courier",
354             title_font_size => 14,
355             right_margin => 20,
356             spacing => 4 } );
357              
358             $pg->layout_font() would return "Courier".
359            
360             =head3 layout_bottom_edge
361              
362             The bottom boundary of the whole chart area.
363              
364             =head3 layout_background
365              
366             Background color.
367              
368             =head3 layout_color
369              
370             Default colour for all grid lines. All colours can be either a greyscale value or an array of RGB values. All
371             values vary from 0 = black to 1 = brightest. (Default: 0.5)
372              
373             Example
374              
375             layout => { background => [ 0.95, 0.95, 0.85 ],
376             color => [ 0, 0.2, 0.8 ],
377             light_color => 0.85 }
378              
379             Grid lines will be a blue shade on a beige background,
380             except the lightest lines which will be light grey.
381              
382             =head3 layout_dots_per_inch
383              
384             Marks are spaced at a multiple of this value. If this does not match the physical output device, the appearance
385             can be somewhat ragged. (Default: 300)
386              
387             =head3 layout_font
388              
389             Default font for everything except titles. (Default: "Helvetica")
390              
391             =head3 layout_font_color
392              
393             Default colour for all fonts. (Default: 0)
394              
395             =head3 layout_font_size
396              
397             Default font size for everything except the title font. (Default: 10)
398              
399             =head3 layout_heading
400              
401             The title above the grid. (Default: "")
402              
403             =head3 layout_heading_font
404              
405             Font for the main heading above the graph. (Default: "Helvetica-Bold")
406              
407             =head3 layout_heading_font_color
408              
409             Colour for main heading. (Defaults to C)
410              
411             =head3 layout_heading_font_size
412              
413             Size for main heading. (Default: 12)
414              
415             =head3 layout_heading_height
416              
417             Size of area above the graph holding the main title and the y axis title. (Defaults to just enough space)
418              
419             =head3 layout_heavy_color
420              
421             The colour of the major, labelled, lines. (Defaults to C)
422              
423             =head3 layout_heavy_width
424              
425             Width of the labelled lines. (Default: 0.75)
426              
427             =head3 layout_key_width
428              
429             Width of box at the right of the graph, allocated for the key. If this is 0, no key box is drawn. (Default: 0)
430              
431             The key is drawn by a seperate PostScript::Graph::Key object. This merely allocates space within the chart edges.
432              
433             =head3 layout_left_edge
434              
435             The left boundary of the whole paper area.
436              
437             =head3 layout_light_color
438              
439             Colour of the minor, unlabelled, lines. (Defaults to C)
440              
441             =head3 layout_light_width
442              
443             Width of the lightest lines. (Default: 0.25)
444              
445             =head3 layout_mid_color
446              
447             A scale of 10 will be divided into two lots of 5 seperated by a slightly heavier line at the 5 mark. This is the
448             'mid' line. (Defaults to C)
449              
450             =head3 layout_mid_width
451              
452             Width of the mid-lines, see . (Default: 0.75)
453              
454             =head3 no_drawing
455              
456             If true, the call to C is not carried out in the constructor, allowing some tinkering with labels
457             etc. before comitting to postscript. The only way to do this is to access the objects data directly. Use with
458             caution. (Default: 0)
459              
460             =head3 layout_right_edge
461              
462             The right boundary of the whole chart area.
463              
464             =head3 layout_right_margin
465              
466             Space at the right hand side of the graph area, taken up by part of the last label. (Default: 15)
467              
468             =head3 layout_spacing
469              
470             Increasing this value seperates out the various parts of the chart, like leading added to text. (Default: 0)
471              
472             =head3 layout_sub_divisons
473              
474             Used by PostScript::Graph::Bar to signal the number of series per label. Not appropriate for anything else.
475              
476             =head3 layout_top_edge
477              
478             The top boundary of the whole chart area.
479              
480             =head3 layout_top_margin
481              
482             Space above the graph area taken up by part of the topmost y label. (Default: 5)
483              
484             =cut
485              
486             sub init_layout {
487 17     17 0 47 my ($o, $opt) = @_;
488 17 100       107 $opt->{layout} = {} unless (defined $opt->{layout});
489 17         44 my $r = $opt->{layout};
490 17         68 $o->{ch}{left} = 0;
491 17         41 my $ch = $o->{ch};
492              
493 17         43 my $ps = $o->{ps};
494 17         193 my @bbox = $ps->get_page_bounding_box();
495 17 100       279 $ch->{left} = defined($r->{left_edge}) ? $r->{left_edge} : $bbox[0]+1;
496 17 100       85 $ch->{bottom} = defined($r->{bottom_edge}) ? $r->{bottom_edge} : $bbox[1]+1;
497 17 100       179 $ch->{right} = defined($r->{right_edge}) ? $r->{right_edge} : $bbox[2]-1;
498 17 100       99 $ch->{top} = defined($r->{top_edge}) ? $r->{top_edge} : $bbox[3]-1;
499 17 50       78 $ch->{tmargin} = defined($r->{top_margin}) ? $r->{top_margin} : 5;
500 17 50       77 $ch->{rmargin} = defined($r->{right_margin}) ? $r->{right_margin} : 15;
501 17 100       113 $ch->{bottom} = defined($r->{bottom_edge}) ? $r->{bottom_edge} : $bbox[1]+1;
502 17 50       141 $ch->{spc} = defined($r->{spacing}) ? $r->{spacing} : 0;
503 17 100       99 $ch->{dpi} = defined($r->{dots_per_inch}) ? $r->{dots_per_inch} : 300;
504            
505 17 50       88 $ch->{color} = defined($r->{color}) ? str($r->{color}) : 0.5;
506 17 50       87 $ch->{fgnd} = defined($r->{outline}) ? str($r->{outline}) : 0;
507 17 100       194 $ch->{bgnd} = defined($r->{background}) ? str($r->{background}) : 1;
508 17 100       210 $ch->{heavycol} = defined($r->{heavy_color}) ? str($r->{heavy_color}) : $ch->{color};
509 17 100       282 $ch->{midcol} = defined($r->{mid_color}) ? str($r->{mid_color}) : $ch->{color};
510 17 50       172 $ch->{lightcol} = defined($r->{light_color}) ? str($r->{light_color}) : $ch->{color};
511 17 50       77 $ch->{heavyw} = defined($r->{heavy_width}) ? str($r->{heavy_width}) : 0.75;
512 17 50       131 $ch->{midw} = defined($r->{mid_width}) ? $r->{mid_width} : 0.5;
513 17 50       127 $ch->{lightw} = defined($r->{light_width}) ? $r->{light_width} : 0.25;
514              
515 17 50       105 $ch->{font} = defined($r->{font}) ? $r->{font} : "Helvetica";
516 17 50       93 $ch->{fontsize} = defined($r->{font_size}) ? $r->{font_size} : 10;
517 17 50       142 $ch->{fontcol} = defined($r->{font_color}) ? str($r->{font_color}) : 0;
518 17 100       183 $ch->{hfont} = defined($r->{heading_font}) ? $r->{heading_font} : "Helvetica-Bold";
519 17 100       80 $ch->{hsize} = defined($r->{heading_font_size}) ? $r->{heading_font_size} : 12;
520 17 100       269 $ch->{hcol} = defined($r->{heading_font_color}) ? str($r->{heading_font_color}) : $ch->{fontcol};
521 17 100       107 $ch->{title} = defined($r->{heading}) ? $r->{heading} : "";
522              
523 17         111 $o->init_scale_sizes("y", $opt->{"y_axis"});
524            
525             # both y axis and key block are full height
526 17         51 $ch->{yx0} = $ch->{left} + $ch->{spc};
527 17         63 $ch->{yx1} = $ch->{yx0} + $o->{y}{width};
528 17         55 $ch->{yy0} = $ch->{bottom} + $ch->{spc};
529 17         261 $ch->{yy1} = $ch->{top} - $ch->{spc};
530            
531 17 100       80 $ch->{keyw} = defined($r->{key_width}) ? $r->{key_width} : 0;
532            
533             # heading and x axis fit within side borders
534 17         80 $o->init_scale_sizes("x", $opt->{"x_axis"}); # x width depends on y width and key width
535            
536 17 50       91 $ch->{head} = defined($r->{heading_height}) ? $r->{heading_height} : $ch->{hsize};
537 17         66 $ch->{head} += 1.5 * $o->{y}{fsize}; # y label goes in heading space
538 17         61 $ch->{hx0} = $ch->{yx1};
539 17         94 $ch->{hx1} = $ch->{yx1} + $o->{x}{width};
540 17         79 $ch->{hy1} = $ch->{top} - $ch->{spc};
541 17         117 $ch->{hy0} = $ch->{hy1} - $ch->{head} - $ch->{spc};
542              
543 17         48 $ch->{xx0} = $ch->{yx1};
544 17         56 $ch->{xx1} = $ch->{hx1};
545 17         51 $ch->{xy0} = $ch->{bottom} + $ch->{spc};
546 17         86 $ch->{xy1} = $ch->{xy0} + $o->{x}{height};
547            
548             # graph area
549 17         40 $ch->{gx0} = $ch->{xx0};
550 17         45 $ch->{gy0} = $ch->{xy1};
551 17         57 $ch->{gx1} = $ch->{xx1};
552 17         83 $ch->{gy1} = $ch->{hy0} - $ch->{tmargin} - $ch->{spc};
553             }
554             # Internal method, intializing whole chart area
555              
556             ### Axis options
557              
558 0     0 0 0 sub x_axis_color { color_as_array( shift()->{x}{color} ); }
559 2     2 0 12 sub x_axis_low { shift()->{x}{llo}; }
560 2     2 0 12 sub x_axis_high { shift()->{x}{lhi}; }
561 2     2 0 11 sub x_axis_width { shift()->{x}{width}; }
562 2     2 0 12 sub x_axis_height { shift()->{x}{height}; }
563 2     2 0 11 sub x_axis_label_gap { shift()->{x}{labelgap}; }
564 0     0 0 0 sub x_axis_si_shift { shift()->{x}{si}; }
565 2     2 0 14 sub x_axis_smallest { shift()->{x}{smallest}; }
566 2     2 0 14 sub x_axis_title { shift()->{x}{title}; }
567 2     2 0 12 sub x_axis_font { shift()->{x}{font}; }
568 2     2 0 12 sub x_axis_font_color { shift()->{x}{fcol}; }
569 2     2 0 18 sub x_axis_font_size { shift()->{x}{fsize}; }
570 0     0 0 0 sub x_axis_heavy_color { color_as_array( shift()->{x}{heavycol} ); }
571 0     0 0 0 sub x_axis_mid_color { color_as_array( shift()->{x}{midcol} ); }
572 0     0 0 0 sub x_axis_light_color { color_as_array( shift()->{x}{lightcol} ); }
573 0     0 0 0 sub x_axis_heavy_width { shift()->{x}{heavyw}; }
574 0     0 0 0 sub x_axis_mid_width { shift()->{x}{midw}; }
575 0     0 0 0 sub x_axis_light_width { shift()->{x}{lightw}; }
576 2     2 0 13 sub x_axis_mark_min { shift()->{x}{markmin}; }
577 2     2 0 13 sub x_axis_mark_max { shift()->{x}{markmax}; }
578 0     0 0 0 sub x_axis_mark_gap { shift()->{x}{markgap}; }
579 2     2 0 12 sub x_axis_labels_req { shift()->{x}{labsreq}; }
580 2     2 0 14 sub x_axis_rotate { shift()->{x}{rotate} != 0; }
581 2     2 0 28 sub x_axis_center { shift()->{x}{center} != 0; }
582 2     2 0 13 sub x_axis_show_lines { shift()->{x}{show}; }
583 0     0 0 0 sub y_axis_color { color_as_array( shift()->{y}{color} ); }
584 11     11 0 73 sub y_axis_low { shift()->{y}{llo}; }
585 11     11 0 68 sub y_axis_high { shift()->{y}{lhi}; }
586 2     2 0 14 sub y_axis_width { shift()->{y}{width}; }
587 2     2 0 15 sub y_axis_height { shift()->{y}{height}; }
588 2     2 0 14 sub y_axis_label_gap { shift()->{y}{labelgap}; }
589 0     0 0 0 sub y_axis_si_shift { shift()->{y}{si}; }
590 2     2 0 14 sub y_axis_smallest { shift()->{y}{smallest}; }
591 2     2 0 21 sub y_axis_title { shift()->{y}{title}; }
592 2     2 0 13 sub y_axis_font { shift()->{y}{font}; }
593 2     2 0 16 sub y_axis_font_color { shift()->{y}{fcol}; }
594 2     2 0 13 sub y_axis_font_size { shift()->{y}{fsize}; }
595 0     0 0 0 sub y_axis_heavy_color { color_as_array( shift()->{y}{heavycol} ); }
596 0     0 0 0 sub y_axis_mid_color { color_as_array( shift()->{y}{midcol} ); }
597 0     0 0 0 sub y_axis_light_color { color_as_array( shift()->{y}{lightcol} ); }
598 0     0 0 0 sub y_axis_heavy_width { shift()->{y}{heavyw}; }
599 0     0 0 0 sub y_axis_mid_width { shift()->{y}{midw}; }
600 0     0 0 0 sub y_axis_light_width { shift()->{y}{lightw}; }
601 2     2 0 13 sub y_axis_mark_min { shift()->{y}{markmin}; }
602 2     2 0 14 sub y_axis_mark_max { shift()->{y}{markmax}; }
603 0     0 0 0 sub y_axis_mark_gap { shift()->{y}{markgap}; }
604 2     2 0 13 sub y_axis_labels_req { shift()->{y}{labsreq}; }
605 2     2 0 13 sub y_axis_rotate { shift()->{y}{rotate} != 0; }
606 2     2 0 13 sub y_axis_center { shift()->{y}{center} != 0; }
607 2     2 0 13 sub y_axis_show_lines { shift()->{y}{show}; }
608              
609             =head2 Axis Options
610              
611             The C entries below refer to four things: x_axis and y_axis options and x_axis_ and y_axis_ functions
612             which return those values. Remove the C prefix to get the option name, and prepend C or C to get
613             the relevant function name. The options belong within hashes indexed by either C or C.
614              
615             Example
616              
617             Options documentated as:
618              
619             axis_low
620             axis_high
621            
622             Would be set by:
623            
624             $pg = new PostScript::Graph::Paper(
625             x_axis => { low => 1,
626             high => 12,
627             },
628             y_axis => { low => 247,
629             high => 980,
630             } );
631              
632             And inspected by:
633            
634             $pg->x_axis_low() == 1
635             $pg->x_axis_high() == 14
636             $pg->y_axis_low() == 200
637             $pg->y_axis_high() == 1000
638              
639             Note that the original values have been
640             adjusted as the scales were calculated.
641              
642             =head3 axis_center
643              
644             By default, any labels given to C are placed centrally between the lines. Setting this to 0 puts the
645             labels in the normal 'number' position, next to the major lines.
646              
647             =head3 axis_color
648              
649             Colour for grid lines on one axis. See L. (Defaults to C).
650              
651             =head3 axis_draw_fn
652              
653             The string given here should be the name of a PostScript function which will draw the axis, lines and labels. See
654             the code for the C and C functions which provide the defaults.
655              
656             =head3 axis_font
657              
658             Font for labels and title on the axis. (Defaults to C)
659              
660             =head3 axis_font_color
661              
662             Colour for axis title and labels. (Defaults to C)
663              
664             =head3 axis_font_size
665              
666             Size for title and labels on the axis. (Defaults to C)
667              
668             =head3 axis_heavy_color
669              
670             The colour of the major, labelled, lines. (Defaults to C)
671              
672             =head3 axis_heavy_width
673              
674             Width of the labelled lines. (Defaults to C)
675              
676             =head3 axis_height
677              
678             For x: space beneath the x axis. (Defaults to just enough space for the labels and x axis title)
679              
680             For y: should not be changed. (Defaults to full height of chart area, baring top and bottom space)
681              
682             =head3 axis_high
683              
684             The highest number required to appear on the axis. This will be rounded up to suit the chosen scale. (Default:
685             100)
686              
687             =head3 axis_label_gap
688              
689             The space between the start of each label. The effect is for the program to choose more or fewer labels on the
690             x axis. Although available to the y axis, the spacing between labels is rarely an issue. (Default: 30)
691              
692             =head3 axis_labels
693              
694             This should be a reference to a list of strings. If a list of labels is provided, the axes uses these, ignoring
695             C and C.
696              
697             The functions C and C are unusual in that they set as well as return their value.
698             Note that any alterations made after C and before C, must have all strings enclosed in '()' for
699             postscript. The number of labels must NOT be changed.
700              
701             =head3 axis_labels_req
702              
703             An indication of the number of major (labelled) marks wanted along the axis. The program overrides this if it is
704             not suitable. (Default derived from C)
705              
706             =head3 axis_light_color
707              
708             Colour of the minor, unlabelled, lines. (Defaults to C)
709              
710             =head3 axis_light_width
711              
712             Width of the lightest lines. (Defaults to C)
713              
714             =head3 axis_low
715              
716             The lowest number required to appear on the axis. This will be rounded down to suit the chosen scale. (Default:
717             0)
718              
719             =head3 axis_mid_color
720              
721             A scale of 10 will be divided into two lots of 5 seperated by a slightly heavier line at the 5 mark. This is the
722             'mid' line. (Defaults to C)
723              
724             =head3 axis_mid_width
725              
726             Width of the mid-lines, see . (Defaults to C)
727              
728             =head3 axis_mark_gap
729              
730             The gap between smallest marks. This is a calculated value and cannot be set, although it may be controlled with
731             B.
732              
733             =head3 axis_mark_min
734              
735             The smallest mark on the axis. (Defaults to C)
736              
737             =head3 axis_mark_max
738              
739             The tallest mark on the axis. (defaults to C)
740              
741             =head3 axis_rotate
742              
743             Setting this to 1 rotates the axis labels 90 degrees right. (Defaults to 1 on the x axis when labels are
744             provided, 0 otherwise)
745              
746             =head3 axis_smallest
747              
748             This is the smallest allowable gap between axis marks. Setting this controls how many subdivisions the program
749             generates. It would be wise to set this as a multiple of C. (Defaults to 3 dots)
750              
751             =head3 axis_si_shift
752              
753             The number of 0's removed at a time when adjusting the axis labels, e.g. 3 for thousands, 2 for hundreds or 0 for
754             no adjustment. (Default: 3)
755              
756             =head3 axis_title
757              
758             The text printed at the top of the y axis and below the right of the x axis. (Default: "")
759              
760             =head3 axis_width
761              
762             For x: should not be changed. (Defaults to width between y axis and key area)
763              
764             For y: width allocated for y axis marks and labels. (Default: 36)
765              
766             =cut
767              
768             sub x_axis_labels {
769 0     0 0 0 my ($o, $ar) = @_;
770 0 0       0 $o->{x}{labels} = $ar if (defined $ar);
771 0         0 return $o->{x}{labels};
772             }
773              
774             sub y_axis_labels {
775 0     0 0 0 my ($o, $ar) = @_;
776 0 0       0 $o->{y}{labels} = $ar if (defined $ar);
777 0         0 return $o->{y}{labels};
778             }
779              
780             sub init_scale_sizes {
781 34     34 0 97 my ($o, $axis, $r) = @_;
782 34 100       103 $r = {} unless (defined $r);
783 34 50       188 $o->{$axis}{markmin} = 0 unless (defined $o->{$axis}{markmin});
784 34         66 my $sc = $o->{$axis};
785 34         60 my $ch = $o->{ch};
786 34 50       224 $r = {} unless (defined $r);
787            
788 34 50       150 $sc->{markmin} = defined($r->{mark_min}) ? $r->{mark_min} : 0.5;
789 34 50       170 $sc->{markmax} = defined($r->{mark_max}) ? $r->{mark_max} : 8;
790 34 50       125 $sc->{font} = defined($r->{font}) ? $r->{font} : $ch->{font};
791 34 50       180 $sc->{fsize} = defined($r->{font_size}) ? $r->{font_size} : $ch->{fontsize};
792 34 50       121 $sc->{fcol} = defined($r->{font_color}) ? str($r->{font_color}) : $ch->{fontcol};
793              
794 34         72 $sc->{labels} = $r->{labels};
795 34 100       96 my $bar = defined($r->{labels}) ? 1 : 0;
796 34 100       103 my $offset = defined($r->{offset}) ? ($r->{offset} != 0) : 0;
797 34 100       166 $sc->{offset} = $bar ? $offset : 0;
798 34 100       166 $sc->{rotate} = defined($r->{rotate}) ? ($r->{rotate} != 0) : $bar;
799 34 100       174 $sc->{center} = defined($r->{center}) ? ($r->{center} != 0) : $bar;
800 34 100       225 $sc->{show} = defined($r->{show_lines}) ? ($r->{show_lines}) : not $bar;
801 34         102 $sc->{flags} = $bar * $fl_bar;
802 34         93 $sc->{flags} |= $sc->{rotate} * $fl_rotate;
803 34         80 $sc->{flags} |= $sc->{center} * $fl_center;
804 34         71 $sc->{flags} |= $sc->{offset} * $fl_offset;
805 34         181 $sc->{flags} |= $sc->{show} * $fl_show;
806             #warn sprintf '%s axis flags=%o, bar=%o, show_lines=%o%s', $axis, $sc->{flags}, $bar, $r->{show_lines} || 0,"\n";
807             #warn "rotate=$sc->{rotate}, center=$sc->{center}, offset=$sc->{offset}, show=$sc->{show}\n";
808            
809 34         51 my $maxlen = 0;
810 34 100       2089 if (defined $sc->{labels}) {
811 12         22 foreach my $label (@{$sc->{labels}}) {
  12         40  
812 114         132 my $len = length($label);
813 114 100       235 $maxlen = $len if ($len > $maxlen);
814             }
815             }
816 34         58 my ($width, $height);
817 34 100       1238 if ($axis eq "x") {
    50          
818 17         85 $width = $ch->{right} - 1 - $ch->{keyw} - $ch->{rmargin} - $ch->{yx1};
819 17 100 66     1091 if (defined($sc->{labels}) and ($sc->{flags} & 1 == 1)) {
820 11 50       44 my $ratio = defined $r->{glyph_ratio} ? $r->{glyph_ratio} : 0.5;
821 11         45 $height = $sc->{markmax} + (1 + $maxlen * $ratio) * $sc->{fsize};
822             } else {
823 6         31 $height = $sc->{markmax} + 2.5 * $sc->{fsize};
824             }
825             } elsif ($axis eq "y") {
826 17 50 66     104 if (defined($sc->{labels}) and ($sc->{flags} & 1 == 0)) {
827 0         0 $width = $sc->{markmax} + $maxlen * 0.8 * $sc->{fsize};
828             } else {
829 17         43 $width = 30;
830             }
831 17         91 $height = $ch->{top} - $ch->{bottom} - 2 * $ch->{spc};
832             }
833 34 50       144 $sc->{width} = defined($r->{width}) ? $r->{width} : $width;
834 34 100       140 $sc->{height} = defined($r->{height}) ? $r->{height} : $height;
835             }
836             # Internal method, setting axis sizes required for chart dimensions
837             # Requires layout fonts to have been initialized
838             # Called from within initlayout, before all other axis inits
839              
840             sub init_scale_options {
841 34     34 0 91 my ($o, $axis, $r) = @_;
842 34         93 $o->{$axis}{llo} = 0;
843 34         66 my $sc = $o->{$axis};
844 34         59 my $ch = $o->{ch};
845 34 100       111 $r = {} unless (defined $r);
846            
847             # collect options and set defaults
848 34 50 33     135 undef $r->{label_gap} if defined($r->{label_gap}) and ($r->{label_gap} <= 0);
849 34 100       122 $sc->{llo} = defined($r->{low}) ? $r->{low} : 0;
850 34 100       119 $sc->{lhi} = defined($r->{high}) ? $r->{high} : 100;
851 34 50       171 $sc->{labelgap} = defined($r->{label_gap}) ? $r->{label_gap} : 30; # gap between labels
852 34 100       148 $sc->{smallest} = defined($r->{smallest}) ? $r->{smallest} : 3 * 72/$ch->{dpi}; # 3 dots
853 34 100 50     243 $sc->{title} = defined($r->{title}) ? $r->{title} : ($sc->{title} || "");
854 34 50       123 $sc->{si} = defined($r->{si_shift}) ? $r->{si_shift} : 3;
855            
856 34         78 my $x = ($axis eq "x");
857 34         65 my $y = ($axis eq "y");
858 34 100       125 if ($x) {
    50          
859 17         46 $sc->{phi} = $ch->{gx1};
860 17         94 $sc->{plo} = $ch->{gx0};
861 17 50       83 $sc->{draw} = defined($r->{draw_fn}) ? $r->{draw_fn} : "xdraw";
862             } elsif ($y) {
863 17         47 $sc->{phi} = $ch->{gy1};
864 17         53 $sc->{plo} = $ch->{gy0};
865 17 50       120 $sc->{draw} = defined($r->{draw_fn}) ? $r->{draw_fn} : "ydraw";
866             } else {
867 0         0 die "init_scale(): axis not x or y\nStopped";
868             }
869              
870 34         181 my $bar = (($sc->{flags} & $fl_bar) == $fl_bar);
871 34         91 my $show = (($sc->{flags} & $fl_show) == $fl_show);
872 34 100 100     199 if ($x and $bar) {
873             #print "using background\n";
874 11 100       47 if ($show) {
875 3 50       13 $sc->{heavycol} = defined($r->{heavy_color}) ? str($r->{heavy_color}) : $ch->{heavycol};
876 3 50       15 $sc->{midcol} = defined($r->{mid_color}) ? str($r->{mid_color}) : $ch->{midcol};
877             } else {
878 8         26 $sc->{heavycol} = $ch->{bgnd};
879 8         22 $sc->{midcol} = $ch->{bgnd};
880             }
881 11 50       67 $sc->{lightcol} = defined($r->{light_color}) ? str($r->{light_color}) : $ch->{bgnd};
882             } else {
883             #print "using colours\n";
884 23 50       196 $sc->{heavycol} = defined($r->{heavy_color}) ? str($r->{heavy_color}) : $ch->{heavycol};
885 23 50       125 $sc->{midcol} = defined($r->{mid_color}) ? str($r->{mid_color}) : $ch->{midcol};
886 23 50       139 $sc->{lightcol} = defined($r->{light_color}) ? str($r->{light_color}) : $ch->{lightcol};
887             }
888 34 50       128 $sc->{heavyw} = defined($r->{heavy_width}) ? str($r->{heavy_width}) : $ch->{heavyw};
889 34 50       129 $sc->{midw} = defined($r->{mid_width}) ? $r->{mid_width} : $ch->{midw};
890 34 50       142 $sc->{lightw} = defined($r->{light_width}) ? $r->{light_width} : $ch->{lightw};
891             }
892             # Internal method, reading scale options
893             # Called within new, after initlayout (and init_scale_sizes) but before init_bars or init_scale
894              
895             sub init_bars {
896 12     12 0 32 my ($o, $axis, $r) = @_;
897 12         26 my $sc = $o->{$axis};
898 12         29 my $ch = $o->{ch};
899 12 50       51 $r = {} unless (defined $r);
900              
901             #print join(",", @{$sc->{labels}}) . "\n";
902 12         24 my @labels;
903 12         20 foreach my $label (@{$sc->{labels}}) {
  12         39  
904 114         235 push @labels, "($label)";
905             }
906 12 100       70 unless ($labels[$#labels] eq "()") {
907 3         5 push @labels, "()";
908             }
909             # kludge to avoid postscript divide-by-zero error I can't be bothered to trace
910 12         51 while ($#labels < 2) {
911 3         10 push @labels, "()";
912             }
913            
914 12 100       72 my $subdivs = defined($r->{sub_divisions}) ? $r->{sub_divisions} : 1;
915 12         54 my $markmul = ($sc->{markmax} - $sc->{markmin})/$subdivs;
916 12 50       60 $sc->{markmul} = defined($r->{mark_mul}) ? $r->{mark_mul} : $markmul;
917 12         52 $sc->{markgap} = ($sc->{phi} - $sc->{plo})/($#labels * $subdivs);
918 12 100       75 $sc->{markcen} = $subdivs > 1 ? ($sc->{markgap} - 0.5) * $subdivs : $sc->{markgap} * 2;
919 12 50       57 my $n = ($sc->{flags} & $fl_offset) ? $#labels - 1 : $#labels;
920 12         84 $sc->{factors} = [ ($n, $subdivs) ];
921 12         70 $sc->{labels} = [ @labels ];
922 12         28 $sc->{ldepth} = 0;
923             #print "$axis labels = $sc->{labels}\n";
924              
925 12         37 $sc->{labsreq} = $#labels;
926 12         23 $sc->{llo} = 0;
927 12         23 $sc->{lhi} = $#labels;
928 12         54 $sc->{l2pm} = ($sc->{phi} - $sc->{plo})/($sc->{lhi} - $sc->{llo});
929 12         100 $sc->{l2pc} = $sc->{plo} - $sc->{l2pm} * $sc->{llo};
930 12         56 $sc->{p2lm} = ($sc->{lhi} - $sc->{llo})/($sc->{phi} - $sc->{plo});
931 12         78 $sc->{p2lc} = $sc->{llo} - $sc->{p2lm} * $sc->{plo};
932             #print "$axis logical = $sc->{p2lm} * physical + $sc->{p2lc}\n";
933             #print "$axis physical = $sc->{l2pm} * logical + $sc->{l2pc}\n\n";
934              
935             }
936             # Internal method, intializing one barchart axis
937              
938             sub init_scale {
939 22     22 0 61 my ($o, $axis, $r) = @_;
940 22         51 my $sc = $o->{$axis};
941 22         44 my $ch = $o->{ch};
942 22 100       217 $r = {} unless (defined $r);
943              
944             # kludge to better handle -ve scales
945 22 50       91 ($sc->{llo}, $sc->{lhi}) = ($sc->{lhi}, $sc->{llo}) unless $sc->{llo} <= $sc->{lhi};
946 22         52 my ($sclrange, $scprange);
947 22 100 100     130 if ($sc->{llo} < 0 and $sc->{lhi} > 0) {
948 5         11 my $negrange = 0 - $sc->{llo};
949 5         12 my $posrange = $sc->{lhi} - 0;
950 5         15 my $physrange = $sc->{phi} - $sc->{plo};
951 5 50       30 if ($posrange > $negrange) {
952 5         8 $sclrange = $posrange;
953 5         21 $scprange = $physrange * $posrange/($posrange + $negrange);
954             } else {
955 0         0 $sclrange = $negrange;
956 0         0 $scprange = $physrange * $negrange/($posrange + $negrange);
957             }
958             } else {
959 17         50 $sclrange = $sc->{lhi} - $sc->{llo};
960 17         101 $scprange = $sc->{phi} - $sc->{plo};
961             }
962 22         104 $sc->{labsreq} = int($scprange/$sc->{labelgap});
963 22 50       110 $sc->{labsreq} = defined($r->{labels_req}) ? $r->{labels_req} : $sc->{labsreq}; # allow override
964             #warn "$axis, labsreq=$sc->{labsreq} labelgap=$sc->{labelgap} scprange=$scprange\n";
965 22 50       76 $sc->{labsreq} = 1 if $sc->{labsreq} < 1;
966              
967             ## calculate number of major marks to use
968 22 100       160 my $lbase10 = $sclrange > 0 ? log($sclrange)/log(10) : 0;
969 22         60 my $mult = 10 ** int($lbase10);
970 22         49 my $mant = $sclrange/$mult;
971 22         68 my @scale = (0.2, 0.5, 1, 2, 5);
972 22         61 my @subdiv = ( 2, 5, 2, 5, 2);
973 22         48 my ($best, $scale, $subdivs) = (99, 1, 1);
974 22         95 for (my $i = 0; $i <= $#scale; $i++) {
975 110         166 my $smant = $mant*$scale[$i];
976 110         156 my $smult = $mult/$scale[$i];
977 110         179 my $score = abs($smant - $sc->{labsreq});
978 110 100       305 if ($score < $best) {
979 98         110 $best = $score;
980 98         119 $scale = $smult;
981 98         255 $subdivs = $subdiv[$i];
982             }
983             }
984 22         92 $sclrange = $sc->{lhi} - $sc->{llo};
985 22         46 $scprange = $sc->{phi} - $sc->{plo};
986             #warn "$axis requested: physical $sc->{plo} to $sc->{phi}, logical $sc->{llo} to $sc->{lhi}\n";
987             #warn "$axis lrange=$sclrange, prange=$scprange, labelsreq=$sc->{labsreq}, smallest=$sc->{smallest}\n";
988            
989             ## include outer marks as required
990 22         64 my $lhi = int($sc->{lhi}/$scale) * $scale;
991 22         56 my $llo = int($sc->{llo}/$scale) * $scale;
992 22         102 $llo -= $scale while ($llo > $sc->{llo});
993 22         42 $sc->{llo} = $llo;
994 22         121 $lhi += $scale while ($lhi < $sc->{lhi});
995 22 50       111 $lhi = $llo + $scale if $lhi == $llo;
996 22         40 $sc->{lhi} = $lhi;
997 22         52 my $nmarks = ($lhi - $llo)/$scale;
998             #warn "$axis nmarks=$nmarks, scale=$scale, llo=$sc->{llo}, lhi=$sc->{lhi}\n";
999            
1000             ## calculate subdivisions of subdivisions ...
1001 22         65 my @factor = ($nmarks);
1002 22         64 my @spread = ($scale);
1003 22         66 my $nphys = int($scprange/$sc->{smallest});
1004 22         43 my $rem = $nphys/$nmarks;
1005 22         84 while ($rem > $subdivs) {
1006 67         84 $rem /= $subdivs;
1007 67         78 $nmarks *= $subdivs;
1008 67         81 $scale /= $subdivs;
1009 67         100 push @factor, $subdivs;
1010 67         103 push @spread, $scale;
1011 67 100       206 $subdivs = ($subdivs == 2) ? 5 : 2;
1012             }
1013 22 50       138 if ($rem/5 > 1) {
    100          
1014 0         0 $nmarks *= 5;
1015 0         0 $scale /= 5;
1016 0         0 push @factor, 5;
1017 0         0 push @spread, $scale;
1018             } elsif ($rem/2 > 1) {
1019 12         27 $nmarks *= 2;
1020 12         26 $scale /= 2;
1021 12         31 push @factor, 2;
1022 12         40 push @spread, $scale;
1023             }
1024 22         76 $sc->{factors} = [ @factor ]; # nesting of (sub)divisions
1025 22         86 $sc->{spreads} = [ @spread ]; # logical size of those (sub)divisions
1026 22         106 $sc->{markgap} = $scprange/$nmarks; # physical size between smallest marks
1027 22         63 $sc->{markcen} = $sc->{markgap};
1028             #warn "$axis factors = [", join(", ", @factor), "], markgap=$sc->{markgap}, subdivs=$subdivs\n";
1029              
1030             ## calculate physical width of all the marks
1031 22         35 my $marks = 1;
1032 22         63 foreach my $factor (@factor) { $marks *= $factor; }
  101         157  
1033 22         90 $sc->{phi} = $sc->{plo} + $marks * $sc->{markgap};
1034              
1035             ## calculate depth for printed labels
1036 22         42 my $nlabels = 1;
1037 22         41 my $last = 0;
1038 22         66 $sc->{ldepth} = 0;
1039 22         100 for (my $depth = 0; $depth <= $#factor; $depth++) {
1040 50         65 $last = $nlabels;
1041 50         65 $nlabels *= $factor[$depth];
1042 50 100       183 if ($nlabels >= $sc->{labsreq}) {
1043 22 100       96 if (abs($last - $sc->{labsreq}) < abs($nlabels - $sc->{labsreq})) {
1044 13         25 $nlabels = $last;
1045 13         39 $sc->{ldepth} = $depth - 1;
1046             } else {
1047 9         17 $sc->{ldepth} = $depth;
1048             }
1049 22         45 last;
1050             }
1051             }
1052 22 50       96 $sc->{ldepth} = 0 if $sc->{ldepth} < 0;
1053             #warn "$axis spreads = [", join(", ", @spread), "], depth=$sc->{ldepth}\n";
1054 22 50       119 $sc->{markmul} = ($#factor >= 0) ? ($sc->{markmax} - $sc->{markmin})/($#factor + 1) : 0;
1055            
1056             ## calculate any SI adjustment to labels
1057 22 100       130 my $lhi10 = $sc->{lhi} != 0 ? log(abs($sc->{lhi}))/log(10) : 0;
1058 22 50       220 my $si10 = $sc->{si} ? ($sc->{si} * int($lhi10/$sc->{si})) : 0;
1059 22         43 my $si = 10 ** $si10;
1060 22 50       97 if ($si != 1) {
1061 0 0       0 $sc->{title} = '' unless (defined $sc->{title});
1062 0         0 my $groups = $si10/$sc->{si};
1063 0         0 my $zeroes = '|' x $sc->{si};
1064 0 0       0 my $extra = $groups > 1 ? (' ' . "$zeroes " x ($groups-1)) : '';
1065 0         0 $extra = " (in 1$extra${zeroes}'s)";
1066 0         0 $extra =~ tr/|/0/;
1067 0         0 $sc->{title} .= $extra;
1068             }
1069            
1070             ## now for the actual labels
1071 22         40 my @count = ();
1072 22         54 foreach my $f (@factor) { push @count, 0; }
  101         175  
1073 22         53 my $depth = $sc->{ldepth};
1074 22         47 my $value = $sc->{llo};
1075 22         87 my @labels = ($value/$si);
1076 22         82 while ($depth >= 0) {
1077 293         661 for ($depth = $sc->{ldepth}; $depth >= 0; $depth--) {
1078 374         407 ++$count[$depth];
1079 374 100       1132 last if ($count[$depth] < $factor[$depth]);
1080 103         248 $count[$depth] = 0;
1081             }
1082 293         387 $value = $sc->{llo};
1083 293         658 for (my $i = 0; $i <= $sc->{ldepth}; $i++) {
1084 483         1225 $value += $count[$i] * $spread[$i];
1085             }
1086 293         716 push @labels, $value/$si;
1087             }
1088 22         42 pop @labels;
1089 22         62 push @labels, $sc->{lhi}/$si;
1090 22         142 $sc->{labels} = [ @labels ];
1091             #warn "$axis produced : physical $sc->{plo} to $sc->{phi}, logical $sc->{llo} to $sc->{lhi}, si=$si\n";
1092              
1093             ## y = mx + c values
1094 22         184 $sc->{l2pm} = ($sc->{phi} - $sc->{plo})/($sc->{lhi} - $sc->{llo});
1095 22         90 $sc->{l2pc} = $sc->{plo} - $sc->{l2pm} * $sc->{llo};
1096 22         90 $sc->{p2lm} = ($sc->{lhi} - $sc->{llo})/($sc->{phi} - $sc->{plo});
1097 22         163 $sc->{p2lc} = $sc->{llo} - $sc->{p2lm} * $sc->{plo};
1098             #warn "$axis logical = $sc->{p2lm} * physical + $sc->{p2lc}\n";
1099             #warn "$axis physical = $sc->{l2pm} * logical + $sc->{l2pc}\n\n";
1100             }
1101             ## Internal method, initializing one scale
1102             # expects either ("x", $opts{x_axis}) or ("y", $opts{y_axis})
1103             # $o->{ch}{...} must already exist
1104              
1105             =head1 OBJECT METHODS
1106              
1107             Methods are provided which access the option values given to the constructor. Those are B, and all B,
1108             B and B methods documented under L.
1109              
1110             The most common PostScript::File methods are also provided as members of this class.
1111              
1112             However, the most useful methods are those which give access to the layout calculations including conversion
1113             functions.
1114              
1115             =head2 Convenience methods
1116              
1117             A few methods of the underlying PostScript::File object are provided for convenience. The others can be called
1118             via the B function. The following both do the same thing.
1119              
1120             $pg->newpage();
1121             $pg->file()->newpage();
1122              
1123             =cut
1124              
1125             sub output {
1126 3     3 1 1272 my ($o, @params) = @_;
1127 3         23 $o->{ps}->output( @params );
1128             }
1129              
1130             =head3 output( file [, dir] )
1131              
1132             Output the chart as a file. See L.
1133              
1134             =cut
1135              
1136             sub newpage {
1137 0     0 1 0 my ($o, @params) = @_;
1138 0         0 $o->{ps}->newpage( @params );
1139             }
1140              
1141             =head3 newpage( [page] )
1142              
1143             Start a new page in the underlying PostScript::File object. See L and
1144             L.
1145              
1146             =cut
1147              
1148             sub add_function {
1149 0     0 1 0 my ($o, @params) = @_;
1150 0         0 $o->{ps}->add_function( @params );
1151             }
1152              
1153             =head3 add_function( name, code )
1154              
1155             Add functions to the underlying PostScript::File object. See L for details.
1156              
1157             =cut
1158              
1159             sub add_to_page {
1160 0     0 1 0 my ($o, @params) = @_;
1161 0         0 $o->{ps}->add_to_page( @params );
1162             }
1163              
1164             =head3 add_to_page( [page], code )
1165              
1166             Add postscript code to the underlying PostScript::File object. See L for details.
1167              
1168             =cut
1169              
1170             sub graph_area {
1171 10     10 1 24 my $o = shift;
1172 10         108 return ($o->{ch}{gx0}, $o->{ch}{gy0}, $o->{ch}{gx1}, $o->{ch}{gy1});
1173             }
1174            
1175             =head2 Result methods
1176              
1177             These fall into three groups according to their return value. B<_area> methods return an array of four values
1178             representing the physical coordinates of (left, bottom, right, top). B<_point> methods return an array again, but
1179             this time representing an (x, y) value. The underlying constants are also accessable.
1180              
1181             =head3 graph_area
1182              
1183             Return an array holding (x0, y0, x1, y1), the bounding box of the graph area.
1184              
1185             =cut
1186              
1187             sub key_area {
1188 14     14 1 292 my $o = shift;
1189 14         83 my $left = $o->{ch}{gx1} + $o->{ch}{rmargin};
1190 14         84 my $right = $o->{ch}{right} - $o->{ch}{spc} - 1;
1191 14         52 my $top = $o->{ch}{gy1};
1192 14         58 my $bottom = $o->{ch}{bottom} + $o->{ch}{spc};
1193 14         74 return ($left, $bottom, $right, $top);
1194             }
1195            
1196             =head3 key_area
1197              
1198             Return an array holding (x0, y0, x1, y1), the bounding box of the area allocated for the key, if any.
1199              
1200             =cut
1201              
1202             sub vertical_bar_area {
1203 354     354 1 568 my ($o, $bar, $y) = @_;
1204 354         406 my ($left, $bottom, $right, $top);
1205 354 50       977 if (defined $o->{x}{labels}) {
1206 354         1032 $left = $o->{ch}{gx0} + ($bar + 0.5) * $o->{x}{markgap};
1207 354         589 $right = $left + $o->{x}{markgap};
1208             } else {
1209 0         0 $left = $o->{ch}{gx0} + $bar * $o->{x}{markgap};
1210 0         0 $right = $left + $o->{x}{markgap};
1211             }
1212 354 50       655 if (defined $y) {
1213 354         721 $bottom = $o->{y}{l2pc};
1214 354         741 $top = $y * $o->{y}{l2pm} + $o->{y}{l2pc};
1215             # reverse if y < 0
1216 354 100       839 if ($top < $bottom) {
1217 25         40 my $temp = $top;
1218 25         28 $top = $bottom;
1219 25         38 $bottom = $temp;
1220             }
1221             # clip if out of graph area
1222 354         669 my $gy0 = $o->{ch}{gy0};
1223 354         555 my $gy1 = $o->{ch}{gy1};
1224 354 50       791 $top = $gy0 if ($top < $gy0);
1225 354 50       747 $bottom = $gy0 if ($bottom < $gy0);
1226 354 50       711 $top = $gy1 if ($top > $gy1);
1227 354 50       751 $bottom = $gy1 if ($bottom > $gy1);
1228             } else {
1229 0         0 $bottom = $o->{ch}{gy0};
1230 0         0 $top = $o->{ch}{gy1};
1231             }
1232 354         1643 return ($left, $bottom, $right, $top);
1233             }
1234              
1235             =head3 vertical_bar_area
1236              
1237             Return the physical coordinates of a barchart bar. Use as:
1238              
1239             @area = vertical_bar_area( $bar )
1240             @area = vertical_bar_area( $bar, $y )
1241              
1242             Where C<$bar> is the 0 based number of the bar and C<$y> is an optional coordinate indicating the top of the bar.
1243              
1244             =cut
1245              
1246             sub horizontal_bar_area {
1247 0     0 1 0 my ($o, $bar, $x) = @_;
1248 0 0       0 $x = $o->{x}{lhi} unless (defined $x);
1249 0         0 my $left = $o->{ch}{gx0};
1250 0         0 my $bottom = $o->{ch}{gy0} + $bar * $o->{y}{markgap};
1251 0         0 my $right = $x * $o->{x}{l2pm} + $o->{x}{l2pc};
1252 0         0 my $top = $bottom + $o->{y}{markgap};
1253 0         0 return ($left, $bottom, $right, $top);
1254             }
1255              
1256             =head3 horizontal_bar_area
1257              
1258             Return the physical coordinates of a barchart bar. Use as:
1259              
1260             @area = horizontal_bar_area( $bar )
1261             @area = horizontal_bar_area( $bar, $x )
1262              
1263             Where C<$bar> is the 0 based number of the bar and C<$x> is an optional coordinate indicating the 'top' of the bar.
1264              
1265             =cut
1266              
1267             sub physical_point {
1268 0     0 1 0 my ($o, $x, $y) = @_;
1269 0         0 return ($x * $o->{x}{l2pm} + $o->{x}{l2pc}, $y * $o->{y}{l2pm} + $o->{y}{l2pc});
1270             }
1271              
1272             =head3 physical_point( x, y )
1273              
1274             Return the physical, native postscript, coordinates corresponding to the logical point (x, y) on the graph.
1275              
1276             =cut
1277              
1278             sub logical_point {
1279 0     0 1 0 my ($o, $x, $y) = @_;
1280 0         0 return ($x * $o->{x}{p2lm} + $o->{x}{p2lc}, $y * $o->{y}{p2lm} + $o->{y}{p2lc});
1281             }
1282              
1283             =head3 logical_point( px, py )
1284              
1285             Return the logical, graph, coordinates corresponding to a point on the postscript page.
1286              
1287             =cut
1288              
1289             sub px {
1290 70     70 1 93 my ($o, $v) = @_;
1291 70         253 return $v * $o->{x}{l2pm} + $o->{x}{l2pc};
1292             }
1293              
1294             =head3 px
1295              
1296             Use as physical_x = $gp->ps( logical_x )
1297              
1298             =cut
1299              
1300             sub py {
1301 79     79 1 116 my ($o, $v) = @_;
1302 79         288 return $v * $o->{y}{l2pm} + $o->{y}{l2pc};
1303             }
1304              
1305             =head3 py
1306              
1307             Use as physical_y = $gp->ps( logical_y )
1308              
1309             =cut
1310              
1311             sub lx {
1312 0     0 1 0 my ($o, $v) = @_;
1313 0         0 return $v * $o->{x}{p2lm} + $o->{x}{p2lc};
1314             }
1315              
1316             =head3 lx
1317              
1318             Use as logical_x = $gp->ps( physical_x )
1319              
1320             =cut
1321              
1322             sub ly {
1323 0     0 0 0 my ($o, $v) = @_;
1324 0         0 return $v * $o->{y}{p2lm} + $o->{y}{p2lc};
1325             }
1326              
1327             =head3 py
1328              
1329             Use as logical_y = $gp->ps( physical_y )
1330              
1331             =cut
1332              
1333             sub color_as_array ($) {
1334 384     384 0 571 my $col = shift;
1335 384         927 my ($r, $g, $b) = ($col =~ /\[\s*([\d.]+)\s+([\d.]+)\s+([\d.]+)/);
1336 384 100       981 $col = [ $r, $g, $b ] if (defined $b);
1337 384         1831 return $col;
1338             }
1339            
1340             =head1 POSTSCRIPT CODE
1341              
1342             There should be no reason to access this under normal use. However, as the purpose of this module is to make
1343             drawing graphs for postscript easier. Therefore the main graph-drawing function is documented here, along with
1344             the variables and functions that may be useful elsewhere.
1345              
1346             =head3 drawgpaper
1347              
1348             The principal function requires 62 settings. To make this more manageable there are a number of functions which
1349             merely accept and store a small group of these. After these have been executed, B is then called with
1350             no parameters.
1351              
1352             Usage is given below with the functions indented after their parameters. Each function remove all its parameters
1353             from the stack. All functions and variables are within the B dictionary. It is written out as it
1354             would appear within a perl 'here' document, with perl variables for each parameter. The '/' in front of font
1355             names, and '()' around text are required by postscript.
1356              
1357             gpaperdict begin
1358            
1359             $graph_left
1360             $graph_bottom
1361             $graph_right
1362             $graph_top
1363             graph_area
1364              
1365             $heavy_width
1366             $heavy_color
1367             $mid_width
1368             $mid_color
1369             $light_width
1370             $light_color
1371             graph_colors
1372              
1373             $heading_left
1374             $heading_bottom
1375             $heading_right
1376             $heading_top
1377             heading_area
1378              
1379             /$heading_font
1380             $heading_font_size
1381             $heading_font_color
1382             ($heading_text)
1383             heading_labels
1384            
1385             $x_axis_left
1386             $x_axis_bottom
1387             $x_axis_right
1388             $x_axis_top
1389             x_axis_area
1390              
1391             $x_axis_mark_min
1392             $x_axis_mark_multiplier
1393             $x_axis_mark_max
1394             $x_axis_mark_gap
1395             xaxis_marks
1396            
1397             $x_axis_factors_array_as_string
1398             $x_axis_labels_array_as_string
1399             $x_axis_label_depth
1400             $x_axis_flags
1401             /$x_axis_font
1402             $x_axis_font_size
1403             $x_axis_font_color
1404             ($x_axis_text)
1405             xaxis_labels
1406            
1407             $y_axis_left
1408             $y_axis_bottom
1409             $y_axis_right
1410             $y_axis_top
1411             y_axis_area
1412              
1413             $y_axis_mark_min
1414             $y_axis_mark_multiplier
1415             $y_axis_mark_max
1416             $y_axis_mark_gap
1417             yaxis_marks
1418            
1419             $y_axis_factors_array_as_string
1420             $y_axis_labels_array_as_string
1421             $y_axis_label_depth
1422             $y_axis_flags
1423             /$y_axis_font
1424             $y_axis_font_size
1425             $y_axis_font_color
1426             ($y_axis_text)
1427             yaxis_labels
1428              
1429             drawgpaper
1430              
1431             end % gpaperdict
1432            
1433             Most of these are self explanatory or relate to options documented elsewhere, but a few might need some
1434             explanation.
1435              
1436             C indicate how the labels are to be printed.
1437              
1438             Bit Action if true
1439             0 rotate text
1440             1 centre text between marks
1441              
1442             C means a list of all the labels to be printed on the x axis, written out as
1443             a postscript array, such as:
1444              
1445             "[ (label1) (label2) (label3) ]"
1446             "[ 0 0.5 1 1.5 2 ]"
1447              
1448             C has the same format. However, the contents refer to the nesting of the axis
1449             marks. For example, the x axis goes from 400 to 800 in units of 100. Each 100 is subdivided into
1450             2 and then 5, so the smallest divisions are worth 10 each. Labels are placed at the 100 and 50 marks. The factor
1451             array would be as follows.
1452              
1453             [ 4 2 5 ]
1454              
1455             C would be 1 in the previous example (postscript arrays are zero based).
1456            
1457             C is the size of the smallest mark - the 10's above.
1458              
1459             C is the size of the largest mark - the 100's above.
1460              
1461             C is the size added for each decreas in factor depth.
1462              
1463             =head3 px
1464              
1465             Convert a logical x value to a postscript x value. It is probably faster to use B to do any
1466             conversions and write postscript values into the postscript code. PostScript interpreters seem to use much slower
1467             processors.
1468              
1469             =head3 py
1470              
1471             Convert a logical y value to a postscript y value.
1472              
1473             =head3 gpapercolor
1474              
1475             Set the drawing color. This expects a single parameter which may be an array of RGB values or a grayscale value.
1476              
1477             0.5 gpapercolor
1478             [ 1 0.8 0 ] gpapercolor
1479              
1480             =head3 gpaperfont
1481              
1482             Select a font for subsequent text. It expects three parameters - a font name, size and colour. The font name
1483             should evaluate to a literal name as used by C. The size is stored in the variable C for
1484             reference later, and the color is just passed to C.
1485              
1486             /Helvetica 12 0 gpaperfont
1487             /$fontname $fontsize [ $r $g $b ] gpaperfont
1488              
1489             =head3 fillbox
1490              
1491             Fill and outlines a box. Use as follows. The colours are passed to C.
1492              
1493             $left $bottom $right $top
1494             $fill_color
1495             $outline_color $outline_width
1496             fillbox
1497              
1498             =head3 drawbox
1499              
1500             Draw an unfilled box. Use as follows. The colours are passed to C.
1501              
1502             $left $bottom $right $top
1503             $outline_color $outline_width
1504             drawbox
1505              
1506             =head3 centered
1507              
1508             Show text horizontally centred about the coordinated given. Use as:
1509              
1510             $message $x $y centered
1511              
1512             =head3 rjustify
1513              
1514             Show right justified text, ending at the point given. Use as:
1515              
1516             $message $x $y rjustified
1517              
1518             =head3 rotated
1519              
1520             Show text rotated 90 degrees right, starting at the point given. Use as:
1521              
1522             $message $x $y rotated
1523              
1524             =head3 copy_array
1525              
1526             Do a deep copy of an array so that one can be changed without affecting the other. This works differently from
1527             the others. It requires an array, and exits leaving both copies on the stack. The variable C is also
1528             set to the highest index allowed.
1529              
1530             =head2 gpaperdict variables
1531              
1532             Here are some of the variables in the gpaperdict dictionary which might need to be accessed directly.
1533              
1534             array_max largest index into copied array
1535             bgnd background colour for grid
1536             boxc colour of box outline
1537             boxw width of box outline
1538             fillc fill colour of box
1539             fontsize height of most recent gpaperfont
1540             gx0 graph left (same as xx0)
1541             gy0 graph bottom (same as yy0)
1542             gx1 graph right (same as xx1)
1543             gy1 graph top
1544             hcol font colour used on heading
1545             hfont font name used on heading
1546             hsize font size used on heading
1547             hx0 head left
1548             hx1 head right
1549             hy0 head bottom
1550             hy1 head top
1551             xlc constant for logical x
1552             xlm multiplier for logical x
1553             xmarkcen width for centering label
1554             ylc constant for logical y
1555             ylm multiplier for logical y
1556             ymarkcen width for centering label
1557              
1558             =cut
1559              
1560            
1561             sub ps_functions {
1562 17     17 0 46 my ($class, $ps) = @_;
1563              
1564 17         42 my $name = "GraphPaper";
1565 17 50       106 $ps->add_function( $name, <has_function($name));
1566             /gpaperdict 120 dict def
1567             gpaperdict begin
1568             /finish 0 def
1569             /labelbuf 80 string def
1570              
1571             /gpapercolor {
1572             gpaperdict begin
1573             dup type (arraytype) eq {
1574             aload pop
1575             setrgbcolor
1576             }{
1577             dup 0 le { neg } if
1578             setgray
1579             } ifelse
1580             end
1581             } bind def
1582             % _ array|int => _
1583              
1584             /gpaperfont {
1585             gpaperdict begin
1586             gpapercolor
1587             /fontsize exch def
1588             findfont fontsize scalefont setfont
1589             end
1590             } bind def
1591             % _ font size color => _
1592              
1593             /centered {
1594             3 2 roll labelbuf cvs 3 1 roll
1595             2 index stringwidth pop 2 div neg
1596             3 2 roll add exch
1597             moveto show
1598             } bind def
1599             % _ str x y => _
1600              
1601             /rjustified {
1602             3 2 roll labelbuf cvs 3 1 roll
1603             2 index stringwidth pop neg
1604             3 2 roll add exch
1605             moveto show
1606             } bind def
1607             % _ str x y => _
1608              
1609             /rotated {
1610             3 2 roll labelbuf cvs 3 1 roll
1611             gsave
1612             translate
1613             -90 rotate
1614             0 0 moveto show
1615             grestore
1616             } bind def
1617             % _str x y => _
1618              
1619             /init_xy {
1620             /setstrokeadjust where { pop true setstrokeadjust } if
1621             newpath
1622             moveto
1623             store_xy
1624             stroke
1625             } bind def
1626             % _ x y => _
1627              
1628             /store_xy {
1629             gpaperdict begin
1630             currentpoint
1631             /y exch def
1632             /x exch def
1633             end
1634             } bind def
1635             % _ x y => _
1636              
1637             /copy_array {
1638             gpaperdict begin
1639             mark 1 index aload pop
1640             /array_max counttomark 2 sub def
1641             ]
1642             end
1643             } bind def
1644             % _ array => _ array array
1645             % make deep copy of array and set array_max
1646              
1647             /drawonegrid {
1648             gpaperdict begin
1649             /label 0 def
1650             /drawline exch cvx def
1651             copy_array
1652             0 drawline
1653             /finish 0 def
1654             {
1655             % dec counters in array
1656             array_max -1 0
1657             {
1658             2 copy 2 copy get 1 sub put
1659             dup /factor exch def
1660             2 copy get 0 gt { pop exit } if
1661             dup 0 eq {
1662             2 copy get 0 eq {
1663             /finish 1 def
1664             } if
1665             } if
1666             2 copy dup 5 index
1667             exch get put
1668             pop
1669             } for
1670             factor drawline
1671             finish 1 eq { exit } if
1672             } loop
1673             pop pop
1674             end
1675             } def
1676             % Requires an array of scales
1677             % and a suitable fn for drawing each line
1678             % _ factor_array fn_name => _
1679              
1680             /setlines {
1681             dup 0 eq {
1682             heavyw setlinewidth
1683             heavyc gpapercolor
1684             }{
1685             dup 1 eq {
1686             midw setlinewidth
1687             midc gpapercolor
1688             }{
1689             lightw setlinewidth
1690             lightc gpapercolor
1691             } ifelse
1692             } ifelse
1693             } bind def
1694             % _ depth => _ depth
1695              
1696             /drawbox {
1697             7 dict begin
1698             gsave
1699             /boxw exch def /boxc exch def
1700             /y1 exch def /x1 exch def /y0 exch def /x0 exch def
1701             newpath
1702             x0 y0 moveto x0 y1 lineto x1 y1 lineto x1 y0 lineto
1703             closepath
1704             boxc gpapercolor boxw setlinewidth
1705             stroke
1706             grestore
1707             end
1708             } bind def
1709             % x0 y0 x1 y1 outline_col outline_width => _
1710              
1711             /fillbox {
1712             7 dict begin
1713             gsave
1714             /boxw exch def /boxc exch def /fillc exch def
1715             /y1 exch def /x1 exch def /y0 exch def /x0 exch def
1716             newpath
1717             x0 y0 moveto x0 y1 lineto x1 y1 lineto x1 y0 lineto
1718             closepath
1719             gsave fillc gpapercolor fill grestore
1720             boxc gpapercolor boxw setlinewidth
1721             stroke
1722             grestore
1723             end
1724             } bind def
1725             % x0 y0 x1 y1 fill_col outline_col outline_width => _
1726              
1727             /graph_area {
1728             gpaperdict begin
1729             /fgnd exch def
1730             /bgnd exch def
1731             /gy1 exch def
1732             /gx1 exch def
1733             /gy0 exch def
1734             /gx0 exch def
1735             /width gx1 gx0 sub def
1736             /height gy1 gy0 sub def
1737             end
1738             } bind def
1739             % _ x0 y0 x1 y1 bgnd boxcol => _
1740              
1741             /set_xaxis_colors {
1742             gpaperdict begin
1743             /lightc xlightc def
1744             /lightw xlightw def
1745             /midc xmidc def
1746             /midw xmidw def
1747             /heavyc xheavyc def
1748             /heavyw xheavyw def
1749             end
1750             } bind def
1751             % _ => _
1752              
1753             /set_yaxis_colors {
1754             gpaperdict begin
1755             /lightc ylightc def
1756             /lightw ylightw def
1757             /midc ymidc def
1758             /midw ymidw def
1759             /heavyc yheavyc def
1760             /heavyw yheavyw def
1761             end
1762             } bind def
1763             % _ => _
1764              
1765             /xaxis_colors {
1766             gpaperdict begin
1767             /xlightc exch def
1768             /xlightw exch def
1769             /xmidc exch def
1770             /xmidw exch def
1771             /xheavyc exch def
1772             /xheavyw exch def
1773             end
1774             } bind def
1775             % _ heavyw heavyc midw midc lightw lightc => _
1776              
1777             /yaxis_colors {
1778             gpaperdict begin
1779             /ylightc exch def
1780             /ylightw exch def
1781             /ymidc exch def
1782             /ymidw exch def
1783             /yheavyc exch def
1784             /yheavyw exch def
1785             end
1786             } bind def
1787             % _ heavyw heavyc midw midc lightw lightc => _
1788              
1789             /xaxis_area {
1790             gpaperdict begin
1791             /xy1 exch def
1792             /xx1 exch def
1793             /xy0 exch def
1794             /xx0 exch def
1795             end
1796             } bind def
1797             % _ x0 y0 x1 y1 => _
1798              
1799             /xaxis_labels {
1800             gpaperdict begin
1801             /xtitle exch def
1802             /xcol exch def
1803             /xsize exch def
1804             /xfont exch def
1805             /xflags exch def
1806             /xldepth exch def
1807             /xlabels exch def
1808             /xfactors exch def
1809             end
1810             } bind def
1811             % _ factors labels flags font size color title => _
1812              
1813             /xaxis_marks {
1814             gpaperdict begin
1815             /xdrawfn exch def
1816             /xmarkcen exch def
1817             /xmarkgap exch def
1818             /xmarkmax exch def
1819             /xmarkmul exch def
1820             /xmarkmin exch def
1821             end
1822             } bind def
1823             % _ xmarkmin xmarkmul xmarkmax xmarkgap xmarkcen /xdrawfn => _
1824              
1825             /yaxis_area {
1826             gpaperdict begin
1827             /yy1 exch def
1828             /yx1 exch def
1829             /yy0 exch def
1830             /yx0 exch def
1831             end
1832             } bind def
1833             % _ x0 y0 x1 y1 => _
1834              
1835             /yaxis_labels {
1836             gpaperdict begin
1837             /ytitle exch def
1838             /ycol exch def
1839             /ysize exch def
1840             /yfont exch def
1841             /yflags exch def
1842             /yldepth exch def
1843             /ylabels exch def
1844             /yfactors exch def
1845             end
1846             } bind def
1847             % _ factors labels flags font size color title => _
1848              
1849             /yaxis_marks {
1850             gpaperdict begin
1851             /ydrawfn exch def
1852             /ymarkcen exch def
1853             /ymarkgap exch def
1854             /ymarkmax exch def
1855             /ymarkmul exch def
1856             /ymarkmin exch def
1857             end
1858             } bind def
1859             % _ ymarkmin ymarkmul ymarkmax ymarkgap ymarkcen /ydrawfn => _
1860              
1861             /heading_area {
1862             gpaperdict begin
1863             /hy1 exch def
1864             /hx1 exch def
1865             /hy0 exch def
1866             /hx0 exch def
1867             end
1868             } bind def
1869             % _ x0 y0 x1 y1 => _
1870              
1871             /heading_labels {
1872             gpaperdict begin
1873             /htitle exch def
1874             /hcol exch def
1875             /hsize exch def
1876             /hfont exch def
1877             end
1878             } bind def
1879             % _ hfont hsize hcol title => _
1880              
1881             /conv_consts {
1882             gpaperdict begin
1883             /ylc exch def
1884             /ylm exch def
1885             /xlc exch def
1886             /xlm exch def
1887             end
1888             } bind def
1889             % _ xlm xlc ylm ylc => _
1890              
1891             /px {
1892             xlm mul xlc add
1893             } bind def
1894            
1895             % _ int => int
1896             /py {
1897             ylm mul ylc add
1898             } bind def
1899            
1900             % _ => _
1901             /drawgpaper {
1902             gpaperdict begin
1903             gx0 gy0 gx1 gy1 bgnd bgnd 0.25 fillbox
1904             hfont hsize hcol gpaperfont
1905             htitle hx1 hx0 add 2 div hy1 hsize sub centered
1906            
1907             xfont xsize xcol gpaperfont
1908             xtitle xx1 xy0 rjustified
1909             xflags $fl_offset and $fl_offset eq {
1910             gx0 xmarkgap add gy0 init_xy
1911             }{
1912             gx0 gy0 init_xy
1913             } ifelse
1914             set_xaxis_colors
1915             xfactors xdrawfn drawonegrid
1916            
1917             yfont ysize ycol gpaperfont
1918             ytitle yx0 hy0 ysize 0.5 mul add moveto show
1919             gx0 gy0 init_xy
1920             set_yaxis_colors
1921             yfactors ydrawfn drawonegrid
1922            
1923             gx0 gy0 gx1 gy1 fgnd heavyw drawbox
1924             end
1925             } bind def
1926             % _ int => int
1927              
1928             /xdraw {
1929             gpaperdict begin
1930             dup xldepth le {
1931             gsave
1932             xcol gpapercolor
1933             xlabels label get
1934             /xx x def /yy y def
1935             xflags $fl_center and $fl_center eq {
1936             /xx xx xmarkcen 0.5 mul add def
1937             } if
1938             xflags $fl_rotate and $fl_rotate eq {
1939             xx fontsize 0.33 mul sub
1940             yy xmarkmax 1.25 mul sub
1941             rotated
1942             }{
1943             xx yy xmarkmax sub fontsize sub
1944             centered
1945             } ifelse
1946             grestore
1947             /label label 1 add def
1948             } if
1949             setlines
1950             newpath
1951             x y moveto
1952             dup xmarkmul mul xmarkmin add
1953             xmarkmax exch sub
1954             dup neg 0 exch rlineto
1955             0 exch rmoveto
1956             dup 2 le {
1957             0 height rlineto
1958             0 height neg rmoveto
1959             } if
1960             xmarkgap 0 rmoveto
1961             store_xy
1962             stroke
1963             pop
1964             end
1965             } bind def
1966             % _ depth => _
1967             % draw one vertical line
1968              
1969             /ydraw {
1970             gpaperdict begin
1971             dup yldepth le {
1972             gsave
1973             ycol gpapercolor
1974             ylabels label get
1975             yflags 1 and 1 eq {
1976             yflags 2 and 2 eq {
1977             % rotate and centre
1978             x ymarkmax sub fontsize sub
1979             1 index labelbuf cvs stringwidth pop 2 div
1980             y add ymarkcen 0.5 mul add
1981             }{
1982             % rotate and not centre
1983             x ymarkmax sub fontsize sub
1984             1 index labelbuf cvs stringwidth pop 2 div
1985             y add
1986             } ifelse
1987             rotated
1988             }{
1989             yflags 2 and 2 eq {
1990             % not rotate and centre
1991             x ymarkmax sub 2 sub
1992             y fontsize 0.33 mul sub ymarkcen 0.6 mul add
1993             }{
1994             % not rotate and not centre
1995             x ymarkmax sub 2 sub
1996             y fontsize 0.33 mul sub
1997             } ifelse
1998             rjustified
1999             } ifelse
2000             grestore
2001             /label label 1 add def
2002             } if
2003             setlines
2004             newpath
2005             x y moveto
2006             dup ymarkmul mul ymarkmin add
2007             ymarkmax exch sub
2008             dup neg 0 rlineto
2009             0 rmoveto
2010             dup 2 le {
2011             width 0 rlineto
2012             width neg 0 rmoveto
2013             } if
2014             0 ymarkgap rmoveto
2015             store_xy
2016             stroke
2017             pop
2018             end
2019             } bind def
2020             % _ depth => _
2021             % draw one horizontal line
2022              
2023             /xdrawstock {
2024             gpaperdict begin
2025             dup xldepth le {
2026             gsave
2027             xcol gpapercolor
2028             xlabels label get
2029             dup length 0 ne {
2030             x fontsize 0.33 mul sub
2031             y xmarkmax 1.5 mul sub
2032             rotated
2033             pop 0
2034             }{
2035             pop pop 1
2036             } ifelse
2037             grestore
2038             /label label 1 add def
2039             }{
2040             pop 2
2041             } ifelse
2042             setlines
2043             newpath
2044             x y moveto
2045             dup xmarkmul mul xmarkmin add
2046             xmarkmax exch sub
2047             dup neg 0 exch rlineto
2048             0 exch rmoveto
2049             dup 2 le {
2050             0 height rlineto
2051             0 height neg rmoveto
2052             } if
2053             xmarkgap 0 rmoveto
2054             store_xy
2055             stroke
2056             pop
2057             end
2058             } bind def
2059             % _ depth => _
2060             % draw one vertical line
2061             % label if depth > 0
2062              
2063             end % gpaperdict
2064             END_COMMON_FUNCTIONS
2065             }
2066             # Internal method
2067             # Postscript functions common to all axes
2068             #
2069             # This list is to ensure that enough space is allowed in gpaperdict
2070             # for Level 1 interpreters.
2071             #
2072             ## gpaperdict functions:
2073             # centered show text centred horizontally
2074             # gpapercolor select colour or greyscale
2075             # gpaperfont select font, noting size
2076             # conv_consts setup conversion constants
2077             # copy_array deep copy of array, sets array_max
2078             # drawbox draw unfilled box
2079             # drawgpaper main function drawing all areas
2080             # drawonegrid draw all vertical or horizontal lines
2081             # fillbox draw and fill box
2082             # graph_area setup graph area
2083             # graph_colors setup colours for lines
2084             # heading_area setup heading area
2085             # heading_labels setup title for heading
2086             # init_xy set starting position
2087             # rjustified show text right justified
2088             # rotated show text rotated 90 degrees right
2089             # setlines determine colour of graph lines
2090             # store_xy mark end of current path
2091             # xaxis_area setup x axis area
2092             # xaxis_labels setup labels and title for x axis
2093             # xaxis_marks setup mark data for x axis
2094             # xdraw draw vertical mark according to array index
2095             # xdrawstock custom fn drawing marks for stock chart
2096             # yaxis_area setup y axis area
2097             # yaxis_labels setup labels and title for y axis
2098             # yaxis_marks setup mark data for y axis
2099             # ydraw draw horizontal mark line according to array index
2100             #
2101             ## gpaperdict variables:
2102             # array_max largest index into copied array
2103             # bgnd background colour for grid
2104             # boxc colour of box outline
2105             # boxw width of box outline
2106             # drawline place holder in drawonegrid for xdraw/ydraw
2107             # factor array index in drawonegrid indicating the factor changed
2108             # fgnd colour of grid outline
2109             # fillc fill colour of box
2110             # finish flag used by drawgpaper
2111             # fontsize height of most recent gpaperfont
2112             # gx0 graph left (same as xx0)
2113             # gy0 graph bottom (same as yy0)
2114             # gx1 graph right (same as xx1)
2115             # gy1 graph top
2116             # hcol font colour used on heading
2117             # hfont font name used on heading
2118             # hsize font size used on heading
2119             # height height of graph area
2120             # htitle title for heading
2121             # hx0 head left
2122             # hx1 head right
2123             # hy0 head bottom
2124             # hy1 head top
2125             # label label counter used by drawonegrid
2126             # labelbuf buffer for string conversion
2127             # width width of graph area
2128             # x current position between paths
2129             # x0 temp left
2130             # x1 temp right
2131             # xcol font colour uses on x axis
2132             # xdrawfn the function to use for drawing x axis
2133             # xfactors array holding mark info
2134             # xflags 1=rotate, 2=centre
2135             # xfont font name used on x axis
2136             # xheavyc colour for heavy lines
2137             # xheavyw width of heavy lines
2138             # xlabels array of labels for x axis
2139             # xlc constant for logical x
2140             # xldepth print labels up to this depth
2141             # xlightc colour for light lines
2142             # xlightw width of light lines
2143             # xlm multiplier for logical x
2144             # xmarkcen width for centering labels
2145             # xmarkgap gap between adjacent marks
2146             # xmarkmax tallest mark
2147             # xmarkmin smallest mark
2148             # xmarkmul step added for each depth
2149             # xmidc colour for mid lines
2150             # xmidw width of mid lines
2151             # xsize font size used on x axis
2152             # xtitle title for x axis
2153             # xx temp x used by xdraw
2154             # xx0 x axis left
2155             # xx1 x axis right
2156             # xy0 x axis bottom
2157             # xy1 x axis top (same as yy0)
2158             # y current position between paths
2159             # y0 temp bottom
2160             # y1 temp top
2161             # ycol font colour used on y axis
2162             # ydrawfn the function to use for drawing y axis
2163             # yfactors array of scale info for drawonegrid
2164             # yflags 1=rotate, 2=centre
2165             # yfont font name used on y axis
2166             # yheavyc colour for heavy lines
2167             # yheavyw width of heavy lines
2168             # ylabels array of labels for y axis
2169             # ylc constant for logical y
2170             # yldepth print labels up to this depth
2171             # ylightc colour for light lines
2172             # ylightw width of light lines
2173             # ylm multiplier for logical y
2174             # ymarkcen width for centering labels
2175             # ymarkgap gap between adjacent marks
2176             # ymarkmax tallest mark
2177             # ymarkmin smallest mark
2178             # ymarkmul step added for each depth
2179             # ymidc colour for mid lines
2180             # ymidw width of mid lines
2181             # ysize font size used on y axis
2182             # ytitle title for y axis
2183             # yx0 y axis left
2184             # yx1 y axis right (same as xx0)
2185             # yy temp y used by xdraw
2186             # yy0 y axis bottom
2187             # yy1 y axis top
2188              
2189             sub draw_scales {
2190 17     17 1 551 my ($o) = @_;
2191 17         66 my $ch = $o->{ch};
2192 17         66 my $x = $o->{x};
2193 17         59 my $y = $o->{y};
2194 17         48 my $xfactors = array_as_string( @{$x->{factors}} );
  17         194  
2195 17         372 my $yfactors = array_as_string( @{$y->{factors}} );
  17         120  
2196 17         389 my $xlabels = array_as_string( @{$x->{labels}} );
  17         111  
2197 17         599 my $ylabels = array_as_string( @{$y->{labels}} );
  17         107  
2198              
2199 17         2792 $o->{ps}->add_to_page( <
2200             gpaperdict begin
2201             $ch->{gx0} $ch->{gy0} $ch->{gx1} $ch->{gy1} $ch->{bgnd} $ch->{fgnd} graph_area
2202             $ch->{hx0} $ch->{hy0} $ch->{hx1} $ch->{hy1} heading_area
2203             /$ch->{hfont} $ch->{hsize} $ch->{hcol} ($ch->{title}) heading_labels
2204             $ch->{xx0} $ch->{xy0} $ch->{xx1} $ch->{xy1} xaxis_area
2205             $ch->{yx0} $ch->{yy0} $ch->{yx1} $ch->{yy1} yaxis_area
2206             $x->{heavyw} $x->{heavycol} $x->{midw} $x->{midcol} $x->{lightw} $x->{lightcol} xaxis_colors
2207             $y->{heavyw} $y->{heavycol} $y->{midw} $y->{midcol} $y->{lightw} $y->{lightcol} yaxis_colors
2208             $x->{markmin} $x->{markmul} $x->{markmax} $x->{markgap} $x->{markcen} /$x->{draw} xaxis_marks
2209             $y->{markmin} $y->{markmul} $y->{markmax} $y->{markgap} $y->{markcen} /$y->{draw} yaxis_marks
2210             $xfactors $xlabels $x->{ldepth} $x->{flags}
2211             /$x->{font} $x->{fsize} $x->{fcol} ($x->{title}) xaxis_labels
2212             $yfactors $ylabels $y->{ldepth} $y->{flags}
2213             /$y->{font} $y->{fsize} $y->{fcol} ($y->{title}) yaxis_labels
2214             drawgpaper
2215             $x->{l2pm} $x->{l2pc} $y->{l2pm} $y->{l2pc} conv_consts
2216             end
2217             END_SCALES
2218             }
2219              
2220             =head3 draw_scales()
2221              
2222             Commits to postscript the settings collected and calculted by C. Under normal circumstances this should not
2223             need to be called. It is only necessary if the layout option C has been specified.
2224              
2225             =head1 BUGS
2226              
2227             Very likely. This is still alpha software and has been tested in fairly limited conditions.
2228              
2229             =head1 AUTHOR
2230              
2231             Chris Willmot, chris@willmot.org.uk
2232              
2233             =head1 SEE ALSO
2234              
2235             L, L and L for the other modules in this suite.
2236              
2237             L, L and L for modules that use this one.
2238              
2239             =cut
2240              
2241              
2242             1;