File Coverage

blib/lib/PDL/Graphics/Simple/Gnuplot.pm
Criterion Covered Total %
statement 27 164 16.4
branch 3 106 2.8
condition 1 23 4.3
subroutine 7 9 77.7
pod 0 3 0.0
total 38 305 12.4


line stmt bran cond sub pod time code
1             ######################################################################
2             ######################################################################
3             ######################################################################
4             ###
5             ###
6             ### Gnuplot backend for PDL::Graphics:Simple
7             ###
8             ### See the PDL::Graphics::Simple docs for details
9             ###
10             ##
11             #
12             package PDL::Graphics::Simple::Gnuplot;
13              
14 1     1   8 use strict;
  1         2  
  1         49  
15 1     1   5 use warnings;
  1         16  
  1         129  
16 1     1   8 use File::Temp qw/tempfile/;
  1         3  
  1         91  
17 1     1   7 use PDL::Options q/iparse/;
  1         2  
  1         120  
18 1     1   8 use PDL;
  1         2  
  1         11  
19 1     1   6606 use PDL::ImageND; # for polylines
  1         6217  
  1         11  
20             our $required_PGG_version = 1.5;
21              
22             our $mod = {
23             shortname => 'gnuplot',
24             module=>'PDL::Graphics::Simple::Gnuplot',
25             engine => 'PDL::Graphics::Gnuplot',
26             synopsis=> 'Gnuplot 2D/3D (versatile; beautiful output)',
27             pgs_api_version=> '1.012',
28             };
29             PDL::Graphics::Simple::register( $mod );
30              
31             our $filetypes = {
32             ps => ['pscairo','postscript'],
33             dxf => 'dxf',
34             png => ['pngcairo','png'],
35             pdf => ['pdfcairo','pdf'],
36             txt => 'dumb',
37             jpg => 'jpeg',
38             svg => 'svg',
39             gif => 'gif'
40             };
41              
42             our @disp_terms = qw/ qt wxt x11 aqua windows /;
43             our $disp_opts = {
44             wxt=>{persist=>1},
45             x11=>{persist=>1},
46             aqua=>{persist=>0},
47             windows=>{persist=>0}
48             };
49              
50             ##########
51             # PDL::Graphics::Simple::Gnuplot::check
52             # Checker
53             sub check {
54 1     1 0 3 my $force = shift;
55 1 50       5 $force = 0 unless(defined($force));
56              
57 1 50 33     9 return $mod->{ok} unless( $force or !defined($mod->{ok}) );
58              
59             # Eval PDL::Graphics::Gnuplot. Require relatively recent version.
60             # We don't specify the version in the 'use', so we can issue a
61             # warning on an older version.
62 1         3 eval { require PDL::Graphics::Gnuplot; PDL::Graphics::Gnuplot->import; };
  1         246  
  0         0  
63 1 50       7 if ($@) {
64 1         6 $mod->{ok} = 0;
65 1         5 $mod->{msg} = $@;
66 1         7 return 0;
67             }
68 0 0         if ($PDL::Graphics::Gnuplot::VERSION < $required_PGG_version) {
69 0           $mod->{msg} = sprintf("PDL::Graphics::Gnuplot was found, but is too old (v%s < v%s). Ignoring it.\n",
70             $PDL::Graphics::Gnuplot::VERSION,
71             $required_PGG_version
72             );
73 0           $mod->{ok} = 0;
74 0           return 0;
75             }
76              
77 0           my $gpw = eval { gpwin() };
  0            
78 0 0         if ($@) {
79 0           $mod->{ok} = 0;
80 0           $mod->{msg} = $@;
81 0           die "PDL::Graphics::Simple: PDL::Graphics::Gnuplot didn't construct properly.\n\t$@";
82             }
83 0           $mod->{valid_terms} = $gpw->{valid_terms};
84              
85 0           my $okterm = undef;
86 0 0         if ($ENV{PDL_SIMPLE_DEVICE}) {
87 0           $okterm = 1;
88             } else {
89 0           for my $term (@disp_terms) {
90 0 0         if ($mod->{valid_terms}{$term}) {
91 0           $okterm = $term;
92 0           last;
93             }
94             }
95             }
96              
97 0 0         unless ( defined $okterm ) {
98 0           $mod->{ok} = 0;
99 0           my $s = "Gnuplot doesn't seem to support any of the known display terminals:\n they are: (".join(",",@disp_terms).")\n";
100 0           $mod->{msg} = $s;
101 0           die "PDL::Graphics::Simple: $s";
102             }
103 0           $mod->{gp_version} = $PDL::Graphics::Gnuplot::gp_version;
104 0           $mod->{ok} = 1;
105 0           return 1;
106             }
107              
108              
109             ##########
110             # PDL::Graphics::Simple::Gnuplot::new
111             # Constructor
112             our $new_defaults = {
113             size => [6,4.5,'in'],
114             type => '',
115             output => '',
116             multi=>undef
117             };
118              
119              
120             sub new {
121 0     0 0   my $class = shift;
122 0           my $opt_in = shift;
123 0 0         $opt_in = {} unless(defined($opt_in));
124 0           my $opt = { iparse( $new_defaults, $opt_in ) };
125 0           my $gpw;
126              
127             # Force a recheck on failure, in case the user fixed gnuplot.
128             # Also loads PDL::Graphics::Gnuplot.
129 0 0         unless(check()) {
130 0 0         die "$mod->{shortname} appears nonfunctional: $mod->{msg}\n" unless(check(1));
131             }
132              
133             # Generate the @params array to feed to gnuplot
134 0           my @params = ();
135 0           push( @params, "size" => $opt->{size} );
136              
137             # tempfile gets set if we need to write to a temporary file for image conversion
138 0           my $conv_tempfile = '';
139              
140             # Do different things for interactive and file types
141 0 0         if ($opt->{type} =~ m/^i/i) {
142 0 0         push(@params, title=>$opt->{output}) if defined $opt->{output};
143             # Interactive - try known terminals unless PDL_SIMPLE_DEVICE given
144 0           push @params, font=>"=16", dashed=>1;
145 0 0         if (my $try = $mod->{itype}) {
146             $gpw = gpwin($mod->{itype}, @params,
147 0 0 0       ($disp_opts->{$try} // {})->{persist} ? (persist=>0) : ()
148             );
149             } else {
150 0 0         if (my $try = $ENV{PDL_SIMPLE_DEVICE}) {
151             $gpw = gpwin($try, @params,
152 0 0 0       ($disp_opts->{$try} // {})->{persist} ? (persist=>0) : ()
153             );
154             } else {
155 0           attempt:for my $try( @disp_terms ) {
156 0           eval { $gpw = gpwin($try, @params,
157 0 0 0       ($disp_opts->{$try} // {})->{persist} ? (persist=>0) : ()
158             ); };
159 0 0         last attempt if $gpw;
160             }
161             }
162 0 0         die "Couldn't start a gnuplot interactive window" unless($gpw);
163 0           $mod->{itype} = $gpw->{terminal};
164             }
165             } else {
166             # File output - parse out file type, and then see if we support it.
167             # (Maybe the parsing part could be pushed into a utility routine...)
168              
169             # Filename extension -- 2-4 characters
170 0           my $ext;
171 0 0         if ($opt->{output} =~ m/\.(\w{2,4})$/) {
172 0           $ext = $1;
173             } else {
174 0           $ext = '.png';
175 0           print STDERR "PDL::Graphics::Simple::Gnuplot: Warning - defaulting to .png type for file '$opt->{output}'\n";
176             }
177 0           $opt->{ext} = $ext;
178              
179             ##########
180             # Scan through the supported file types. Gnuplot has several drivers for some
181             # of the types, so we search until we find a valid one.
182             # At the end, $ft has either a valid terminal name from the table (at top),
183             # or undef.
184 0           my $ft = $filetypes->{$ext};
185 0 0         if (ref $ft eq 'ARRAY') {
    0          
186 0           try:for my $try (@$ft) {
187 0 0         if ($mod->{valid_terms}{$try}) {
188 0           $ft = $try;
189 0           last try;
190             }
191             }
192 0 0         if (ref($ft)) {
193 0           $ft = undef;
194             }
195             } elsif (!defined($mod->{valid_terms}{$ft})) {
196 0           $ft = undef;
197             }
198              
199             # Now $ext has the file type - check if its a supported type. If not, make a
200             # tempfilename to hold gnuplot's output.
201 0 0         unless ( defined($ft) ) {
202 0 0 0       unless ($mod->{valid_terms}{pscairo} or $mod->{valid_terms}{postscript}) {
203 0           die "PDL::Graphics::Simple: $ext isn't a valid output file type for your gnuplot,\n\tand it doesn't support .ps either. Sorry, I give up.\n";
204             }
205              
206             # Term is invalid but png is supported - set up a tempfile for conversion.
207 0           my($fh);
208 0           ($fh,$conv_tempfile) = tempfile('pgs_gnuplot_XXXX');
209 0           close $fh;
210 0           unlink($conv_tempfile); # just to be sure;
211 0           $conv_tempfile .= ".ps";
212 0 0         $ft = $mod->{valid_terms}{pscairo} ? 'pscairo' : 'postscript';
213             }
214 0   0       push @params, output => ($conv_tempfile || $opt->{output});
215 0 0         push @params, color => 1 if $PDL::Graphics::Gnuplot::termTab->{$ft}{color};
216 0 0         push @params, dashed => 1 if $PDL::Graphics::Gnuplot::termTab->{$ft}{dashed};
217 0           $gpw = gpwin( $ft, @params );
218             }
219              
220              
221 0           my $me = { opt => $opt, conv_fn => $conv_tempfile, obj=>$gpw };
222              
223             # Deal with multiplot setup...
224 0 0         if (defined($opt->{multi})) {
225 0           $me->{nplots} = $opt->{multi}[0] * $opt->{multi}[1];
226 0           $me->{plot_no} = 0;
227             } else {
228 0           $me->{nplots} = 0;
229             }
230              
231 0           return bless($me, 'PDL::Graphics::Simple::Gnuplot');
232             }
233              
234              
235             ##############################
236             # PDL::Graphics::Simple::Gnuplot::plot
237             # Most of the curve types are implemented by passing them on to gnuplot -- circles is an
238             # exception, since the gnuplot "circles" curve type doesn't scale the circles in scientific
239             # coordinates (they are always rendered as circular on the screen), and we want to match
240             # the scaling behavior of the other engines.
241              
242             our $curve_types = {
243             points => 'points',
244             lines => 'lines',
245             bins => 'histeps',
246             errorbars => 'yerrorbars',
247             limitbars => 'yerrorbars',
248             image => 'image',
249             circles => sub {
250             my($me, $po, $co, @data) = @_;
251             my $ang = PDL->xvals(362)*3.14159/180;
252             my $c = $ang->cos;
253             my $s = $ang->sin;
254             $s->slice("361") .= $c->slice("361") .= PDL->pdl(1.1)->acos; # NaN
255             my $dr = $data[2]->flat;
256             my $dx = ($data[0]->flat->slice("*1") + $dr->slice("*1") * $c)->flat;
257             my $dy = ($data[1]->flat->slice("*1") + $dr->slice("*1") * $s)->flat;
258             $co->{with} = "lines";
259             return [ $co, $dx, $dy ];
260             },
261             contours => sub {
262             my ($me, $po, $co, $vals, $cvals) = @_;
263             $co->{with} = "lines";
264             $co->{style} //= 6; # so all contour parts have same style, blue somewhat visible against sepia
265             my @out;
266             for my $thresh ($cvals->list) {
267             my ($pi, $p) = contour_polylines($thresh, $vals, $vals->ndcoords);
268             next if $pi->at(0) < 0;
269             push @out, map [ $co, $_->dog ], path_segs($pi, $p->mv(0,-1));
270             }
271             @out;
272             },
273             polylines => sub {
274             my ($me, $po, $co, $xy, $pen) = @_;
275             $co->{with} = "lines";
276             $co->{style} //= 6; # so all polylines have same style, blue somewhat visible against sepia
277             my $pi = $pen->eq(0)->which;
278             map [ $co, $_->dog ], path_segs($pi, $xy->mv(0,-1));
279             },
280             fits => 'fits',
281             labels => sub {
282             my($me, $po, $co, @data) = @_;
283             my $label_list = ($po->{label} or []);
284             for my $i(0..$data[0]->dim(0)-1) {
285             my $j = "";
286             my $s = $data[2]->[$i];
287             if ( $s =~ s/^([\<\>\| ])// ) {
288             $j = $1;
289             }
290             my @spec = ("$s", at=>[$data[0]->at($i), $data[1]->at($i)]);
291             push @spec,"left" if $j eq '<';
292             push @spec,"center" if $j eq '|';
293             push @spec,"right" if $j eq '>';
294             push @{$label_list}, \@spec;
295             }
296             $po->{label} = $label_list;
297             $co->{with} = "labels";
298             return [ $co, [$po->{xrange}[0]], [$po->{yrange}[0]], [""] ];
299             },
300             };
301              
302             sub plot {
303 0     0 0   my $me = shift;
304 0           my $ipo = shift;
305              
306             my $po = {
307             title => $ipo->{title},
308             xlab => $ipo->{xlabel},
309             ylab => $ipo->{ylabel},
310             key => $ipo->{key},
311             xrange => $ipo->{xrange},
312             yrange => $ipo->{yrange},
313             cbrange => $ipo->{crange},
314             colorbox => $ipo->{wedge},
315             justify => $ipo->{justify}>0 ? $ipo->{justify} : undef,
316 0 0         clut => 'sepia',
317             };
318              
319 0 0         if ( defined($ipo->{legend}) ) {
320 0           my $legend = "";
321 0 0         if ( $ipo->{legend} =~ m/l/i ) {
    0          
322 0           $legend .= ' left ';
323             } elsif ($ipo->{legend} =~ m/r/i) {
324 0           $legend .= ' right ';
325             } else {
326 0           $legend .= ' center ';
327             }
328 0 0         if ( $ipo->{legend} =~ m/t/i) {
    0          
329 0           $legend .= ' top ';
330             } elsif ( $ipo->{legend} =~ m/b/i) {
331 0           $legend .= ' bottom ';
332             } else {
333 0           $legend .= ' center ';
334             }
335 0           $po->{key} = $legend;
336             }
337              
338 0 0         $po->{logscale} = [$ipo->{logaxis}] if $ipo->{logaxis};
339              
340 0 0         unless ($ipo->{oplot}) {
341 0           $me->{curvestyle} = 0;
342             }
343              
344 0           my @arglist = $po;
345 0           for my $block (@_) {
346             die "PDL::Graphics::Simple::Gnuplot: undefined curve type $block->[0]{with}"
347 0 0         unless my $ct = $curve_types->{ $block->[0]{with} };
348 0 0         my @blocks = ref($ct) eq 'CODE' ? $ct->($me, $po, @$block) : [{%{$block->[0]}, with=>$ct}, @$block[1..$#$block]];
  0            
349             # Now parse out curve options and deal with line styles...
350 0           for my $b (@blocks) {
351 0           my ($co, @rest) = @$b;
352 0           my $gco = { with => $co->{with} };
353 0 0         unless($co->{with} eq 'labels') {
354 0   0       $me->{curvestyle} = $co->{style} // ($me->{curvestyle}//0)+1;
      0        
355 0           $gco->{dashtype} = $gco->{linetype} = $me->{curvestyle};
356 0 0         if ( $co->{width} ) {
357 0 0         $gco->{pointsize} = $co->{width} if $co->{with} =~ m/^points/;
358 0           $gco->{linewidth} = $co->{width};
359             }
360             }
361 0 0         $gco->{legend} = $co->{key} if defined $co->{key};
362 0           push @arglist, $gco, @rest;
363             }
364             }
365              
366 0 0         if ($me->{nplots}) {
367 0 0         unless($me->{plot_no}) {
368 0           $me->{obj}->multiplot( layout=>[@{$me->{opt}{multi}}[0,1]] );
  0            
369             }
370             }
371              
372 0 0         if ($ipo->{oplot}) {
373 0           delete @$po{qw(logaxis xrange yrange cbrange justify)};
374 0           $me->{obj}->replot(@arglist);
375             } else {
376 0           $me->{obj}->plot(@arglist);
377             }
378              
379 0 0         if ($me->{nplots}) {
380 0           $me->{plot_no}++;
381 0 0         if ($me->{plot_no} >= $me->{nplots}) {
382 0           $me->{obj}->end_multi;
383 0           $me->{plot_no} = 0;
384 0 0         $me->{obj}->close if $me->{opt}{type} =~ m/^f/i;
385             }
386             } else {
387 0 0         $me->{obj}->close if $me->{opt}{type} =~ m/^f/i;
388             }
389              
390 0 0 0       if ($me->{opt}{type} =~ m/^f/i and $me->{conv_fn}) {
391 0           print "converting $me->{conv_fn} to $me->{opt}{output}...";
392 0           $a = rim($me->{conv_fn});
393 0           wim($a->slice('-1:0:-1')->mv(1,0), $me->{opt}{output});
394 0           unlink($me->{conv_fn});
395             }
396             }
397              
398             1;