forked from PDLPorters/PDL-Graphics-Simple
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSimple.pm
More file actions
1578 lines (1209 loc) · 48.5 KB
/
Simple.pm
File metadata and controls
1578 lines (1209 loc) · 48.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
=head1 NAME
PDL::Graphics::Simple - Simple backend-independent plotting for PDL
=head1 SYNOPSIS
# Simple interface - throw plots up on-screen, ASAP
use PDL::Graphics::Simple;
imag $a; # Display an image PDL
imag $a, 0, 300; # Display with color range
line $rrr, $fit; # Plot a line
points $rr, $sec; # Plot points
hold; # Hold graphics so subsequent calls overplot
line $rrr, $fit; # Overplot a line in a contrasting color
release; # Release graphics
# Object interface - simple plotting, to file or screen
$w = pgswin( size=>[8,4], multi=>[2,2] ); # 2x2 plot grid on an 8"x4" window
$w = pgswin( size=>[1000,1000,'px'], output=>'plot.png' ); # output to a PNG
$w->plot( with=>'points', $rr, $sec, with=>'line', $rrr, $fit,
{title=>"Points and fit", xlabel=>"Abscissa", ylabel=>"Ordinate"});
=head1 DESCRIPTION
PDL can plot through a plethora of external plotting modules. Each
module tends to be less widely available than Perl itself, and to
require an additional step or two to install. For simple applications
("throw up an image on the screen", or "plot a curve") it is useful to
have a subset of all plotting capability available in a backend-independent
layer. PDL::Graphics::Simple provides that capability.
PDL::Graphics::Simple implements all the functionality used in the
PDL::Book examples, with identical syntax. It also generalizes that
syntax - you can use ::Simple graphics, with slight syntactical
differences, in the same manner that you would use any of the engine
modules. See the Examples below for details.
The plot you get will always be what you asked for, regardless of
which plotting engine you have installed on your system.
Only a small subset of PDL's complete graphics functionality is
supported -- each individual plotting module has unique advantages and
functionality that are beyond what PDL::Graphics::Simple can do. Only
2-D plotting is supported. For 3-D plotting, use
L<PDL::Graphics::Gnuplot> or L<PDL::Graphics::TriD> directly.
When plotting to a file, the file output is not guaranteed to be
present until the plot object is destroyed (e.g. by being undefed or
going out of scope).
=head1 STATE OF DEVELOPMENT
PDL::Graphics::Simple currently supports most of the
planned functionality. It is being released as a beta
test to determine if it meets users' needs and gain feedback on
the API -- so please give feedback!
=head1 SUPPORTED GRAPHICS ENGINES
PDL::Graphics::Simple includes support for the following graphics
engines. Additional driver modules can be loaded dynamically; see
C<register>, below. Each of the engines has unique capabilities and
flavor that are not captured in PDL::Graphics::Simple - you are
encouraged to look at the individual modules for more capability!
=over 3
=item * Gnuplot (via PDL::Graphics::Gnuplot)
Gnuplot is an extremely richly featured plotting package that offers
markup, rich text control, RGB color, and 2-D and 3-D plotting. Its
output is publication quality. It is supported on POSIX systems,
MacOS, and Microsoft Windows, and is available from most package
managers.
=item * PGPLOT (via PDL::Graphics::PGPLOT::Window)
PGPLOT is venerable and nearly as fully featured as Gnuplot for 2-D
plotting. It lacks RGB color output. It does have rich text control,
but uses simple plotter fonts that are generated internally. It
is supported on MacOS and POSIX, but is not as widely available as
Gnuplot.
=item * PLplot (via PDL::Graphics::PLplot)
PLplot is a moderately full featured plotting package that
generates publication quality output with a simple high-level interface.
It is supported on MacOS and POSIX.
=item * Prima (via PDL::Graphics::Prima)
Prima is based around a widget paradigm that enables complex
interaction with data in real-time, and it is highly optimized for
that application. It is not as mature as the other platforms,
particularly for static plot generation to files. This means that
PDL::Graphics::Simple does not play to its considerable strengths,
although Prima is serviceable and fast in this application. Please
run the Prima demo in the perldl shell for a better sample of Prima's
capabilities.
=back
=head1 EXAMPLES
PDL::Graphics::Simple can be called using plot-atomic or curve-atomic
plotting styles, using a pidgin form of calls to any of the main
modules. The examples are divided into Book-like (very simple),
PGPLOT-like (curve-atomic), and Gnuplot-like (plot-atomic) cases.
There are three main styles of interaction with plot objects that
PDL::Graphics::Simple supports, reflective of the pre-existing
modules' styles of interaction. You can mix-and-match them to match
your particular needs and coding style. Here are examples showing
convenient ways to call the code.
=head2 First steps (non-object-oriented)
For the very simplest actions there are non-object-oriented shortcuts.
Here are some examples of simple tasks, including axis labels and plot
titles. These non-object-oriented shortcuts are useful for display
with the default window size. They make use of a package-global plot
object.
The non-object interface will keep using the last plot engine you used
successfully. On first start, you can specify an engine with the
environment variable C<PDL_SIMPLE_ENGINE>. As of 1.011, only that will be tried, but
if you didn't specify one, all known engines are tried in alphabetical
order until one works.
The value of C<PDL_SIMPLE_ENGINE> should be the "shortname" of the
engine, currently:
=over
=item C<gnuplot>
=item C<plplot>
=item C<pgplot>
=item C<prima>
=back
=over 3
=item * Load module and create line plots
use PDL::Graphics::Simple;
$x = xvals(51)/5;
$y = $x**3;
$y->line;
line( $x, $y );
line( $x, $y, {title=>"My plot", ylabel=> "Ordinate", xlabel=>"Abscissa"} );
=item * Bin plots
$y->bins;
bins($y, {title=>"Bin plot", xl=>"Bin number", yl=>"Count"} );
=item * Point plots
$y->points;
points($y, {title=>"Points plot"});
=item * Logarithmic scaling
line( $y, { log=>'y' } ); # semilog
line( $y, { log=>'xy' } ); # log-log
=item * Image display
$im = 10 * sin(rvals(101,101)) / (10 + rvals(101,101));
imag $im; # Display image
imag $im, 0, 1; # Set lower/upper color range
=item * Overlays
points($x, $y, {logx=>1});
hold;
line($x, sqrt($y)*10);
release;
=item * Justify aspect ratio
imag $im, {justify=>1}
points($x, $y, {justify=>1});
=item * Erase/delete the plot window
erase();
=back
=head2 Simple object-oriented plotting
More functionality is accessible through direct use of the PDL::Graphics::Simple
object. You can set plot size, direct plots to files, and set up multi-panel plots.
The constructor accepts window configuration options that set the plotting
environment, including size, driving plot engine, output, and multiple
panels in a single window.
For interactive/display plots, the plot is rendered immediately, and lasts until
the object is destroyed. For file plots, the file is not guaranteed to exist
and be correct until the object is destroyed.
The basic plotting method is C<plot>. C<plot> accepts a collection of
arguments that describe one or more "curves" (or datasets) to plot,
followed by an optional plot option hash that affects the entire plot.
Overplotting is implemented via plot option, via a held/released state
(as in PGPLOT), and via a convenience method C<oplot> that causes the
current plot to be overplotted on the previous one.
Plot style (line/points/bins/etc.) is selected via the C<with> curve option.
Several convenience methods exist to create plots in the various styles.
=over 3
=item * Load module and create basic objects
use PDL::Graphics::Simple;
$x = xvals(51)/5;
$y = $x**3;
$win = pgswin(); # plot to a default-shape window
$win = pgswin( size=>[4,3] ); # size is given in inches by default
$win = pgswin( size=>[10,5,'cm'] ); # You can feed in other units too
$win = pgswin( out=>'plot.ps' ); # Plot to a file (type is via suffix)
$win = pgswin( engine=>'gnuplot' ); # Pick a particular plotting engine
$win = pgswin( multi=>[2,2] ); # Set up for a 2x2 4-panel plot
=item * Simple plots with C<plot>
$win->plot( with=>'line', $x, $y, {title=>"Simple line plot"} );
$win->plot( with=>'errorbars', $x, $y, sqrt($y), {title=>"Error bars"} );
$win->plot( with=>'circles', $x, $y, sin($x)**2 );
=item * Plot overlays
# All at once
$win->plot( with=>'line', $x, $y, with=>'circles', $x, $y/2, sqrt($y) );
# Using oplot (IDL-style; PLplot-style)
$win->plot( with=>'line', $x, $y );
$win->oplot( with=>'circles', $x, $y/2, sqrt($y) );
# Using object state (PGPLOT-style)
$win->line( $x, $y );
$win->hold;
$win->circles( $x, $y/2, sqrt($y) );
$win->release;
=back
=head1 FUNCTIONS
=cut
package PDL::Graphics::Simple;
use strict;
use warnings;
use PDL;
use PDL::Options q/iparse/;
use File::Temp qw/tempfile tempdir/;
use Scalar::Util q/looks_like_number/;
our $VERSION = '1.015';
$VERSION =~ s/_//g;
##############################
# Exporting
use base 'Exporter';
our @EXPORT = qw(pgswin line points bins imag cont hold release erase);
our @EXPORT_OK = (@EXPORT, qw(image plot));
our $API_VERSION = '1.012'; # PGS version where that API started
##############################
# Configuration
# Knowledge base containing found info about each possible backend
our $mods = {};
our $mod_abbrevs = undef;
our $last_successful_type = undef;
our $global_plot = undef;
# lifted from PDL::Demos::list
sub _list_submods {
my @d = @_;
my @found;
my %found_already;
foreach my $path ( @INC ) {
next if !-d (my $dir = File::Spec->catdir( $path, @d ));
my @c = do { opendir my $dirfh, $dir or die "$dir: $!"; grep !/^\./, readdir $dirfh };
for my $f (grep /\.pm$/ && -f File::Spec->catfile( $dir, $_ ), @c) {
$f =~ s/\.pm//;
my $found_mod = join "::", @d, $f;
next if $found_already{$found_mod}++;
push @found, $found_mod;
}
for my $t (grep -d $_->[1], map [$_, File::Spec->catdir( $dir, $_ )], @c) {
my ($subname, $subd) = @$t;
# one extra level
my @c = do { opendir my $dirfh, $subd or die "$subd: $!"; grep !/^\./, readdir $dirfh };
for my $f (grep /\.pm$/ && -f File::Spec->catfile( $subd, $_ ), @c) {
$f =~ s/\.pm//;
my $found_mod = join "::", @d, $subname, $f;
next if $found_already{$found_mod}++;
push @found, $found_mod;
}
}
}
@found;
}
for my $module (_list_submods(qw(PDL Graphics Simple))) {
(my $file = $module) =~ s/::/\//g;
require "$file.pm";
}
$mod_abbrevs ||= _make_abbrevs($mods); # Deal with abbreviations.
=head2 show
=for usage
PDL::Graphics::Simple::show
=for ref
C<show> lists the supported engines and a one-line synopsis of each.
=cut
sub show {
my $format = "%-10s %-30s %-s\n";
printf($format, "NAME","Module","(synopsis)");
printf($format, "----","------","----------");
for my $engine( sort keys %$mods ) {
printf($format, $engine, $mods->{$engine}->{engine}, $mods->{$engine}->{synopsis});
}
print "\n";
}
##############################
# Constructor - scan through registered subclasses and generate the correct one.
=head2 pgswin - exported constructor
=for usage
$w = pgswin( %opts );
=for ref
C<pgswin> is a constructor that is exported by default into the using package. Calling
C<pgswin(%opts)> is exactly the same as calling C<< PDL::Graphics::Simple->new(%opts) >>.
=head2 new
=for usage
$w = PDL::Graphics::Simple->new( %opts );
=for ref
C<new> is the main constructor for PDL::Graphics::Simple. It accepts a list of options
about the type of window you want:
=over 3
=item engine
If specified, this must be one of the supported plotting engines. You
can use a module name or the shortened name. If you don't give one,
the constructor will try the last one you used, or else scan through
existing modules and pick one that seems to work. It will first check
the environment variable C<PDL_SIMPLE_ENGINE>, and as of 1.011, only that will be tried, but
if you didn't specify one, all known engines are tried in alphabetical
order until one works.
=item size
This is a window size as an ARRAY ref containing [width, height,
units]. If no units are specified, the default is "inches". Accepted
units are "in","pt","px","mm", and "cm". The conversion used for pixels
is 100 px/inch.
=item type
This describes the kind of plot to create, and should be either "file"
or "interactive" - though only the leading character is checked. If
you don't specify either C<type> or C<output> (below), the default is
"interactive". If you specify only C<output>, the default is "file".
=item output
This should be a window number or name for interactive plots, or a
file name for file plots. The default file name is "plot.png" in the
current working directory. Individual plotting modules all support at
least '.png', '.pdf', and '.ps' -- via format conversion if necessary.
Most other standard file types are supported but are not guaranteed to
work.
=item multi
This enables plotting multiple plots on a single screen. You feed in
a single array ref containing (nx, ny). Subsequent calls to plot
send graphics to subsequent locations on the window. The ordering
is always horizontal first, and left-to-right, top-to-bottom.
B<NOTE> for multiplotting: C<oplot> does not work and will cause an
exception. This is a limitation imposed by Gnuplot.
=back
=cut
our $new_defaults = {
engine => '',
size => [8,6,'in'],
type => '',
output => '',
multi => undef
};
sub pgswin { __PACKAGE__->new(@_) }
sub _translate_new {
my $opt_in = shift;
$opt_in = {} unless(defined($opt_in));
$opt_in = { $opt_in, @_ } if !ref $opt_in;
my $opt = { iparse( $new_defaults, $opt_in ) };
##############################
# Pick out a working plot engine...
unless ($opt->{engine}) {
# find the first working subclass...
unless ($last_successful_type) {
my @try = $ENV{'PDL_SIMPLE_ENGINE'} || sort keys %$mods;
attempt: for my $engine( @try ) {
print "Trying $engine ($mods->{$engine}->{engine})...";
my $s;
my $a = eval { $mods->{$engine}{module}->can('check')->() };
if ($@) {
chomp $@;
$s = "$@";
} else {
$s = ($a ? "ok" : "nope");
}
print $s."\n";
if ($a) {
$last_successful_type = $engine;
last attempt;
}
}
barf "Sorry, all known plotting engines failed. Install one and try again"
unless $last_successful_type;
}
$opt->{engine} = $last_successful_type;
}
my $engine = $mod_abbrevs->{lc($opt->{engine})};
unless(defined($engine) and defined($mods->{$engine})) {
barf "$opt->{engine} is not a known plotting engine. Use PDL::Graphics::Simple::show() for a list";
}
$last_successful_type = $opt->{engine};
my $size = _regularize_size($opt->{size},'in');
my $type = $ENV{PDL_SIMPLE_OUTPUT} ? 'f' : $opt->{type};
my $output = $ENV{PDL_SIMPLE_OUTPUT} || $opt->{output};
unless ($type) {
# Default to file if output looks like a filename; to interactive otherwise.
$type = ( ($output =~ m/\.(\w{2,4})$/) ? 'f' : 'i' );
}
unless ($type =~ m/^[fi]/i) {
barf "$type is not a known output type (must be 'file' or 'interactive')";
}
# Default to 'plot.png' if no output is specified.
$output ||= $type eq 'f' ? "plot.png" : "";
# Hammer it into a '.png' if no suffix is specified
if ( $type =~ m/^f/i and $output !~ m/\.(\w{2,4})$/ ) {
$output .= ".png";
}
# Error-check multi
if( defined($opt->{multi}) ) {
if( ref($opt->{multi}) ne 'ARRAY' or @{$opt->{multi}} != 2 ) {
barf "PDL::Graphics::Simple::new: 'multi' option requires a 2-element ARRAY ref";
}
$opt->{multi}[0] ||= 1;
$opt->{multi}[1] ||= 1;
}
my $params = { size=>$size, type=>$type, output=>$output, multi=>$opt->{multi} };
($engine, $params);
}
sub new {
my $pkg = shift;
my ($engine, $params) = &_translate_new;
my $obj = $mods->{$engine}{module}->new($params);
bless { engine=>$engine, params=>$params, obj=>$obj }, $pkg;
}
=head2 plot
=for usage
$w = PDL::Graphics::Simple->new( %opts );
$w->plot($data);
=for ref
C<plot> plots zero or more traces of data on a graph. It accepts two kinds of
options: plot options that affect the whole plot, and curve options
that affect each curve. The arguments are divided into "curve blocks", each
of which contains a curve options hash followed by data.
If the last argument is a hash ref, it is always treated as plot options.
If the first and second arguments are both hash refs, then the first argument
is treated as plot options and the second as curve options for the first curve
block.
=head3 Plot options:
=over 3
=item oplot
If this is set, then the plot overplots a previous plot.
=item title
If this is set, it is a title for the plot as a whole.
=item xlabel
If this is set, it is a title for the X axis.
=item ylabel
If this is set, it is a title for the Y axis.
=item xrange
If this is set, it is a two-element ARRAY ref containing a range for
the X axis. If it is clear, the axis is autoscaled.
=item yrange
If this is set, it is a two-element ARRAY ref containing a range for
the Y axis. If it is clear, the axis is autoscaled.
=item logaxis
This should be empty, "x", "y", or "xy" (case and order insensitive).
Named axes are scaled logarithmically.
=item crange
If this is set, it is a two-element ARRAY ref containing a range for
color values, full black to full white. If it is clear, the engine or
plot module is responsible for setting the range.
=item wedge
If this is set, then image plots get a scientific colorbar on the
right side of the plot. (You can also say "colorbar", "colorbox", or "cb" if
you're more familiar with Gnuplot).
=item justify
If this is set to a true value, then the screen aspect ratio is adjusted
to keep the Y axis and X axis scales equal -- so circles appear circular, and
squares appear square.
=item legend (EXPERIMENTAL)
The "legend" plot option is intended for full support but it is currently
experimental: it is not fully implemented in all the engines, and
implementation is more variable than one would like in the engines that
do support it.
This controls whether and where a plot legend should be placed. If
you set it, you supply a combination of 't','b','c','l', and 'r':
indicating top, bottom, center, left, right position for the plot
legend. For example, 'tl' for top left, 'tc' for center top, 'c' or
'cc' for dead center. If left unset, no legend will be plotted. If
you set it but don't specify a position (or part of one), it defaults
to top and left.
If you supply even one 'key' curve option in the curves, legend defaults
to the value 'tl' if it isn't specified.
=back
=head3 Curve options:
=over 3
=item with
This names the type of curve to be plotted. See below for supported curve types.
=item key
This gives a name for the following curve, to be placed in a master plot legend.
If you don't specify a name but do call for a legend, the curve will be named
with the plot type and number (e.g. "line 3" or "points 4").
=item width
This lets you specify the width of the line, as a multiplier on the standard
width the engine uses. That lets you pick normal-width or extra-bold lines
for any given curve. The option takes a single positive natural number.
=item style
You can specify the line style in a very limited way -- as a style
number supported by the backend. The styles are generally defined by
a mix of color and dash pattern, but the particular color and dash
pattern depend on the engine in use. The first 30 styles are
guaranteed to be distinguishable. This is useful to produce, e.g.,
multiple traces with the same style. C<0> is a valid value.
=back
=head3 Curve types supported
=over 3
=item points
This is a simple point plot. It takes 1 or 2 columns of data.
=item lines
This is a simple line plot. It takes 1 or 2 columns of data.
=item bins
Stepwise line plot, with the steps centered on each X value. 1 or 2 columns.
=item errorbars
Simple points-with-errorbar plot, with centered errorbars. It takes 2
or 3 columns, and the last column is the absolute size of the errorbar (which
is centered on the data point).
=item limitbars
Simple points-with-errorbar plot, with asymmetric errorbars. It takes
3 or 4 columns, and the last two columns are the absolute low and high
values of the errorbar around each point (specified relative to the
origin, not relative to the data point value).
=item circles
Plot unfilled circles. Requires 2 or 3 columns of data; the last
column is the radius of each circle. The circles are circular in
scientific coordinates, not necessarily in screen coordinates (unless
you specify the "justify" plot option).
=item image
This is a monochrome or RGB image. It takes a 2-D or 3-D array of
values, as (width x height x color-index). Images are displayed in
a sepiatone color scale that enhances contrast and preserves intensity
when converted to grayscale. If you use the convenience routines
(C<image> or C<imag>), the "justify" plot option defaults to 1 -- so
the image will be displayed with square pixel aspect. If you use
C<< plot(with=>'image' ...) >>, "justify" defaults to 0 and you will have
to set it if you want square pixels.
For RGB images, the numerical values need to be in the range 0-255,
as they are interpreted as 8 bits per plane colour values. E.g.:
$w = pgswin(); # plot to a default-shape window
$w->image( pdl(xvals(9,9),yvals(9,9),rvals(9,9))*20 );
# or, from an image on disk:
$image_data = rpic( 'my-image.png' )->mv(0,-1); # need RGB 3-dim last
$w->image( $image_data );
If you have a 2-D field of values that you would like to see with a heatmap:
use PDL::Graphics::ColorSpace;
sub as_heatmap {
my ($d) = @_;
my $max = $d->max;
die "as_heatmap: can't work if max == 0" if $max == 0;
$d /= $max; # negative OK
my $hue = (1 - $d)*240;
$d = cat($hue, pdl(1), pdl(1));
(hsv_to_rgb($d->mv(-1,0)) * 255)->byte->mv(0,-1);
}
$w->image( as_heatmap(rvals 300,300) );
=item contours
As of 1.012. Draws contours. Takes a 2-D array of values, as (width x
height), and optionally a 1-D vector of contour values.
=item fits
As of 1.012. Displays an image from an ndarray with a FITS header.
Uses C<CUNIT[12]> etc to make X & Y axes including labels.
=item polylines
As of 1.012. Draws polylines, with 2 arguments (C<$xy>, C<$pen>).
The "pen" has value 0 for the last point in that polyline.
use PDL::Transform::Cartography;
use PDL::Graphics::Simple qw(pgswin);
$coast = earth_coast()->glue( 1, scalar graticule(15,1) );
$w = pgswin();
$w->plot(with => 'polylines', $coast->clean_lines);
=item labels
This places text annotations on the plot. It requires three input
arguments: the X and Y location(s) as PDLs, and the label(s) as a list
ref. The labels are normally left-justified, but you can explicitly
set the alignment for each one by beginning the label with "<" for
left "|" for center, and ">" for right justification, or a single " "
to denote default justification (left).
=back
=cut
# Plot options have a bunch of names for familiarity to different package users.
# They're hammered into a single simplified set for transfer to the engines.
our $plot_options = PDL::Options->new( {
oplot=> 0,
title => undef,
xlabel=> undef,
ylabel=> undef,
legend => undef,
xrange=> undef,
yrange=> undef,
logaxis=> "",
crange=> undef,
bounds=> undef,
wedge => 0,
justify=>undef,
});
$plot_options->synonyms( {
cbrange=>'crange',
replot=>'oplot',
xtitle=>'xlabel',
ytitle=>'ylabel',
key=>'legend',
colorbar=>'wedge',
colorbox=>'wedge',
cb=>'wedge',
logscale => 'logaxis',
});
our $plot_types = {
points => { args=>[1,2], ndims=>[1] },
polylines => { args=>[1,2], ndims=>[1,2] },
lines => { args=>[1,2], ndims=>[1] },
bins => { args=>[1,2], ndims=>[1] },
circles => { args=>[2,3], ndims=>[1] },
errorbars => { args=>[2,3], ndims=>[1] },
limitbars => { args=>[3,4], ndims=>[1] },
image => { args=>[1,3], ndims=>[2,3] },
fits => { args=>[1], ndims=>[2,3] },
contours => { args=>[1,2], ndims=>[2] },
labels => { args=>[3], ndims=>[1] },
};
our $plot_type_abbrevs = _make_abbrevs($plot_types);
our $curve_options = PDL::Options->new( {
with => 'lines',
key => undef,
style => undef,
width => undef
});
$curve_options->synonyms( {
legend =>'key',
name=>'key'
});
$curve_options->incremental(0);
sub _fits_convert {
my ($data, $opts) = @_;
eval "use PDL::Transform";
barf "PDL::Graphics::Simple: couldn't load PDL::Transform for 'with fits' option: $@" if $@;
barf "PDL::Graphics::Simple: 'with fits' needs an image, RGB triplet, or RGBA quad" unless $data->ndims==2 || ($data->ndims==3 && ($data->dim(2)==4 || $data->dim(2)==3 || $data->dim(2)==1));
my $h = $data->gethdr;
barf "PDL::Graphics::Simple: 'with fits' expected a FITS header"
unless $h && ref $h eq 'HASH' && !grep !$h->{$_}, qw(NAXIS NAXIS1 NAXIS2);
# Now update plot options to set the axis labels, if they haven't been updated already...
my %new_opts = %$opts;
for ([qw(xlabel CTYPE1 X CUNIT1 (pixels))],
[qw(ylabel CTYPE2 Y CUNIT2 (pixels))],
) {
my ($label, $type, $typel, $unit, $unitdef) = @$_;
next if defined $new_opts{$label};
$new_opts{$label} = join(" ",
$h->{$type} || $typel,
$h->{$unit} ? "($h->{$unit})" : $unitdef
);
}
my @dims01 = map $data->dim($_), 0,1;
$data = $data->map(t_identity(), \@dims01, $h); # resample removing rotation etc
my ($xcoords, $ycoords) = ndcoords(@dims01)->apply(t_fits($data->hdr, {ignore_rgb=>1}))->mv(0,-1)->dog;
$new_opts{xrange} = [$xcoords->minmax] if !grep defined, @{$new_opts{xrange}};
$new_opts{yrange} = [$ycoords->minmax] if !grep defined, @{$new_opts{yrange}};
('image', \%new_opts, $data, $xcoords, $ycoords);
}
sub _translate_plot {
my ($held, $keys) = (shift, shift);
##############################
# Trap some simple errors
barf "plot: requires at least one argument to plot!" if !@_;
barf "plot: requires at least one argument to plot, in addition to plot options"
if @_ == 1 and ref($_[0]) eq 'HASH';
barf "Undefined value given in plot args" if grep !defined(), @_;
##############################
# Collect plot options. These can be in a leading or trailing
# hash ref, with the leading overriding the trailing one. If the first
# two elements are hash refs, then the first is plot options and
# the second is curve options, otherwise we treat the first as curve options.
# A curve option hash is required for every curve.
my $po = {};
while (ref($_[-1]) eq 'HASH') {
my $h = pop;
@$po{keys %$h} = values %$h;
}
if (ref($_[0]) eq 'HASH' and ref($_[1]) eq 'HASH') {
my $h = shift;
@$po{keys %$h} = values %$h;
}
my $called_from_imag = delete $po->{called_from_imag};
$po = $plot_options->options($po);
$po->{oplot} = 1 if $held;
##############################
# Check the plot options for correctness.
### bounds is a synonym for xrange/yrange together.
### (dcm likes it)
if (defined($po->{bounds})) {
barf "Bounds option must be a 2-element ARRAY ref containing (xrange, yrange)"
if !ref($po->{bounds}) or ref($po->{bounds}) ne 'ARRAY' or @{$po->{bounds}} != 2;
for my $t ([0,'xrange'], [1, 'yrange']) {
my ($i, $r) = @$t;
next if !defined $po->{bounds}[$i];
warn "WARNING: bounds overriding $r since both were specified\n"
if defined $po->{$r};
$po->{$r} = $po->{bounds}[$i];
}
}
for my $r (grep defined($po->{$_}), qw(xrange yrange)) {
barf "Invalid ".(uc substr $r, 0, 1)." range (must be a 2-element ARRAY ref with differing values)"
if !ref($po->{$r}) or ref($po->{$r}) ne 'ARRAY' or @{$po->{$r}} != 2
or $po->{$r}[0] == $po->{$r}[1];
}
if( defined($po->{wedge}) ) {
$po->{wedge} = !!$po->{wedge};
}
if( length($po->{logaxis}) ) {
if($po->{logaxis} =~ m/[^xyXY]/) {
barf "logaxis must be X, Y, or XY (case insensitive)";
}
$po->{logaxis} =~ tr/XY/xy/;
$po->{logaxis} =~ s/yx/xy/;
}
unless($po->{oplot}) {
$keys = [];
}
$po->{justify} //= ($called_from_imag ? 1 : 0);
##############################
# Parse out curve blocks and check each one for existence.
my @blocks = ();
my $xminmax = [undef,undef];
my $yminmax = [undef,undef];
while( @_ ) {
my $co = {};
my @args = ();
if (ref $_[0] eq 'HASH') {
$co = shift;
} else {
# Attempt to parse out curve option hash entries from an inline hash.
# Keys must exist and not be refs and contain at least one letter.
while( @_ and !ref($_[0]) and $_[0] =~ m/[a-zA-Z]/ ) {
my $a = shift;
my $b = shift;
$co->{$a} = $b;
}
}
##############################
# Parse curve options and expand into standard form so we can find "with".
$curve_options->options({key=>undef});
my %co2 = %{$curve_options->options( $co )};
my $ptn = $plot_type_abbrevs->{ $co2{with} };
barf "Unknown plot type $co2{with}"
unless defined($ptn) and defined($plot_types->{$ptn});
if($co2{key} and !defined($po->{legend})) {
$po->{legend} = 'tl';
}
unless( $ptn eq 'labels' ) {
my $ptns = $ptn;
$ptns=~s/s$//;
push @$keys, $co2{key} // sprintf "%s %d",$ptns,1+@$keys;
}
my $pt = $plot_types->{$co2{with} = $ptn};
##############################
# Snarf up the other arguments.
while( @_ and ( UNIVERSAL::isa($_[0], 'PDL') or
looks_like_number($_[0]) or
ref $_[0] eq 'ARRAY'
)
) {
push @args, shift;
}
##############################
# Most array refs get immediately converted to
# PDLs. But the last argument to a "with=labels" curve
# needs to be left as an array ref. If it's a PDL we throw
# an error, since that's a common mistake case.
if ( $ptn eq 'labels' ) {
barf "Last argument to 'labels' plot type must be an array ref!"
if ref($args[-1]) ne 'ARRAY';
$_ = PDL->pdl($_) for grep !UNIVERSAL::isa($_,'PDL'), @args[0..$#args-1];
} else {
$_ = PDL->pdl($_) for grep !UNIVERSAL::isa($_,'PDL'), @args;
}
##############################
# Now check options
barf "plot style $ptn requires ".join(" or ", @{$pt->{args}})." columns; you gave ".(0+@args)
if !grep @args == $_, @{$pt->{args}};
if ($ptn eq 'contours' and @args == 1) {
my $cntr_cnt = 9;
push @args, zeroes($cntr_cnt)->xlinvals($args[-1]->minmax);
} elsif ($ptn eq 'polylines' and @args == 1) {
barf "Single-arg form of '$ptn' must have dim 0 of 3"
if $args[0]->dim(0) != 3;
@args = ($args[0]->slice('0:1'), $args[0]->slice('(2)'));
} elsif (defined($pt->{args}[1])) { # Add an index variable if needed
barf "First arg to '$ptn' must have at least $pt->{ndims}[0] dims"
if $args[0]->ndims < $pt->{ndims}[0];
if ( $pt->{args}[1] - @args == 2 ) {
my @dims = ($args[0]->dims)[0,1];
unshift @args, xvals(@dims), yvals(@dims);
}
if ( $pt->{args}[1] - @args == 1 ) {
unshift @args, xvals($args[0]);
}
}
if ($ptn eq 'contours') { # not supposed to be compatible
barf "Wrong dims for contours: need 2-D values, 1-D contour values"
unless $args[0]->ndims == 2 and $args[1]->ndims == 1;
($xminmax, $yminmax) = ([0, $args[0]->dim(0)-1], [0, $args[0]->dim(1)-1]);
} elsif ($ptn eq 'polylines') { # not supposed to be compatible
barf "Wrong dims for contours: need 2-D values, 1-D contour values"
unless $args[0]->ndims == 2 and $args[1]->ndims == 1;
($xminmax, $yminmax) = map [$_->minmax], $args[0]->using(0,1);
} else {