File Coverage

blib/lib/PDL/Graphics/Simple/PGPLOT.pm
Criterion Covered Total %
statement 24 187 12.8
branch 3 86 3.4
condition 1 26 3.8
subroutine 6 9 66.6
pod 0 3 0.0
total 34 311 10.9


line stmt bran cond sub pod time code
1              
2             ######################################################################
3             ######################################################################
4             ######################################################################
5             ###
6             ###
7             ### PGPLOT interface to PDL::Graphics::Simple.
8             ###
9             ### See the PDL::Graphics::Simple docs for details
10             ###
11             ##
12             #
13              
14             package PDL::Graphics::Simple::PGPLOT;
15              
16 1     1   8 use strict;
  1         2  
  1         50  
17 1     1   7 use warnings;
  1         2  
  1         81  
18 1     1   7 use File::Temp qw/tempfile/;
  1         1  
  1         133  
19 1     1   9 use PDL::Options q/iparse/;
  1         2  
  1         95  
20              
21 1     1   8 use PDL;
  1         2  
  1         7  
22              
23             our $mod = {
24             shortname => 'pgplot',
25             module=>'PDL::Graphics::Simple::PGPLOT',
26             engine => 'PDL::Graphics::PGPLOT::Window',
27             synopsis=> 'PGPLOT (venerable but trusted)',
28             pgs_api_version=> '1.012',
29             };
30             PDL::Graphics::Simple::register( $mod );
31             print $@;
32              
33             sub check {
34 1     1 0 3 my $force = shift;
35 1 50       26 $force = 0 unless(defined($force));
36 1 50 33     9 return $mod->{ok} unless( $force or !defined($mod->{ok}) );
37 1         3 eval { require PDL::Graphics::PGPLOT::Window; PDL::Graphics::PGPLOT::Window->import; };
  1         197  
  0         0  
38 1 50       7 if ($@) {
39 1         5 $mod->{ok} = 0;
40 1         4 $mod->{msg} = $@;
41 1         6 return 0;
42             }
43             # Module loaded OK, now try to extract valid devices from it
44 0           eval {
45 0           my %devs;
46 0           PGPLOT::pgqndt(my $n);
47 0           for my $count (1..$n) {
48 0           PGPLOT::pgqdt($count,my ($type,$v1,$descr,$v2,$v3));
49 0           $devs{substr $type, 1} = 1; # chop off "/"
50             }
51 0           $mod->{devices} = \%devs;
52             };
53 0 0         if ($@) {
54 0           $mod->{ok} = 0;
55 0           $mod->{msg} = $@;
56 0           return 0;
57             }
58 0           delete $mod->{disp_dev};
59 0 0 0       if ($ENV{PDL_SIMPLE_DEVICE} || $ENV{PGPLOT_DEV}) {
60 0   0       $mod->{disp_dev} = $ENV{PDL_SIMPLE_DEVICE} || $ENV{PGPLOT_DEV};
61 0           $mod->{disp_dev} =~ s#^/+##;
62             } else {
63 0           TRY:for my $try (qw/XWINDOW XSERVE CGW GW/) {
64 0 0         if ($mod->{devices}->{$try}) {
65 0           $mod->{disp_dev} = $try;
66 0           last TRY;
67             }
68             }
69             }
70 0 0         unless (exists($mod->{disp_dev})) {
71 0           $mod->{ok} = 0;
72 0           $mod->{msg} = "Couldn't identify a PGPLOT display device -- giving up.\n";
73 0           return 0;
74             }
75 0 0         unless ($mod->{devices}{VCPS}) {
76 0           $mod->{ok} = 0;
77 0           $mod->{msg} = "Couldn't find the VCPS file-output device -- giving up.\n";
78 0           return 0;
79             }
80 0           $mod->{pgplotpm_version} = $PGPLOT::VERSION;
81 0           { PGPLOT::pgqinf('VERSION', $mod->{pgplot_version}, my $len); }
  0            
82 0           $mod->{ok} = 1;
83 0           return 1;
84             }
85              
86             ##########
87             # PDL::Graphics::Simple::PGPLOT::new
88             our $new_defaults ={
89             size => [8,6,'in'],
90             type => '',
91             output=>'',
92             multi=>undef
93             };
94              
95             our $filetypes = {
96             png => 'PNG',
97             ps => 'VCPS'
98             };
99              
100             sub new {
101 0     0 0   my $pkg = shift;
102 0           my $opt_in = shift;
103 0           my $opt = { iparse( $new_defaults, $opt_in ) };
104              
105             # Force a recheck on failure, in case the user fixed PGPLOT.
106             # Also loads PDL::Graphics::PGPLOT::Window.
107 0 0         unless(check()) {
108 0 0         die "$mod->{shortname} appears nonfunctional: $mod->{msg}\n" unless(check(1));
109             }
110              
111             # Figure the device name and size to feed to PGPLOT.
112             # size has already been regularized.
113 0           my $conv_tempfile;
114             my $dev;
115              
116 0 0         if( $opt->{type} =~ m/^i/i) {
117 0   0       $dev = ( $opt->{output} // "" ) . "/$mod->{disp_dev}";
118             } else {
119 0           my $ext;
120              
121 0 0 0       if($PDL::VERSION < 3 and ($PDL::VERSION > 2.1 or $PDL::VERSION < 2.005)) {
      0        
122 0           print STDERR "WARNING - file output shapes vary under PDL < 2.005 (early version: $PDL::VERSION)\n";
123             }
124              
125 0 0         if( $opt->{output} =~ m/\.(\w{2,4})$/ ) {
126 0           $ext = $1;
127             } else {
128 0           $ext = 'png';
129 0           $opt->{output} .= ".png";
130             }
131              
132 0 0 0       unless ($filetypes->{$ext} and $mod->{devices}{$filetypes->{$ext}}) {
133 0           my($fh);
134 0           ($fh, $conv_tempfile) = tempfile('pgs_pgplot_XXXX');
135 0           close $fh;
136 0           unlink $conv_tempfile; # just to be sure...
137 0           $conv_tempfile .= ".ps";
138 0           $dev = "$conv_tempfile/VCPS";
139             } else {
140 0           $dev = "$opt->{output}/$filetypes->{$ext}";
141             }
142             }
143              
144 0           $ENV{PGPLOT_PS_WIDTH} = $opt->{size}[0] * 1000;
145 0           $ENV{PGPLOT_PS_HEIGHT} = $opt->{size}[1] * 1000;
146              
147 0           my @params = (size => [@{$opt->{size}}[0,1]]);
  0            
148             push @params, nx=>$opt->{multi}[0], ny=>$opt->{multi}[1]
149 0 0         if defined $opt->{multi};
150 0           my $pgw = pgwin( $dev, { @params } );
151 0           my $me = { opt=>$opt, conv_fn=>$conv_tempfile, obj=>$pgw };
152 0           return bless $me;
153             }
154              
155             our $pgplot_methods = {
156             polylines => 'lines',
157             'lines' => 'line',
158             'bins' => 'bin',
159             'points' => 'points',
160             'errorbars' => sub {
161             my ($me, $ipo, $data, $ppo) = @_;
162             $me->{obj}->points($data->[0],$data->[1],$ppo);
163             $me->{obj}->errb($data->[0],$data->[1],$data->[2]);
164             },
165             'limitbars'=> sub {
166             my ($me, $ipo, $data, $ppo) = @_;
167             # use XY absolute error form, but with X errorbars right on the point
168             $me->{obj}->points($data->[0],$data->[1],$ppo);
169             my $z = zeroes($data->[0]);
170             $me->{obj}->errb($data->[0],$data->[1], $z, $z, -($data->[2]-$data->[1]), $data->[3]-$data->[1], $ppo);
171             },
172             'image' => 'imag',
173             'contours' => 'cont',
174             fits => 'fits_imag',
175             'circles'=> sub {
176             my ($me,$ipo,$data,$ppo) = @_;
177             $ppo->{filltype}='outline';
178             $me->{obj}->tcircle(@$data, $ppo);
179             },
180             'labels'=> sub {
181             my ($me,$ipo,$data,$ppo) = @_;
182             for my $i (0..$data->[0]->dim(0)-1) {
183             my $s = $data->[2]->[$i];
184             my $j = 0.0;
185             if ( $s =~ s/^([\<\|\>\ ])// ) {
186             $j = 0.5 if($1 eq '|');
187             $j = 1.0 if($1 eq '>');
188             }
189             $me->{obj}->text( $s, $data->[0]->at($i), $data->[1]->at($i), {JUSTIFICATION=>$j} );
190             }
191             }
192             };
193              
194             sub plot {
195 0     0 0   my $me = shift;
196 0           my $ipo = shift;
197 0           my $po = {};
198 0 0         $po->{title} = $ipo->{title} if defined $ipo->{title};
199 0 0         $po->{xtitle} = $ipo->{xlabel} if defined $ipo->{xlabel};
200 0 0         $po->{ytitle} = $ipo->{ylabel} if defined $ipo->{ylabel};
201 0 0         $po->{justify} = $ipo->{justify} if defined $ipo->{justify};
202              
203 0           my %color_opts;
204 0 0         if (defined $ipo->{crange}) {
205 0 0         $color_opts{MIN} = $ipo->{crange}[0] if defined $ipo->{crange}[0];
206 0 0         $color_opts{MAX} = $ipo->{crange}[1] if defined $ipo->{crange}[1];
207             }
208              
209 0 0 0       if ($ipo->{oplot} and $me->{opt}->{type} =~ m/^f/i) {
210 0           die "The PGPLOT engine does not yet support oplot for files. Instead, \nglom all your lines together into one call to plot.\n";
211             }
212              
213 0 0         unless ($ipo->{oplot}) {
214 0           $me->{curvestyle} = 0;
215 0           $me->{logaxis} = $ipo->{logaxis};
216 0           $po->{axis} = 0;
217 0 0         if($ipo->{logaxis} =~ m/x/i) {
218 0           $po->{axis} += 10;
219 0           $ipo->{xrange} = [ map log10($_), @{$ipo->{xrange}}[0,1] ];
  0            
220             }
221 0 0         if($ipo->{logaxis} =~ m/y/i) {
222 0           $po->{axis} += 20;
223 0           $ipo->{yrange} = [ map log10($_), @{$ipo->{yrange}}[0,1] ];
  0            
224             }
225 0           $me->{obj}->release;
226 0           my @range_vals = (@{$ipo->{xrange}}, @{$ipo->{yrange}});
  0            
  0            
227 0 0         $me->{obj}->env(@range_vals, $po) if grep defined, @range_vals;
228             }
229              
230             # ppo is "post-plot options", which are really a mix of plot and curve options.
231             # Currently we don't parse any plot options into it (they're handled by the "env"
232             # call) but if we end up doing so, it should go here. The linestyle and color
233             # are curve options that are autoincremented each curve.
234 0           my %ppo = ();
235 0           while (@_) {
236 0           my ($co, @data) = @{shift()};
  0            
237 0           my @extra_opts = ();
238 0 0         if ( defined $co->{style} ) {
239 0           $me->{curvestyle} = int($co->{style}) + 1;
240             } else {
241 0           $me->{curvestyle}++;
242             }
243 0           $ppo{ color } = $me->{curvestyle}-1 % 7 + 1;
244 0           $ppo{ linestyle } = ($me->{curvestyle}-1) % 5 + 1;
245 0 0         $ppo{ linewidth } = int($co->{width}) if $co->{width};
246 0           our $pgplot_methods;
247 0           my $pgpm = $pgplot_methods->{$co->{with}};
248 0 0         die "Unknown curve option 'with $co->{with}'!" unless($pgpm);
249 0           my @ppo_added;
250 0 0         if ($pgpm eq 'fits_imag') {
251 0           $ppo{$_} = $po->{$_} for @ppo_added = grep defined $po->{$_}, qw(justify title);
252             }
253 0 0         if($pgpm eq 'imag') {
254 0           @ppo{keys %color_opts} = values %color_opts;
255 0           $ppo{ drawwedge } = ($ipo->{wedge} != 0);
256             # Extract transform parameters from the corners of the image...
257 0           my $xcoords = shift(@data);
258 0           my $ycoords = shift(@data);
259 0           my $datum_pix = [0,0];
260 0           my $datum_sci = [$xcoords->at(0,0), $ycoords->at(0,0)];
261 0           my $t1 = ($xcoords->slice("(-1),(0)") - $xcoords->slice("(0),(0)")) / ($xcoords->dim(0)-1);
262 0           my $t2 = ($xcoords->slice("(0),(-1)") - $xcoords->slice("(0),(0)")) / ($xcoords->dim(1)-1);
263 0           my $t4 = ($ycoords->slice("(-1),(0)") - $ycoords->slice("(0),(0)")) / ($ycoords->dim(0)-1);
264 0           my $t5 = ($ycoords->slice("(0),(-1)") - $ycoords->slice("(0),(0)")) / ($ycoords->dim(1)-1);
265 0           my $transform = pdl(
266             $datum_sci->[0] - $t1 * $datum_pix->[0] - $t2 * $datum_pix->[1],
267             $t1, $t2,
268             $datum_sci->[1] - $t4 * $datum_pix->[0] - $t5 * $datum_pix->[1],
269             $t4, $t5
270             )->flat;
271             { # sepia color table
272 0           my $r = (xvals(256)/255)->sqrt;
  0            
273 0           my $g = (xvals(256)/255);
274 0           my $b = (xvals(256)/255)**2;
275 0           $me->{obj}->ctab($g, $r, $g, $b);
276             }
277             }
278 0 0         $data[0] = $data[0]->log10 if $me->{logaxis} =~ m/x/i;
279 0 0         $data[1] = $data[1]->log10 if $me->{logaxis} =~ m/y/i;
280 0 0         if (ref $pgpm eq 'CODE') {
281 0           $pgpm->($me, $ipo, \@data, \%ppo);
282             } else {
283 0           $me->{obj}->$pgpm(@data,\%ppo);
284             }
285 0 0         delete @ppo{@ppo_added} if @ppo_added;
286 0           $me->{obj}->hold;
287             }
288              
289             ##############################
290             # End of curve plotting.
291             # Now place the legend if necessary.
292 0 0         if ($ipo->{legend}) {
293 0           my $xp;
294 0           my $xrdiff = $ipo->{xrange}->[1] - $ipo->{xrange}->[0];
295 0 0         if( $ipo->{legend}=~ m/l/i ) {
    0          
296 0           $xp = 0.03 * $xrdiff + $ipo->{xrange}->[0];
297             } elsif($ipo->{legend} =~ m/r/i) {
298 0           $xp = 0.8 * $xrdiff + $ipo->{xrange}->[0];
299             } else {
300 0           $xp = 0.4 * $xrdiff + $ipo->{xrange}->[0];
301             }
302 0           my $yp;
303 0           my $yrdiff = $ipo->{yrange}->[1] - $ipo->{yrange}->[0];
304 0 0         if( $ipo->{legend}=~ m/t/i ) {
    0          
305 0           $yp = 0.95 * $yrdiff + $ipo->{yrange}->[0];
306             } elsif($ipo->{legend} =~ m/b/i) {
307 0           $yp = 0.2 * $yrdiff + $ipo->{yrange}->[0];
308             } else {
309 0           $yp = 0.6 * $yrdiff + $ipo->{yrange}->[0];
310             }
311 0           print "keys is [".join(",",@{$me->{keys}})."]; xp is $xp; yp is $yp\n";
  0            
312             $me->{obj}->legend(
313             $me->{keys},
314             $xp, $yp,
315 0           { Color => [ (xvals(0+@{$me->{keys}}) % 7 + 1)->list ],
316 0           LineStyle => [ (xvals(0+@{$me->{keys}}) % 5 + 1)->list ]
  0            
317             }
318             );
319             }
320 0           $me->{obj}->release;
321             }
322              
323             sub DESTROY {
324 0     0     my $me = shift;
325              
326 0           eval { # in case of global destruction
327 0           $me->{obj}->release;
328             };
329              
330 0 0 0       if (defined $me->{type} and $me->{type} =~ m/^f/i) {
331 0           eval { $me->{obj}->close; };
  0            
332 0 0         if ($me->{conv_fn}) {
333 0           wim(rim($me->{conv_fn}), $me->{opt}{output});
334 0           unlink($me->{conv_fn});
335             }
336             }
337             }
338              
339             1;