File Coverage

blib/lib/PostScript/Graph/Style.pm
Criterion Covered Total %
statement 257 285 90.1
branch 168 210 80.0
condition 32 44 72.7
subroutine 26 46 56.5
pod 10 30 33.3
total 493 615 80.1


line stmt bran cond sub pod time code
1             package PostScript::Graph::Style;
2             our $VERSION = 1.02;
3 14     14   184512 use strict;
  14         32  
  14         1214  
4 14     14   95 use warnings;
  14         32  
  14         656  
5 14     14   91 use PostScript::File 1.00 qw(str);
  14         349  
  14         1529  
6              
7             =head1 NAME
8              
9             PostScript::Graph::Style - style settings for postscript objects
10              
11             =head1 SYNOPSIS
12              
13             =head2 Simplest
14              
15             Each time a new object is created the default style will be slightly different.
16              
17             use PostScript::File;
18             use PostScript::Graph::Style;
19              
20             my $file = new PostScript::File();
21             my $seq = new PostScript::Graph::Sequence();
22            
23             while (...) {
24             my $style = new PostScript::Graph::Style(
25             sequence => $seq,
26             point => {}
27             );
28             $style->write($file);
29            
30             $file->add_to_page( <
31             % code using point variables...
32            
33             % setting colour or grey shade
34             gpaperdict begin
35             pocolor gpapercolor
36             end
37              
38             % choosing a line width
39             powidth setlinewidth
40            
41             % scaled relative to point sizing
42             0 ppsize rlineto
43            
44             % showing the chosen point shape
45             100 200 ppshape
46             END_OF_CODE
47             }
48            
49             =head2 Typical
50              
51             It is possible to control how each new object varies.
52              
53             my $seq = new PostScript::Graph::Sequence();
54             $seq->setup( "red", [0, 1, 0.2, 0.8, 0.4, 0.6] );
55             $seq->auto( qw(red green blue);
56            
57             my $file = new PostScript::File();
58             while (...) {
59             my $style = new PostScript::Graph::Style(
60             sequence => $seq,
61             bar => {},
62             );
63             $style->write($file);
64            
65             ... postscript using bar variables ...
66             }
67            
68             Some of the styles may be overriden.
69              
70             my $style = new PostScript::Graph::Style(
71             sequence => $seq,
72             auto => [qw(color dashes)],
73             line => {
74             width => 4,
75             outer_dashes => [],
76             outer_color => [1, 0, 0],
77             },
78             );
79              
80             Or the automatic default feature can be supressed and some or all details specified directly.
81              
82             my $style = new PostScript::Graph::Style(
83             auto => "none",
84             point => {
85             shape => "circle",
86             size => 12,
87             },
88             );
89              
90             =head2 All options
91              
92             my $style = new PostScript::Graph::Style(
93             sequence => $seq,
94             auto => [qw(red green blue)],
95             changes_only => 0,
96             bgnd_outline => 1,
97              
98             line => {
99             color => [0, 1, 0],
100             inner_color => [1, 1, 0],
101             outer_color => 0,
102             dashes => [3, 3],
103             inner_dashes => [5, 2, 5, 10],
104             outer_dashes => [],
105             width => 2,
106             inner_width => 2,
107             outer_width => 2.5,
108             },
109              
110             point => {
111             size => 8,
112             shape => "diamond",
113             color => [0, 1, 0],
114             inner_color => [1, 1, 0],
115             outer_color => 0,
116             width => 2,
117             inner_width => 2,
118             outer_width => 2.5,
119             },
120              
121             bar => {
122             color => [0, 1, 0],
123             inner_color => [1, 1, 0],
124             outer_color => 0,
125             width => 2,
126             inner_width => 2,
127             outer_width => 2.5,
128             },
129             );
130              
131             =head1 DESCRIPTION
132              
133             This module is designed as a supporting part of the PostScript::Graph suite. For top level modules that output
134             something useful, see
135              
136             PostScript::Graph::Bar
137             PostScript::Graph::Stock
138             PostScript::Graph::XY
139              
140             Style settings are provided for objects placed on a graph. Lines on the same graph need to be distinguishable
141             from each other. Each line would have a PostScript::Graph::Style object holding its settings. Passing each line
142             a reference to the same PostScript::Graph::Sequence object makes the styles vary. This can either use the defaults of be
143             controlled to every last detail.
144              
145             Settings are provided for three types of object. A B is any unfilled path, a B is any filled path
146             while a B is a filled path that may contain holes.
147              
148             They all have outer and inner components. The inner component provides the main shape and colour, while the outer
149             'edge' is provided to insulate this from any background colour. Lines may be whole or broken and a variety of
150             builtin shapes is provided. By default, repeated calls to B return styles that differ from one another
151             although like everything else this can be under detailed user control if required.
152              
153             The settings are only useful once they have been written out to a PostScript::File object using B. The
154             following functions return values set in the constructor. See L for more details.
155              
156             bar_outer_color()
157             bar_outer_width()
158             bar_inner_color()
159             bar_inner_width()
160              
161             color()
162            
163             line_outer_color()
164             line_outer_width()
165             line_outer_dashes()
166             line_inner_color()
167             line_inner_width()
168             line_inner_dashes()
169              
170             point_size()
171             point_shape()
172             point_outer_color()
173             point_outer_width()
174             point_inner_color()
175             point_inner_width()
176              
177             bgnd_outline()
178             sequence()
179            
180             =cut
181              
182             ### PostScript::Graph::Sequence
183              
184             package PostScript::Graph::Sequence;
185 14     14   85 use PostScript::File qw(str);
  14         33  
  14         58597  
186              
187             # Largely for testing
188             our $sequence_id = 1;
189              
190             sub new {
191 16     16   3004 my $class = shift;
192              
193 16         40 my $o= {};
194 16         50 bless( $o, $class );
195 16         143 $o->{id} = $sequence_id++;
196 16         52 $o->{styleid} = 0;
197              
198             # Starting selections
199 16         550 $o->{red} = [ 0.5, 1, 0 ],
200             $o->{green} = [ 0, 0.5, 0.25, 0.75, 1 ],
201             $o->{blue} = [ 0, 1, 0.5 ],
202             $o->{gray} = [ 0.6, 0, 0.45, 0.15, 0.75, 0.3, 0.9 ],
203             $o->{color} = [ [0.8,0.8,0], [0,0.5,0.5], [0.3,0,0.3], [0.9,0.3,0] ],
204             $o->{shape} = [qw(dot cross square plus diamond circle)],
205             $o->{width} = [ 0.5, 1, 3, 2 ],
206             $o->{dashes} = [ [], [9, 9], [3, 3], [9, 3], [3, 9], [9, 3, 3, 3] ],
207             $o->{size} = [ 2, 4, 6 ],
208              
209            
210             $o->{initialized} = 0; # Ensure init_defaults is only called once
211 16         53 $o->{auto} = undef; # requested choices
212 16         50 $o->{choices} = []; # choices in use
213 16         44 $o->{max} = []; # for resetting counts
214 16         64 $o->{count} = []; # current position in each choice
215              
216 16         290 return $o;
217             }
218              
219             =head2 Style Generation
220              
221             Although it is possible to specify styles directly, mostly the style just needs to be different from the last
222             one. These dynamic defaults provide around 3600 variations which should be suitable for most cases. The values
223             themselves can be replaced if desired. Permutations of these are then generated on demand and the permutation order
224             is also under user control.
225              
226             =head3 PostScript::Graph::Sequence new
227              
228             Whenever a new PostScript::Graph::Style object is created, it uses certain defaults. These defaults can be made to
229             vary if a sequence is declared as one of the options. This should be the value returned from:
230              
231             my $seq = new PostScript::Graph::Sequence();
232              
233             =cut
234              
235             sub create {
236 234     234   459 my ($o, $list) = @_;
237 234 50       514 return defaults() if ($o->{none});
238              
239 234 100       463 if (defined $list) {
240 226         325 my $old = $o->{choices};
241 226 100       452 if ($#$old == $#$list) {
242 216         4657 for (my $i = 0; $i <= $#$list; $i++) {
243 691 50       3353 if($list->[$i] ne $old->[$i]) {
244 0         0 $o->{initialized} = 0;
245 0         0 last;
246             }
247             }
248             } else {
249 10         28 $o->{initialized} = 0;
250             }
251             }
252            
253 234 100       723 if ($o->{initialized}) {
254 220         483 return $o->next_row();
255             } else {
256 14         65 $o->doreset($list);
257 14         140 return $o->output_row();
258             }
259             }
260             # Internal method
261             # create a new set of values in this sequence
262             # $opts is hash ref with {auto} key
263             # Adds pstyle => previous_style on return
264              
265             sub doreset {
266 14     14   30 my ($o, $list) = @_;
267 14 100       63 if (defined $list) {
268 10 50       107 $o->{auto} = $list unless defined $o->{auto};
269             }
270 14         35 $list = $o->{auto};
271 14         33 $o->{initialized} = 1;
272 14         25 @{$o->{choices}} = ();
  14         76  
273 14         26 @{$o->{max}} = ();
  14         35  
274 14         29 @{$o->{count}} = ();
  14         32  
275            
276 14         81 foreach my $ch (@$list) {
277 28 50 33     187 push @{$o->{choices}}, $ch if ($ch and defined $o->{$ch});
  28         79  
278             }
279 14 100       36 if (@{$o->{choices}} == 0) {
  14         64  
280 4         26 $o->{choices} = [ qw(dashes shape width size) ];
281             }
282             #print "choices = " . join(", ", @{$o->{choices}}) . "\n";
283              
284 14         35 foreach my $key (@{$o->{choices}}) {
  14         47  
285 44 50       143 if (defined $o->{$key}) {
286 44         62 push @{$o->{max}}, $#{$o->{$key}};
  44         250  
  44         93  
287 44         59 push @{$o->{count}}, 0;
  44         107  
288             }
289             }
290 14         36 return;
291             }
292             # Internal method
293             # called by create()
294              
295             sub defaults {
296 234     234   261 my %ref;
297 234         441 $ref{red} = 0;
298 234         477 $ref{green} = 0;
299 234         356 $ref{blue} = 0;
300 234         371 $ref{gray} = 0;
301 234         621 $ref{color} = [0,0.5,0.5];
302 234         867 $ref{shape} = 'dot';
303 234         326 $ref{width} = 0.5;
304 234         1841 $ref{dashes} = [];
305 234         591 $ref{size} = 2;
306 234         326 $ref{pstyle} = 0; # special signal for auto => 'none'
307 234         1063 return \%ref;
308             }
309             # Internal function
310             # ensuring all defaults have some value
311              
312             sub output_row {
313 234     234   280 my $o = shift;
314 234         424 my $r = defaults();
315 234         338 for (my $i = 0; $i < @{$o->{count}}; $i++) {
  985         2692  
316 751         1638 my $key = $o->{choices}[$i];
317 751         1548 my $chosen = $o->{count}[$i];
318 751         2070 my $value = $o->{$key}[$chosen];
319             #warn "key=$key, chosen=$chosen, value=$value\n";
320 751 100 100     2916 if ($key eq 'color' or $key eq 'gray') {
321 24 100       52 if (ref($value) eq "ARRAY") {
322 14         28 $r->{red} = $value->[0];
323 14         25 $r->{green} = $value->[1];
324 14         40 $r->{blue} = $value->[2];
325             } else {
326 10         18 $r->{red} = $value;# * 0.3;
327 10         14 $r->{green} = $value;# * 0.59;
328 10         24 $r->{blue} = $value;# * 0.11;
329             }
330             } else {
331 727 50 33     10154 $r->{$key} = $value if (defined $r->{$key} and defined $value);
332             }
333             }
334 234         467 $r->{pstyle} = $o->{pstyle};
335             #warn "count = " . join(", ", @{$o->{count}}) . "\n";
336             #warn "rgb=[$r->{red},$r->{green},$r->{blue}] ($r->{gray}) c=",str($r->{color})," '$r->{shape}'($r->{size}) w=$r->{width}, ",str($r->{dashes}),"\n";
337 234         662 return $r;
338             }
339             # Internal method
340             # Returns a hash ref filled with suitable values
341              
342             sub next_row {
343 220     220   804 my $o = shift;
344 220 50       317 if (@{$o->{count}}) {
  220         668  
345 220         264 my $i = 0;
346 220         241 while (1) {
347 287 100       853 if ($o->{count}[$i] < $o->{max}[$i]) {
348 219         306 $o->{count}[$i]++;
349 219         427 return $o->output_row();
350             } else {
351 68         120 $o->{count}[$i] = 0;
352 68 100       90 if ($i < $#{$o->{choices}}) {
  68         173  
353 67         112 $i++;
354             } else {
355 1         2 $i = 0;
356 1         4 return $o->output_row();
357             }
358             }
359             }
360             } else {
361 0         0 return defaults();
362             }
363             }
364             # Internal function returning next permutation
365             # as output_row(), repeating indefinitely.
366              
367             sub setup {
368 1     1   209 my ($o, $key, $aref) = @_;
369 1 50 33     13 if (exists $o->{$key} and ref($aref) eq "ARRAY") {
370 1         3 $o->{$key} = $aref;
371 1         4 $o->{initialized} = 0;
372             }
373 1         4 return;
374             }
375              
376             =head3 setup( key, array )
377              
378             The defaults provided by the PostScript::Graph::Sequence are chosen from arrays which may be redefined using this method. Note
379             that it is a B method and B a PostScript::Graph::Style method, and should typically be called
380             directly after the PostScript::Graph::Sequence object is created.
381              
382             Example
383            
384             use PostScript::Graph::Style;
385            
386             my $seq = new PostScript::Graph::Sequence();
387             $seq->setup( "red", [0, 0.5, 1] );
388              
389             C is always an array reference as in the example. C may be one of the following.
390              
391             red green blue
392             gray color width
393             dashes shape size
394            
395             Mostly, their arrays contain integers (0 to 1.0 for colours). The exceptions are C, C, C and
396             possibly C.
397              
398             See L for details on the arrays required for dashes. Suitable values for shape can be one of
399             these entries, taken from the default array.
400              
401             my $seq = new PostScript::Graph::Sequence();
402             $seq->setup( "shape",
403             [ qw(cross plus dot circle square diamond) ]);
404              
405             If the gray array is filled with decimals between 0 and 1 (inclusive), the result is varying shades
406             of grey. It is also possible to use arrays of red-green-blue colours:
407            
408             my $seq = new PostScript::Graph::Sequence();
409             $seq->setup( "color",
410             [ [ 0, 0, 0 ], # white
411             [ 0, 0, 1 ], # blue
412             [ 0, 1, 0 ], # green
413             [ 0, 1, 1 ], # cyan
414             [ 1, 0, 0 ], # red
415             [ 1, 0, 1 ], # mauve
416             [ 1, 1, 0 ], # yellow
417             [ 1, 1, 1 ], ]); # black
418              
419             my $gs = new PostScript::Graph::Style(
420             auto => [qw(color)],
421             bar => {},
422             );
423              
424             The full range of colours may be used provided that the 'bgnd_outline' style option has not been set. By default each
425             line, point and bar are outlined in the complementary colour to the background, making them stand out.
426              
427             More than one variable can be set of course. For example the following would ensure lines with 15 shades of
428             red-orange-yellow, if 'auto' was set to some combination of red, blue and green.
429              
430             my $seq = new PostScript::Graph::Sequence();
431             $seq->setup("red", [ 0.2, 1, 0.4, 0.8, 0.6 ]);
432             $seq->setup("green", [ 0, 0.8, 0.4 ]);
433             $seq->setup("blue", [ 0 ]);
434            
435             =cut
436              
437             sub auto {
438 0     0   0 my ($o, @list) = @_;
439 0         0 $o->{auto} = [ @list ];
440 0         0 $o->{initialized} = 0;
441             }
442              
443             =head3 auto( list )
444              
445             Specify which defaults are changed for each new style.
446              
447             The first feature mentioned will vary fastest from one style to the next while the last varies slowest. Any
448             features not mentioned will not be varied. See L for how to change the defaults for these
449             features.
450              
451             red green blue
452             gray color width
453             dashes shape size
454              
455             If not set directly, it may be set from the C option given to the first PostScript::Graph::Style object created
456             using this sequence.
457              
458             =cut
459              
460             sub reset {
461 0     0   0 shift()->{initialized} = 0;
462             }
463              
464             =head3 reset()
465              
466             Starts the sequence of defaults again.
467              
468             =cut
469              
470             sub new_style_id {
471 234     234   296 my $o = shift;
472 234         2470 $o->{styleid}++;
473 234         642 return $o->{styleid};
474             }
475              
476             sub id {
477 4     4   11 return shift()->{id};
478             }
479              
480             sub default {
481 197 100   197   521 our $default_seq = new PostScript::Graph::Sequence() unless (defined $default_seq);
482 197         304 return $default_seq;
483             }
484              
485             =head3 default()
486              
487             Return a fallback PostScript::Graph::Sequence. Note that these are global settings possibly called by many,
488             unrelated objects, so the sequences generated may not be predictable or even useful.
489              
490             =cut
491              
492             # The fallback sequence if none given
493             our $default_seq;
494              
495             =head2 Class Methods
496              
497             ### PostScript::Graph::Style
498              
499             =cut
500              
501             package PostScript::Graph::Style;
502              
503             our $default_style_id = 1;
504              
505             =head1 CONSTRUCTOR
506              
507             B
508              
509             C can either be a list of hash keys and values or a single hash reference. In both cases the hash must
510             have the same structure. There are a few principal keys and most of these refer to hashes holding a group of
511             options.
512              
513             It is B that at least one of C, C or C is given, even if the hashes are empty.
514             Otherwise no style settings will actually be output.
515              
516             =cut
517              
518             sub new {
519 234     234 1 5819 my $class = shift;
520 234         345 my $opt = {};
521 234 50       560 if (@_ == 1) { $opt = $_[0]; } else { %$opt = @_; }
  234         335  
  0         0  
522            
523 234         786 my $o = {};
524 234         596 bless( $o, $class );
525              
526             ## collect the defaults
527 234         265 my ($d, $seq);
528 234   66     1480 $o->{none} = (defined($opt->{auto}) and ref($opt->{auto}) ne "ARRAY");
529 234 50       507 if ($o->{none}) {
530 0         0 $d = PostScript::Graph::Sequence::defaults();
531 0         0 $o->{id} = $default_style_id++;
532             } else {
533 234 100       1749 $seq = defined($opt->{sequence}) ? $opt->{sequence} : PostScript::Graph::Sequence::default();
534 234         636 $d = $seq->create($opt->{auto});
535 234         447 $o->{seq} = $seq;
536 234         521 $o->{id} = $seq->new_style_id();
537             }
538            
539 234         545 $o->{label} = $opt->{label}; # for debugging
540 234 100       579 $o->{rel} = defined($opt->{changes_only}) ? $opt->{changes_only} : 1; # 'don't set everything'
541 234 50       644 $o->{same} = defined($opt->{bgnd_outline}) ? $opt->{bgnd_outline} : 0; # 'don't complement bgnd'
542 234 50       540 $o->{color} = defined($opt->{use_color}) ? $opt->{use_color} : 1; # 'not monochrome'
543 234 50       1138 my $color = $o->{color} ? [ $d->{red}, $d->{green}, $d->{blue} ] : $d->{gray};
544            
545             ## common options
546 234 100       1539 $color = defined($opt->{color}) ? $opt->{color} : $color;
547 234 50       532 my $width = defined($opt->{width}) ? $opt->{width} : $d->{width};
548 234 50       1303 my $dashes = defined($opt->{dashes}) ? $opt->{dashes} : $d->{dashes};
549              
550             ## line options
551 234         335 my $li = $opt->{line};
552 234 100       472 if ($li) {
553 20 100       50 my $lwidth = defined($li->{width}) ? $li->{width} : $width;
554 20 50       45 my $ldashes = defined($li->{dashes}) ? $li->{dashes} : $dashes;
555 20 100       94 $o->{locolor} = defined($li->{outer_color}) ? $li->{outer_color} : -1;
556 20 100       67 $o->{lowidth} = defined($li->{outer_width}) ? $li->{outer_width} : 2 * $lwidth;
557 20 100       64 $o->{lostyle} = defined($li->{outer_dashes}) ? $li->{outer_dashes} : $ldashes;
558            
559 20 50       82 $o->{licolor} = defined($li->{color}) ? $li->{color} : $color;
560 20 100       312 $o->{licolor} = defined($li->{inner_color}) ? $li->{inner_color} : $o->{licolor};
561 20 100       54 $o->{liwidth} = defined($li->{inner_width}) ? $li->{inner_width} : $lwidth;
562 20 100       52 $o->{listyle} = defined($li->{inner_dashes}) ? $li->{inner_dashes} : $ldashes;
563 20         52 $o->{use_line} = 1;
564             }
565            
566             ## bar options
567 234         311 my $bl = $opt->{bar};
568 234 100       1301 if ($bl) {
569 204 50       394 my $bwidth = defined($bl->{width}) ? $bl->{width} : $width;
570 204 50       484 my $bdashes = defined($bl->{dashes}) ? $bl->{dashes} : [];
571 204 50       661 $o->{bocolor} = defined($bl->{outer_color}) ? $bl->{outer_color} : -1;
572 204 50       548 $o->{bowidth} = defined($bl->{outer_width}) ? $bl->{outer_width} : 2 * $bwidth;
573 204 50       718 $o->{bostyle} = defined($li->{outer_dashes}) ? $li->{outer_dashes} : $bdashes;
574            
575 204 100       741 $o->{bicolor} = defined($bl->{color}) ? $bl->{color} : $color;
576 204 50       428 $o->{bicolor} = defined($bl->{inner_color}) ? $bl->{inner_color} : $o->{bicolor};
577 204 50       484 $o->{biwidth} = defined($bl->{inner_width}) ? $bl->{inner_width} : $bwidth;
578 204 50       449 $o->{bistyle} = defined($li->{inner_dashes}) ? $li->{inner_dashes} : $bdashes;
579 204         450 $o->{use_bar} = 1;
580             }
581              
582             ## point options
583 234         352 my $pp = $opt->{point};
584 234 100       455 if ($pp) {
585 16 50       38 my $pwidth = defined($pp->{width}) ? $pp->{width} : $width;
586 16 50       116 my $pdashes = defined($bl->{dashes}) ? $bl->{dashes} : [];
587 16 100       136 $o->{ppsize} = defined($pp->{size}) ? $pp->{size} : $d->{size};
588 16 50       60 $o->{ppdx} = defined($pp->{x_offset}) ? $pp->{x_offset} : 0;
589 16 50       60 $o->{ppdy} = defined($pp->{y_offset}) ? $pp->{y_offset} : 0;
590 16 100       56 $o->{ppshape} = defined($pp->{shape}) ? $pp->{shape} : $d->{shape};
591            
592 16 100       42 $o->{pocolor} = defined($pp->{outer_color}) ? $pp->{outer_color} : -1;
593 16 100       133 $o->{powidth} = defined($pp->{outer_width}) ? $pp->{outer_width} : 2 * $pwidth;
594 16 50       45 $o->{postyle} = defined($pp->{outer_dashes}) ? $pp->{outer_dashes} : $pdashes;
595            
596 16 50       41 $o->{picolor} = defined($pp->{color}) ? $pp->{color} : $color;
597 16 100       43 $o->{picolor} = defined($pp->{inner_color}) ? $pp->{inner_color} : $o->{picolor};
598 16 100       50 $o->{piwidth} = defined($pp->{inner_width}) ? $pp->{inner_width} : $pwidth;
599 16 50       40 $o->{pistyle} = defined($pp->{inner_dashes}) ? $pp->{inner_dashes} : $pdashes;
600 16         32 $o->{use_point} = 1;
601             }
602              
603 234         1371 return $o;
604             }
605              
606             =head2 Global settings
607              
608             These are mainly concerned with how the defaults are generated for each new PostScript::Graph::Style object.
609              
610             =head3 auto
611              
612             Setting C to the string 'none' prevents the automatic generation of defaults. Of course the same result
613             could be obtained by setting every option so the defaults are never needed. Otherwise this may be a list of
614             features (see the B method for PostScript::Graph::Sequence, above).
615              
616             =head3 changes_only
617              
618             Set this to 0 if you need every style parameter written out to postscript. If this is 1, only the changes from
619             the previous style settings are added to the file. (Default: 1)
620              
621             =head3 color
622              
623             Set default colour for lines, bars and points.
624              
625             =head3 label
626              
627             A string identifying the style, added to the id(). The interaction between styles can get quite complex,
628             especially when using more than one sequence. This label becomes part of the C method and makes styles easier
629             to track.
630              
631             =head3 bgnd_outline
632              
633             By default, the outer colour is the complement of the background (see L). Setting this to 1 makes
634             the outer colour the same as the background.
635              
636             =head3 sequence
637              
638             This identifies a sequence of default values. If this is not defined (but 'auto' is not 'none'), a new sequence
639             would be created with each call resulting in the same style every time.
640              
641             =head3 use_color
642              
643             Set this to 0 to use shades of grey for monochrome printers.
644              
645             This also must be set to 0 to cycle through user defined colours. See L for how to set
646             those. This switch actually determines whether the colour value is taken from the gray array or a composite of
647             the red, green and blue arrays. So putting the custom colours into 'gray' and setting C to 0 reads these.
648             The internal postscript code handles each format interchangeably, so the result is coloured gray!
649              
650             =head3 width
651              
652             Set default line width for lines, bars and points.
653              
654             =head2 Graphic settings
655              
656             The options described below belong within C, C or C sub-hashes unless otherwise mentioned.
657             For example, referring to the descriptions for C and C:
658              
659             line => { color => ... } valid
660             point => { color => ... } valid
661            
662             line => { size => ... } NOT valid
663             point => { size => ... } valid
664              
665             The sub-hashes are significant. B if that feature is to be used, even if the sub-hash is
666             empty. Otherwise, no postscript values of that type will be defined.
667            
668             All C options within these sub-hashes take either a single greyscale decimal or a reference to an array
669             holding decimals for red, green and blue components. All decimals should be between 0 and 1.0 inclusive.
670              
671             color => 1 white
672             outer_color => 0 black
673             inner_color => [1, 0, 0] red
674            
675             B
676              
677             $ps = new PostScript::Graph::Style(
678             auto => "none",
679             line => {
680             width => 2,
681             inner_color => [ 1, 0.6, 0.4 ],
682             }
683             point => {
684             shape => "diamond",
685             size => 12,
686             color => [ 1, 0.8, 0.8 ],
687             inner_width => 2,
688             outer_width => 1,
689             }
690             );
691            
692             =head3 color
693              
694             A synonym for C. See L.
695              
696             =head3 dashes
697              
698             Set both inner and outer dash patterns. See L.
699              
700             =head3 inner_color
701              
702             The main colour of the line or point. See L.
703              
704             =head3 inner_dashes
705              
706             This array ref holds values that determine any dash pattern. They are repeated as needed to give the size 'on'
707             then 'off'. Examples are the best way to describe this.
708              
709             inner_dashes => [] -------------------------
710             inner_dashes => [ 3,3 ] --- --- --- --- -
711             inner_dashes => [ 5,2,1,2 ] ----- - ----- - -----
712              
713             Only available for lines.
714              
715             =head3 inner_width
716              
717             The size of the central portion of the line. Although this can be set of points, C is more likely to be
718             what you want. Probably should be no less than 0.1 to be visible - 0.24 on a 300dpi device or 1 on 72dpi.
719             (Default: 0.5)
720              
721             When used in conjunction with C, setting inner and outer widths to the same value produces
722             a two-colour dash.
723              
724             =head3 outer_color
725              
726             Colour for the 'edges' of the line or point. To be visible C must be greater than .
727             (Default: -1)
728              
729             Note that the default is NOT a valid postscript value (although C handles it fine. See
730             L. If B is called later, it fills all colours marked thus
731             with a background colour now known.
732              
733             =head3 outer_dashes
734              
735             If this is unset, inner lines alternate with the outer colour. To get a dashed line, this should be the same
736             value as C. (Default: "[]")
737              
738             Only available for lines.
739              
740             =head3 outer_width
741              
742             Total width of the line or point, including the border (which may be invisible, depending on colour). The edge is
743             only visible if this is at least 0.5 greater than C. 2 or 3 times C is often best.
744             (Default: 1.5)
745              
746             When using the C point shape, this should be quite small to allow the line to be visible inside the
747             circle.
748              
749             =head3 shape
750              
751             This string specifies the built-in shape to use for points. Suitable values are:
752              
753             north south east west
754             plus cross dot circle
755             square diamond
756              
757             (Default: "dot")
758              
759             Only available for points.
760              
761             =head3 size
762              
763             Width across the inner part of a point shape. (Default: 5)
764              
765             Not available for lines.
766              
767             =head3 width
768              
769             Set the inner line width. The outer width is also set to twice this value.
770              
771             =head3 x_offset
772              
773             Move the active position of a point from the centre to somewhere else. Useful for arrows.
774              
775             Example
776              
777             By default, a left-pointing arrow will be drawn centrally over the specified point. However, specifying an
778             C of 0.75 the size, it will now be drawn with the arrow tip at the point instead (the left edge of the
779             icon). In practice, making the offset a little larger allows for the unbevelled point which becomes quite
780             pronounced as the line width increases.
781              
782             point => {
783             shape => 'east',
784             size => 6,
785             x_offset => -6,
786             }
787              
788             =head3 y_offset
789              
790             Move the active position of a point from the centre to somewhere else. Useful for arrows.
791              
792             Example
793              
794             By default, an up-pointing arrow will be drawn centrally over the specified point. However, specifying an
795             C of 0.75 the size, it will now be drawn with the arrow tip at the point instead (the top edge of the
796             icon).
797              
798             point => {
799             shape => 'north',
800             size => 6,
801             y_offset => 6,
802             }
803              
804             =cut
805              
806             sub number_value {
807 828     828 0 1193 my ($o, $name) = @_;
808 828         22575 my $res = "/$name ". $o->{$name} . " def\n";
809 828         1323 my $prev = $o->{prev};
810 828 100 66     3501 if ($o->{rel} and $prev) {
811 787 50       1998 my $new = defined($o->{$name}) ? $o->{$name} : 'undef';
812 787 50       1703 my $old = defined($prev->{$name}) ? $prev->{$name} : 'undef';
813 787 100       4772 $res = '' if ($new eq $old);
814             }
815 828         1670 return $res;
816             }
817             # Internal method
818             # expects variable name and hash key
819             # string to add to postscript code
820              
821             sub shape_value {
822 16     16 0 24 my ($o, $name) = @_;
823 16         49 my $res = "/$name /make_$o->{$name} cvx def\n";
824 16         29 my $prev = $o->{prev};
825 16 100 66     82 if ($o->{rel} and $prev) {
826 13   50     40 my $new = $o->{$name} || '';
827 13   50     48 my $old = $prev->{$name} || '';
828 13 100       47 $res = '' if ($new eq $old);
829             }
830 16         37 return $res;
831             }
832              
833             sub array_value {
834 1560     1560 0 2164 my ($o, $name) = @_;
835 1560         4971 my $res = "/$name ". str($o->{$name}) . " def\n";
836 1560         18506 my $prev = $o->{prev};
837 1560 100 66     7144 if ($o->{rel} and $prev) {
838 1496   100     4007 my $new = str($o->{$name}) || '';
839 1496   100     31049 my $old = str($prev->{$name}) || '';
840 1496 100 100     39494 $res = '' if ($new eq $old and $old ne '');
841             }
842 1560         3954 return $res;
843             }
844              
845             =head1 OBJECT METHODS
846              
847             =cut
848              
849             sub write {
850 384     384 1 591 my ($o, $ps) = @_;
851 384         991 $o->ps_functions($ps); # only active on first call
852            
853 384         736232 $o->{prev} = $ps->get_page_variable('PostScript::Graph::Style');
854             #warn '% style=' . $o->id() . ', prev=' . ($o->{prev} ? $o->{prev}->id() : 'undef') . "\n";
855            
856 384         2324 my $settings = "gstyledict begin\n";
857 384 100       1048 $settings .= $o->array_value ('locolor') if ($o->{use_line});
858 384 100       920 $settings .= $o->number_value('lowidth') if ($o->{use_line});
859 384 100       793 $settings .= $o->array_value ('lostyle') if ($o->{use_line});
860 384 100       6424 $settings .= $o->array_value ('licolor') if ($o->{use_line});
861 384 100       869 $settings .= $o->number_value('liwidth') if ($o->{use_line});
862 384 100       753 $settings .= $o->array_value ('listyle') if ($o->{use_line});
863 384 100       798 $settings .= $o->shape_value ('ppshape') if ($o->{use_point});
864 384 100       762 $settings .= $o->number_value('ppsize') if ($o->{use_point});
865 384 100       784 $settings .= $o->number_value('ppdx') if ($o->{use_point});
866 384 100       789 $settings .= $o->number_value('ppdy') if ($o->{use_point});
867 384 100       789 $settings .= $o->number_value('powidth') if ($o->{use_point});
868 384 100       753 $settings .= $o->array_value ('pocolor') if ($o->{use_point});
869 384 100       792 $settings .= $o->array_value ('postyle') if ($o->{use_point});
870 384 100       761 $settings .= $o->array_value ('picolor') if ($o->{use_point});
871 384 100       777 $settings .= $o->number_value('piwidth') if ($o->{use_point});
872 384 100       715 $settings .= $o->array_value ('pistyle') if ($o->{use_point});
873 384 100       1234 $settings .= $o->array_value ('bocolor') if ($o->{use_bar});
874 384 100       1450 $settings .= $o->number_value('bowidth') if ($o->{use_bar});
875 384 100       1264 $settings .= $o->array_value ('bostyle') if ($o->{use_bar});
876 384 100       1309 $settings .= $o->array_value ('bicolor') if ($o->{use_bar});
877 384 100       1392 $settings .= $o->number_value('biwidth') if ($o->{use_bar});
878 384 100       1265 $settings .= $o->array_value ('bistyle') if ($o->{use_bar});
879 384         564 $settings .= "end\n";
880              
881 384         1209 $ps->add_to_page( $settings );
882 384         61947 $ps->set_page_variable('PostScript::Graph::Style', $o);
883             }
884              
885             =head3 write( ps )
886              
887             Write style settings to the PostScript::File object. This is a convenient way of setting all the postscript
888             variables at the same time as it calls each of the line, point and bar variants below.
889              
890             All of the postscript variables are set if the constructor option C was
891             set to 0. Otherwise, only those values that are different from the previous style are written out.
892              
893             See L for a list of the variables set.
894              
895             =cut
896              
897             sub background {
898 370     370 1 604 my ($o, $col, $same) = @_;
899 370 50       1326 $same = $o->{same} unless (defined $same);
900              
901 370 50       804 unless ($same) {
902 370 100       786 if (ref($col) eq "ARRAY") {
903 53         131 $col->[0] = 1 - $col->[0];
904 53         87 $col->[1] = 1 - $col->[1];
905 53         180 $col->[2] = 1 - $col->[2];
906             } else {
907 317         473 $col = 1 - $col;
908             }
909             }
910 370 100 100     1218 $o->{locolor} = $col if ($o->{use_line} and $o->{locolor} < 0);
911 370 100 100     1273 $o->{pocolor} = $col if ($o->{use_point} and $o->{pocolor} < 0);
912 370 100 100     4475 $o->{bocolor} = $col if ($o->{use_bar} and $o->{bocolor} < 0);
913             }
914              
915             =head3 background( grey | arrayref [, same] )
916              
917             The default outer colour setting (-1) is interpreted as 'use complement to graphpaper background'. Of course, it
918             is not possible to bind that until the graphpaper object exists. Calling this function sets all outer colour
919             values to be a complement of the colour given, unless C is set to non-zero. If not given, C takes on
920             the value given to the constuctor or 0 by default.
921              
922             =cut
923              
924             sub sequence {
925 0     0 1 0 shift()->{seq};
926             }
927              
928             sub id {
929 4     4 0 39 my $o = shift;
930 4 50       31 my $seqid = $o->{seq} ? $o->{seq}->id() : "";
931 4 50       11 my $ownid = $o->{id} ? $o->{id} : "";
932 4 50       12 my $label = $o->{label} ? " ($o->{label})" : '';
933 4 50       10 my $line = $o->{use_line} ? 'L' : '-';
934 4 50       12 my $point = $o->{use_point} ? 'P' : '-';
935 4 50       10 my $bar = $o->{use_bar} ? 'B' : '-';
936 4         17 return "$seqid.$ownid$label $line$point$bar";
937             }
938              
939             sub bgnd_outline {
940 0     0 1 0 shift()->{same};
941             }
942              
943             sub color {
944 0     0 1 0 shift()->{color};
945             }
946              
947             sub line_outer_color {
948 0     0 0 0 shift()->{locolor};
949             }
950              
951             sub line_outer_width {
952 16     16 0 48 shift()->{lowidth};
953             }
954              
955             sub line_outer_dashes {
956 0     0 0 0 shift()->{lostyle};
957             }
958              
959             sub line_inner_color {
960 0     0 0 0 shift()->{licolor};
961             }
962              
963             sub line_inner_width {
964 0     0 0 0 shift()->{liwidth};
965             }
966              
967             sub line_inner_dashes {
968 0     0 0 0 shift()->{listyle};
969             }
970              
971             sub bar_outer_color {
972 0     0 0 0 shift()->{bocolor};
973             }
974              
975             sub bar_outer_width {
976 354     354 0 1145 shift()->{bowidth};
977             }
978              
979             sub bar_inner_color {
980 204     204 0 733 shift()->{bicolor};
981             }
982              
983             sub bar_inner_width {
984 0     0 0 0 shift()->{biwidth};
985             }
986              
987             sub point_size {
988 16     16 0 48 shift()->{ppsize};
989             }
990              
991             sub point_shape {
992 0     0 0 0 shift()->{ppshape};
993             }
994              
995             sub point_outer_color {
996 0     0 0 0 shift()->{pocolor};
997             }
998              
999             sub point_outer_width {
1000 0     0 0 0 shift()->{powidth};
1001             }
1002              
1003             sub point_inner_color {
1004 0     0 0 0 shift()->{picolor};
1005             }
1006             sub point_inner_width {
1007 0     0 0 0 shift()->{piwidth};
1008             }
1009              
1010             sub use_line {
1011 0     0 1 0 return shift()->{use_line};
1012             }
1013              
1014             =head2 use_line
1015              
1016             Return 1 if line settings are used.
1017              
1018             =cut
1019              
1020             sub use_point {
1021 0     0 1 0 return shift()->{use_point};
1022             }
1023              
1024             =head2 use_point
1025              
1026             Return 1 if point settings are used.
1027              
1028             =cut
1029              
1030             sub use_bar {
1031 0     0 1 0 return shift()->{use_bar};
1032             }
1033              
1034             =head2 use_bar
1035              
1036             Return 1 if bar settings are used.
1037              
1038             =cut
1039              
1040             =head1 POSTSCRIPT CODE
1041              
1042             =head2 PostScript variables
1043              
1044             These are set within the 'gstyledict' dictionary. All C<...color> variables are either a decimal or an array
1045             holding red, green and blue values. They are best passed to L.
1046              
1047             PostScript Perl method
1048             ========== ===========
1049             locolor line_outer_color
1050             lowidth line_outer_width
1051             lostyle line_outer_dashes
1052             licolor line_inner_color
1053             liwidth line_inner_width
1054             listyle line_inner_dashes
1055            
1056             ppshape point_shape
1057             ppsize point_size
1058             pocolor point_outer_color
1059             powidth point_outer_width
1060             picolor point_inner_color
1061             piwidth point_inner_width
1062            
1063             bocolor bar_outer_color
1064             bowidth bar_outer_width
1065             bicolor bar_inner_color
1066             biwidth bar_inner_width
1067              
1068             =head2 Setting Styles
1069              
1070             Once B has been called to update the postscript variables, the graphic environment must be set to use them.
1071             The GraphStyle resource provides a number of functions for this.
1072              
1073             =head3 line_inner
1074              
1075             Sets the colour, width and dash pattern for a line.
1076              
1077             =head3 line_outer
1078              
1079             Sets the colour, width and dash pattern for a line's edge.
1080              
1081             =head3 point_inner
1082              
1083             Sets the colour and width for a point.
1084              
1085             =head3 point_outer
1086              
1087             Sets the colour and width for a point's edge.
1088              
1089             =head3 bar_inner
1090              
1091             Sets the colour and width for a bar.
1092              
1093             =head3 bar_outer
1094              
1095             Sets the colour and width for a bar's edge.
1096              
1097             =head2 Drawing Functions
1098              
1099             The functions which draw the shapes all remove 'x y' from the stack. They use a variable 'ppsize' which should be
1100             the total width of the shape, although the elongated shapes are 1.5 times this on the longer side.
1101              
1102             make_plus make_north
1103             make_cross make_south
1104             make_dot make_east
1105             make_circle make_west
1106             make_square
1107             make_diamond
1108              
1109             =cut
1110              
1111             sub ps_functions {
1112 384     384 1 512 my ($class, $ps) = @_;
1113            
1114 384         526 my $name = "GraphStyle";
1115 384 100       1334 $ps->add_function( $name, <has_function($name));
1116             /gstyledict 22 dict def
1117             gstyledict begin
1118             /ppdx 0 def
1119             /ppdy 0 def
1120            
1121             % _ => _
1122             /line_outer {
1123             gpaperdict begin gstyledict begin
1124             locolor gpapercolor
1125             lowidth setlinewidth
1126             lostyle 0 setdash
1127             2 setlinejoin
1128             end end
1129             } bind def
1130            
1131             % _ => _
1132             /line_inner {
1133             gpaperdict begin gstyledict begin
1134             licolor gpapercolor
1135             liwidth setlinewidth
1136             listyle 0 setdash
1137             2 setlinejoin
1138             end end
1139             } bind def
1140            
1141             % _ => _
1142             /point_outer {
1143             gpaperdict begin gstyledict begin
1144             pocolor gpapercolor
1145             powidth setlinewidth
1146             [ ] 0 setdash
1147             0 setlinejoin
1148             end end
1149             } bind def
1150            
1151             % _ => _
1152             /point_inner {
1153             gpaperdict begin gstyledict begin
1154             picolor gpapercolor
1155             piwidth setlinewidth
1156             [ ] 0 setdash
1157             0 setlinejoin
1158             end end
1159             } bind def
1160              
1161             % _ => _
1162             /bar_outer {
1163             gpaperdict begin gstyledict begin
1164             bocolor gpapercolor
1165             bowidth setlinewidth
1166             [ ] 0 setdash
1167             end end
1168             } bind def
1169            
1170             % _ => _
1171             /bar_inner {
1172             gpaperdict begin gstyledict begin
1173             bicolor gpapercolor
1174             biwidth setlinewidth
1175             [ ] 0 setdash
1176             end end
1177             } bind def
1178              
1179             % _ x y => _
1180             /make_plus {
1181             gpaperdict begin gstyledict begin
1182             newpath
1183             exch ppdx add exch ppdy add
1184             moveto
1185             /dx ppsize 0.5 mul def
1186             /dy ppsize 0.5 mul def
1187             1 -1 rmoveto
1188             dx 0 rlineto
1189             0 1 rlineto
1190             dx neg 0 rlineto
1191             0 dy rlineto
1192             -1 0 rlineto
1193             0 dy neg rlineto
1194             dx neg 0 rlineto
1195             0 -1 rlineto
1196             dx 0 rlineto
1197             0 dy neg rlineto
1198             1 0 rlineto
1199             0 dy rlineto
1200             closepath
1201             end end
1202             } bind def
1203            
1204             % x y => _
1205             /make_cross {
1206             gpaperdict begin gstyledict begin
1207             newpath
1208             exch ppdx add exch ppdy add
1209             moveto
1210             /dx ppsize 0.7071 mul def
1211             /dy ppsize 0.7071 mul def
1212             dx dy rlineto
1213             dx neg dy neg rlineto
1214             dx neg dy rlineto
1215             dx dy neg rlineto
1216             dx neg dy neg rlineto
1217             dx dy rlineto
1218             dx dy neg rlineto
1219             dx neg dy rlineto
1220             closepath
1221             end end
1222             } bind def
1223            
1224             % x y => _
1225             /make_dot {
1226             gpaperdict begin gstyledict begin
1227             newpath
1228             exch ppdx add exch ppdy add
1229             1 index ppsize 2 div add 1 index moveto
1230             ppsize 2 div 0 360 arc
1231             closepath
1232             end end
1233             } bind def
1234              
1235             % x y => _
1236             /make_circle {
1237             gpaperdict begin gstyledict begin
1238             newpath
1239             exch ppdx add exch ppdy add
1240             1 index ppsize 0.6 mul add 1 index moveto
1241             2 copy ppsize 0.6 mul 0 360 arc
1242             1 index ppsize 0.5 mul add 1 index moveto
1243             ppsize 0.5 mul 0 360 arc
1244             closepath
1245             end end
1246             } bind def
1247              
1248             % x y => _
1249             /make_square {
1250             gpaperdict begin gstyledict begin
1251             newpath
1252             exch ppdx add exch ppdy add
1253             ppsize 2 div add exch
1254             ppsize 2 div add exch moveto
1255             0 ppsize neg rlineto
1256             ppsize neg 0 rlineto
1257             0 ppsize rlineto
1258             closepath
1259             end end
1260             } bind def
1261              
1262             % x y => _
1263             /make_diamond {
1264             gpaperdict begin gstyledict begin
1265             newpath
1266             exch ppdx add exch ppdy add
1267             /dx ppsize 0.5 mul def
1268             /dy ppsize 0.75 mul def
1269             dy add moveto
1270             dx neg dy neg rlineto
1271             dx dy neg rlineto
1272             dx dy rlineto
1273             closepath
1274             end end
1275             } bind def
1276              
1277             % x y => _
1278             /make_north {
1279             gpaperdict begin gstyledict begin
1280             newpath
1281             exch ppdx add exch ppdy add
1282             /dx ppsize 0.3333 mul def
1283             /dy ppsize 0.5 mul def
1284             exch dx add exch moveto
1285             dx neg dy rlineto
1286             dx neg dy neg rlineto
1287             dx 2 div 0 rlineto
1288             0 dy neg rlineto
1289             dx 0 rlineto
1290             0 dy rlineto
1291             closepath
1292             end end
1293             } bind def
1294              
1295             % x y => _
1296             /make_south {
1297             gpaperdict begin gstyledict begin
1298             newpath
1299             exch ppdx add exch ppdy add
1300             /dx ppsize 0.3333 mul def
1301             /dy ppsize 0.5 mul def
1302             exch dx sub exch moveto
1303             dx dy neg rlineto
1304             dx dy rlineto
1305             dx neg 2 div 0 rlineto
1306             0 dy rlineto
1307             dx neg 0 rlineto
1308             0 dy neg rlineto
1309             closepath
1310             end end
1311             } bind def
1312              
1313             % x y => _
1314             /make_east {
1315             gpaperdict begin gstyledict begin
1316             newpath
1317             exch ppdx add exch ppdy add
1318             /dx ppsize 0.5 mul def
1319             /dy ppsize 0.3333 mul def
1320             dy add moveto
1321             dx dy neg rlineto
1322             dx neg dy neg rlineto
1323             0 dy 2 div rlineto
1324             dx neg 0 rlineto
1325             0 dy rlineto
1326             dx 0 rlineto
1327             closepath
1328             end end
1329             } bind def
1330              
1331             % x y => _
1332             /make_west {
1333             gpaperdict begin gstyledict begin
1334             newpath
1335             exch ppdx add exch ppdy add
1336             /dx ppsize 0.5 mul def
1337             /dy ppsize 0.3333 mul def
1338             dy add moveto
1339             dx neg dy neg rlineto
1340             dx dy neg rlineto
1341             0 dy 2 div rlineto
1342             dx 0 rlineto
1343             0 dy rlineto
1344             dx neg 0 rlineto
1345             closepath
1346             end end
1347             } bind def
1348              
1349             end
1350             END_FUNCTIONS
1351             }
1352              
1353             =head2 ps_functions
1354              
1355             This class function provides the PostScript dictionary C and code defining the specialist Style functions.
1356              
1357             =cut
1358              
1359             =head1 BUGS
1360              
1361             Please report any you find to the author.
1362              
1363             =head1 AUTHOR
1364              
1365             Chris Willmot, chris@willmot.org.uk
1366              
1367             =head1 SEE ALSO
1368              
1369             L, L and L for the other modules in this suite.
1370              
1371             L, L and L for modules that use this one.
1372              
1373             =cut
1374              
1375             1;