Some words of wisdom here
plain exampleExample examplehtbploe[chapter]
A central requirement of any data analysis package is the possibility of visualisation of data. PDL deals with this in a slightly different manner than some other packages in that no built-in graphics library is used, instead it uses other freely available external packages. In this chapter we will focus on the main 2D plotting package, PGPLOT. The 3D interface is detailed in the next chapter.
Here we will cover the use of the PDL::Graphics::PGPLOT package which uses the freely available PGPLOT subroutine package written by Tim Pearson4.1. This is a very powerful package and PDL::Graphics::PGPLOT does not provide easy access to everything in the PGPLOT package, although it hopefully does most of what you will need.
For advanced use you might have to use some PGPLOT commands directly,
see section
for a discussion of
this. But even if you don't you are recommended to at least keep a
copy of the PGPLOT documentation lying around. It is available from
http://www.astro.caltech.edu/tjp/pgplot.
The goals of the chapter is to familiarise the reader with the PDL interface to PGPLOT and show how complicated datasets can be easily manipulated and displayed. The focus will be on interactive use to facilitate learning, but at the end we will turn to an object-oriented interface that might be more suited for scripts.
To use PDL::Graphics::PGPLOT it is necessary to have the PGPLOT package installed, and in addition have the Perl PGPLOT module (written by Karl Glazebrook and available through CPAN) installed and working. In the following we will assume that you have this all set up.
2-dimensional graphics in PDL is normally performed by the PDL::Graphics::PGPLOT module. The PDL::Graphics::PGPLOT package must be use'ed to give access to the commands. This introduction will be based on interactivity and use of perldl (see also The Whirlwind tour in chapter 1):
That is what you need to get running. We will now play around with a
couple of commands before we turn to a systematic overview in the next
two sections. We will concentrate on the line and
points commands which draws continuous lines and individual
plotting symbols respectively. The final result should look similar to
Figure
.
The first step is to start perldl and use the PDL::Graphics::PGPLOT package (some output is suppressed)
> perldl Type 'help' for online help Type 'demo' for online demos Loaded PDL v2.1 perldl> use PDL::Graphics::PGPLOTNow we need to open a graphics device -- there are quite a few that are supported by PGPLOT, here we will use a normal plot window that can be re-used
perldl> dev('/xs')
You should now have a large plot window on your screen, if you had
some problems try to do dev('?') which will give you a list
of available devices and allow you to choose one.
We first need to define a variable to have something to plot. The
first plan is to simply plot a parabola and a Gaussian (bell) function
as in the left panel in Figure
, so we
need an x-variable that is both positive and negative.
We now want to plot a Gaussian on top of this, but if we were just to issue another plot command it would by default erase the screen, so instead we call the hold function to stop that from happening
The only thing left for us now is to ensure that the next plot will start afresh. Since we issued the hold command all subsequent plots will overplot the existing ones and since we do not want that anymore, we therefore have to release the device to the next set of plot commands:
As a second example we will show how you can create plots with error
bars. We will just carry on so the previous plot will be erased (enjoy
it while you can). We first have to define some variables for the
plot, so we need the
&
variables and the error on
.
In the previous example we let PGPLOT decide on the plotting ranges we were going to use, but now we want some more control over it. To do so we set it up using the env command
That is really all that is needed before plotting the error bars:
Before we proceed to an overview of all commands in PDL::Graphics::PGPLOT it is necessary to define a couple of terms: The first is the concept of device -- this is what the plotting commands work on, often this will be a screen device which shows resulting output on the screen in a window, but it can also be output to a file in some sort of format. Then inside each device there is a plotting area within which plotting commands gives a noticeable result.
Another important concept is holding of plots. When a plot is held, any subsequent plot commands will plot on top of the existing plot. To explicitly hold a plot you issue the command hold and to release it again you use release.
Finally most commands described in the following take a set of options. These are values that can be set to modify the default behaviour of the plotting routine and are very useful so we will first discuss the standard options and how options are specified.
As mentioned above and seen in the brief introduction to the PGPLOT interface earlier, we use options to modify the behaviour of plot commands. Below we will often see examples of specific options, those that are only recognised by a particular plot command. However in addition there are general options that are recognised by many or all plot commands. These are normally the options you use most so it is important to know these.
But first, how do you specify an option? If you read through the walk-through above you have probably already realised that they are set as keys in a hash:
line $x, $y, {Colour => 3}
However due to the way they are implemented in the code (using the
PDL::Options package) the hash is more flexible than normal
Perl hashes. Firstly the options are case-insensitive and secondly
some have synonyms defined so that for instance Color and
Colour are both accepted to avoid bad feelings on one side of
the Atlantic. Finally most, if not all, options can be shortened so
that Lines will be interpreted as LineStyle. This
is mostly useful when working on the perldl command line
however as it is error-prone in scripts (imagine that someone later
implemented a Lines option which did something totally
different, like draw 10 parallell lines, yeah, quite likely).
The following listing of standard options is based on the on-line documentation which you can access yourself inside perldl as
perldl> help PDL::Graphics::PGPLOT::Window
or on a command line using the pdldoc command
odin> pdldoc PDL::Graphics::PGPLOT::Window
It is not envisaged that the standard option set will be significantly
expanded from that listed here, but the on-line documentation should
reflect any changes if they take place.
The following
Alternatively the arrow can be specified as a set of numbers corresponding to an extention to the syntax for the PGPLOT command pgsah. The equivalent to the above is
For the latter the arguments must be in the given order, and if any are not given the default values of 1, 45, 0.3 and 1.0 respectively will be used.
) It can either be specified as a
number, or by a name as in the following table
| Name | Numerical | Explanation |
| Empty | -2 | draw no box, axes or labels |
| Box | -1 | draw box only |
| Normal | 0 | draw box and label it with coordinates |
| Axes | 1 | same as Normal, but also draw
( |
| Grid | 2 | same as Axes, but also draw grid lines |
| LogX | 10 | draw box and label X-axis logarithmically |
| LogY | 20 | draw box and label Y-axis logarithmically |
| LogXY | 30 | draw box and label both axes logarithmically |
The reason why this command is accepted by most commands is that when a command is called before a plot area is set up it will implicitly call env which interprets this option.
Border => { Type => 'Rel', Value => 0.05 }
| 0 | White | 1 | Black | 2 | Red | 3 | Green | 4 | Blue |
| 5 | Cyan | 6 | Magenta | 7 | Yellow | 8 | Orange | 14 | Darkgray |
| 16 | Lightgray |
In addition the package will also try to interpret non-recognised colour names using the default X11 lookup table, normally using the rgb.txt that came with PGPLOT.
For more details on the handling of colour it is best that the user consults the PGPLOT documentation. Further details on the handling of colour can be found in the documentation for the internal routine _set_colour.
| 1 | Solid | 2 | Outline | 3 | Hatched | 4 | CrossHatched |
| 1 | Normal | 2 | Roman | 3 | Italic | 4 | Script |
\fn, \fr, \fi and \fs respectively. See
the documentation in section
for more
information regarding escape sequences.
$hatch=pdl[$angle, $sep, $phase].
below.
| 1 | Solid | 2 | Dashed |
| 3 | Dot-Dash | 4 | Dotted |
| 5 | Dash-Dot-Dot |
| 0 | Square | 1 | Dot | 2 | Plus | 3 | Asterisk |
| 4 | Circle | 5 | Cross | 7 | Triangle | 8 | Earth |
| 9 | Sun | 11 | Diamond | 12 | Star | 17 | Default |
The default options for screen display are not ideal for hard-copies (typically PostScript). Thus there is a separate set of options for certain properties when the output device is a hard-copy one. Here we will quickly summarize these
These options should be set either in the call to dev (see
section
below) or redefined using the
method outlined in the next section.
You might not be happy with the default settings for the various options and want to set a different value permanently instead of specifying it with every call to dev, env or some other command. There is some support for this, but it is limited in that it is not case-insensitive nor does it have synonyms (except for colour/color) so the options must be written as above. (You will be notified if you did something wrong).
That said it is fairly easy to use. You would normally set this in your .perldlrc file (see XXXX). The relevant function is set_pgplot_options which takes a hash as argument with the options and their values, as in the following example:
use PDL::Graphics::PGPLOTOptions ('set_pgplot_options');
set_pgplot_options('Device' => '/xs', 'LineWidth' => 10);
Note that some settings might affect more than you like. In particular the LineWidth and LineStyle options will also affect the axis and axis labels drawn. Unless you are after the blurred, completely unreadable look that is not what you want. Trust me. However character size, device default plot symbol, border and other options can be conveniently be specified in this way.
The first step for the budding plot maker is to set up the drawing area. This involves selecting what device you want to create the plots on and then setting the region you want to plot in4.2.
The destination for your plot commands is set with the dev command, and with different arguments to dev you can send plots to various output devices such as
For more detailed control over the created device, you can specify various options. The main four options you might use are
perldl> dev('/xs', {NX => 4, NY => 2})
will create a plot window with four panels in the X-direction and 2 in
the Y-direction, with a default aspect ration and size.
Alternatively the same window could have a specified width and aspect
ratio by specifying those options as
However dev does not actually draw anything for you, it
merely selects the output device. To set up a plot you either call a
plot command directly, or if you want more control over the axis
ranges you use the command env. This useful command takes
the upper and lower limits in X and Y as input
|
env(0, 1, 0, 1);
|
|
|
env(1, 1000, 0, 1, {Axis => 'LOGX', Xtitle => 'X-axis', Ytitle => 'Y-axis'}) |
|
.
It is important to realise that when you call env explicitly it automatically holds the plot for you, so subsequent plot commands will plot on top of the plotting area, and if you want to make a new plot you need either to call env again or call release explicitly.
The most important commands in the graphics package are probably the
line drawing and point plotting commands line and
points. The most basic command is points which
plots particular symbols at given x&y values:
|
perldl> $x = sequence(10)
perldl> $y = $x*$x + 1 perldl> points $x, $y |
|
|
perldl> points $x, $y, {Symbol => 'Triangle', Plotline => 1, Charsize => 5} |
|
). The points command does
also accept a piddle as the symbol value, in which case it should have
the same length as $x and $y and each point will be plotted with the
corresponding symbol value.
Closely related to points is the routine for plotting
symbols with error-bars, errb. This can be called in a
variety of ways to allow for various ways of giving errorbars and
whether horisontal or vertical errorbars are required. A typical call
is
|
perldl> env(0, 5, -2, 30) perldl> $x=sequence(10)/2.0; $y=$x*$x perldl> $dy = sqrt($x+1); perldl> errb $x, $y, $dy, {Symbol => 'Square'} |
|
|
perldl> $x2 = pdl(1.5, 2.3, 4.7) perldl> $y2 = pdl(10, 22, 0) perldl> $dx = $x2->zeroes(); # No X-errors perldl> $y_u= pdl(12,29,1)-$y2 perldl> $y_l= $y2 - pdl(7, 20, -2) perldl> errb $x2, $y2, $dx, $dx, $y_l, $y_u, {Symbol => 'Triangle'} |
|
We saw above that we could draw lines between points by setting the
PlotLine option to points, however there are
much better ways to draw lines. The basic line-drawing command is
line which draws a straight line between each point.
|
perldl> $x = zeroes(10)->xlinvals(-3, 3) perldl> line $x, sin($x) |
|
above.
A very similar command is bin which is useful for plotting
histograms. This command draws horisontal lines between
and
with the value
.
|
perldl> $x = zeroes(10)->xlinvals(-3, 3) perldl> bin $x, sin($x) |
|
Finally the poly command is like line but fills
the polygon defined by $x and $y with the chosen fillstyle (defaults
to solid fill). If you display this you should consider putting
FillStyle => 'Outline' in your .perldlrc file as
explained in section
, or you can set
it explicitly as in the following example:
|
perldl> $x=zeroes(20)->xlinvals(-2,2); perldl> $y=exp(-$x*$x); perldl> $xpoly = append($x->where($x <= 0), pdl(0)); perldl> $ypoly = append($y->where($x <= 0), pdl(0)); perldl> poly $xpoly, $ypoly, {FillType => 'Hatched'}; |
|
.
PGPLOT was originally designed for astronomy and as such it has good
support for the display of 2D-data. In PDL this support has been
simplified and there is now only one command for image display,
imag, which internally chooses between different PGPLOT
display commands. The simplest use of imag is to let it act
on a 2D
piddle so
|
perldl> $a = rvals(50,50, {Center => [ 25, 25]}); perldl> imag $a; |
|
However, most likely you will find that the shape is not circularly
symmetric because the aspect ratio of your graphics window is
different from 1. How then can we correct this? The easiest solution
is probably to make sure that your graphics device has aspect ratio 1
by giving the Aspect option to the dev command
(see
).
That isn't always an option though, and an alternative approach is to use the option Pix to the imag command. This lets you adjust the aspect ratio of the image pixels. You can in addition specify the number of image pixels per screen unit with the option Pitch so that to display the previous image with square pixels and 2 image pixels per screen pixel you use
In addition you might want to specify a stretch of the gray-scale of the image. This can be obtained first by specifying the max and min values of the displayed image (everything above is set to the max value and everything below to the min value). This is set with the Min and Max options. Additionally it is possible to adjust the image transfer function using the option ITF. Allowed values are Linear, Log and Sqrt.
You can also add a colour bar (colour wedge in PGPLOT parlour) to the image display. This is accomplished either using the draw_wedge (see below) command directly or by setting the DrawWedge option to true in your call to imag. If you want to pass options to the draw_wedge command, you can do that with the Wedge option. See below for further details.
Finally a very useful feature of PGPLOT that is relevant both to
images and also the contour plots (see below) is the concept of a
transform ``matrix''. This is a
element vector,
which maps
input pixels into display pixels so that pixel
is mapped to
use PDL; use PDL::Graphics::PGPLOT; # Create two plot areas in the X-directions dev('/xs', 2, 1); # Create a Gaussian around the center of the image $a=rvals(101, 101, {Center => [50, 50]}); $y = exp(-$a*$a/50.); # Display with a linear transfer function imag $y; # This transform vector maps the extreme points to my $tr = pdl(-10, 1.0/5.0, 0, -10, 0, 1.0/5.0); # Finally display the image with the transform and # a logarithmic transfer function. imag $y, {Transform => $tr, ITF => 'Log'}; |
We have put all this together in
Example
. Here we show the use of the
ITF and and Transform options. Note that using
Transform in conjunction with Pix is going to lead
to unwanted results!
It is often desireable to annotate an image with a colour wedge showing the range of values in the image. This is accomplished with the draw_wedge function in PDL::Graphics::PGPLOT (but you can avoid calling this directly by setting the DrawWedge option in your call to imag, see above). This function should normally give a decent result without the user setting any options except the Label option which sets the annotation, but occasionally it is necessary to change its behaviour and that is done by setting the following options:
dev '/xs', {WindowWidth => 6, Aspect => 1}; $im = rfits('Frei/n4013_lJ.fits'); $im += abs(min($im)-1); $im = log10($im); imag($im, {PlotPosition => [0.1, 0.85, 0.175, 0.925], Min => 2.6, Max => 2.0 }); draw_wedge({Wedge => {Width => 4, Label => 'Log Counts', Displacement => 1}}); |
Note that you will sometimes need to directly set the plot size to
avoid clipping in the display. A full example that shows the use of
draw_wedge can be seen in Figure
where we display a galaxy and display a look-up table next too it.
Contour plots are very similar to image displays and display lines at
particular levels of the image. The function to create contour plots
is cont which at the simplest level only takes a 2D array as
its argument.
|
$a = sequence(100,100); cont $a;
|
|
You use the option Contours to give the wanted contour levels
as a piddle and Labels to give an anonymous array of strings
for labels as shown in the example below
|
use PDL; use PDL::Graphics::PGPLOT; dev('/xs'); $y = ylinvals(zeroes(100,100), -5, 5); $x = xlinvals(zeroes(100,100), -5, 5); $z = cos($x**2)+sin($y*2); cont $z, {Contours => pdl(-1, 0, 1), Labels => ['-1', '0', '1']}; |
|
In addition it is possible to colour the labels differently from the
contour lines (LabelColour), to specify the number of
contours instead of their values (NContours) and to draw
negative contours as dashed lines and positive as solid lines by
setting the option Follow to a value
.
Overlaying a contour plot on top of an image is as easy as displaying the image, call hold and display the contour plot. The reader might want to try a colour version of the example above ($z as in the example):
The final 2D plot command we will deal with here is the command for plotting a vector field, vect. This command takes two arrays as arguments. The first gives the horisontal component and the second the vertical component of the vector field. The length of the vectors can be set using the SCALE option and the position relative to the pixel centers with the option POS.
What is important to note with a command like vect is that
you can use the Transform option to map a smaller vector
array to a larger image. This is often useful because a vector field
with
arrows on top of a similarly sized image will
quickly be unreadable. The result of using this technique is shown in
Figure
together with the code that produced
the plot.
perldl> $x = xlinvals(zeroes(100,100), -5, 5)
perldl> $y = ylinvals(zeroes(100,100), -5, 5) perldl> $z = sin($x*$y/2) perldl> imag $z; hold; Show the partial derivatives wrt. x & y as vectors perldl> $xcomp = $x*cos($x*$y/2)/2 perldl> $ycomp = $y*cos($x*$y/2)/2 We want to show only every sixth vector for clarity perldl> $s = '0:-1:10,0:-1:10'; Finally we need to map the final 10x10 array to the 100x100 image perldl> $tr = pdl(0,10,0,0,0,10) perldl> vect $xcomp->slice($s), $ycomp->slice($s), {Transform=>$tr} |
In addition to the simple commands described above, there are a few convenient commands for drawing simple shapes such as circles, ellipses and rectangles. These are fairly straightforward commands with similar options and invocations so I will go through them fairly quickly. A common issue with these commands as with the poly command is that they draw filled shapes, if you want outlined shapes to be drawn you have to set the Filltype option to Outline.
The circle command is probably the simplest, it draws a circle (which
may or may not look like a circle depending on the aspect ratio of
your display -- see section
above). The
user specifies the radius and the X&Y position of the centre
|
perldl> dev '/xs', {Aspect => 1, WindowWidth => 5} perldl> env 0, 10, 0, 10 perldl> $radius=2; ($x, $y) = (4, 4) perldl> circle $x, $y, $radius, {LineWidth => 3} |
|
The ellipse function is like the circle function
but it requires the user to specify the minor and major axis and the
angle between the major axis and the horisontal. For ease of use it is
probably better to specify these as options, but if you remember
the order you can also give them directly as arguments to the function
(
-position,
-position, major axis, minor axis, angle)
|
perldl> dev '/xs', {Aspect => 1, WindowWidth => 5} perldl> env 0, 10, 0, 10 perldl> ellipse 4, 4, {MajorAxis => 2, MinorAxis => 1, Theta => atan2(1,1)} |
|
And finally the rectangle command draws rectangles where you
can give the position of the centre, the length of the sides and the
angle with the horisontal. The operation is very similar to the
ellipse command with the length of the sides of the
rectangle taking place of the major and minor axis.
|
perldl> dev '/xs', {Aspect => 1, WindowWidth => 5} perldl> env 0, 10, 0, 10 perldl> rectangle 4, 4, {XSide => 2, YSide => 1, Angle => atan2(1,1)} |
|
In addition you can set the sides to be similar by setting the Side option to the length you require. The lengths are all specified in data-coordinates (which is why you should do a plot or call env before using any of these commands).
For other shapes or when these are not sufficiently flexible you should use the poly command which is called by both rectangle and ellipse.
The main command for drawing text on the plotting surface is the
text command which at its basic level just draws a string
from the given
&
position:
|
perldl> dev '/xs' perldl> env 0,10,0,10, {Axis => 'GRID'} perldl> text 'Left justified', 4, 1 perldl> text 'Centered', 4, 2, {Justification => 0.5} perldl> text 'Right justfied', 4, 3, {Justification => 1.0} |
|
In addition to the justification option one can also change the angle
of the text using the Angle option and specify the text
and/or
&
as options (the best advice is to either do all or
none).
PGPLOT has extensive support for non-alphanumeric characters in text
strings and also offers reasonable control over the display of
superscripts, subscripts etc. This is all achieved using
escape sequences. In PGPLOT these are all signaled by the
character \. Thus \u starts a superscript or ends a
subscript -- it signals a shift ``up''. Likewise \d starts a
subscript or ends a superscript. See the example for legend
in Figure
for an example. Here is an
incomplete list of the most useful ones, but there are many
more. Consult the PGPLOT documentation for a full list.
\u |
Go up | \d |
Go down |
\x |
\. |
||
\A |
Å | \ga |
|
\gb |
\gg |
||
\m |
Graph marker |
\(nnnn) |
Hershey character nnnn |
\(0562) |
\(0683) |
||
\(2281) |
\(0766) |
||
\(2233) |
\(2266) |
The only additional text-related function in the PDL::Graphics::PGPLOT interface is the legend command which draws a legend in the plot window. This is a more complex routine which can be a time-saver as soon as you have learned how to use it. It takes the same arguments as the text command with the exception that the text argument is an anonymous array of labels for the legend, and that a fourth argument is accepted which specifies the width of the box in which the legend will be drawn. If this is not set or it is set to the string Automatic it will be adjusted to contain the legend with the default font-size (or that set by the user via the CharSize option).
perldl> $x = sequence(10); $y1 = sqrt($x); $y2 = $x**2;
perldl> line $x, $y1, {LineStyle => 'Dashed', Colour => 'Red'} perldl> line $x, $y2, {LineWidth => 3, Colour => 'Blue'} perldl> legend ['sqrt(x)', 'x \u2'], 0.5, 10,
{LineStyle => ['Dashed', undef], LineWidth => [undef, 3], Colour => ['Red', 'Blue'], Width => 1.0} |
The idea of the legend command is that you give the line-styles, line-widths, colours or symbols you want to illustrate as anonymous arrays to the LineStyle, LineWidth, Colour and Symbol options. Not very clear? Well, maybe an example will help.
Figure
shows an example of legend
in use. Two lines are drawn, a red dashed line and a blue thick line.
To annotate this plot using legend you give the text
annotations as an (anonymous) array of strings, the X&Y position of
the legend box and an anonymous hash containing information about the
legends to draw as shown in the example. The options used to specify a
particular draw style are the same as the ones used in the call to
line and will undergo the same translations -- note however
that you can specify a value of undef which requests that the
current default for the linestyle/linewidth/colour etc. is used. The
Width option is used to set the width of the legend box and
is given in data coordinates. The idea is that you will create the
plot, see where you want the legends to go and then set the X&Y and
width to the appropriate settings and redoing the plot, possibly using
the replay mechanism (see section
).
The legend command has several options the main of which are
illustrated in Figure
. The remaining
options are useful for tweaking the appearance, and a full list is as
follows
PGPLOT has a two disjoint sets of colours. One set determines the
colour table used when displaying images and is initialised to a
grayscale, and the other is a set of 15 colours used to colour all
other plotting objects. The latter set is accessible through the
Colour option described in section
above. Here we will concentrate on accessing the lookup-table for
image display.
The command used to change the colour table is ctab, which in its generic form takes six arguments specifying the intensity levels, red, green and blue colour components, contrast and brightness levels. The contrast and brightness are optional so that we can say
|
perldl> $int = pdl([0, 0.33, 0.66, 1.0]) perldl> $r = pdl([0.5, 0, 0.5, 1]) perldl> $b = pdl([0.0, 0.5, 1.0, 0.5]) perldl> $g = pdl([1.0, 0.5, 0.0, 0.5]) perldl> ctab($int, $r, $g, $b); perldl> $a = rvals(100, 100) perldl> imag $a |
|
It is however normally sufficient to use the colour tables made available by PDL::Graphics::LUT. This package makes available a large number of standard colour tables which can be accessed using the following commands
Thus to set one of the colour tables in the PDL::Graphics::LUT package, you do
The plot commands do not always lend themselves to easy threading (see chapter 3 for a detailed discussion about threading) because it can sometimes be difficult to know what the user intends to do when (say) an array of images is passed to the imag command. Are they to be displayed in several plot panels, are they to be plotted on top of each other, seamlessly plotted next to each other? But even more complex is the question of treatment of options and how to deal with these if there are less options than for instance, lines to draw (a common occurence if you wanted to draw a lot of lines).
That said the PDL::Graphics::PGPLOT interface does have limited support for
threading in the line and points functions. These
call the tline and tpoints internally, and work
just like line and points except that they expect
the input
-piddle to be 2D, with each line in the array plotted
against the
-piddle.
The way the options are treated is the most interesting. To set options for a set of lines, give an anonymous array as argument to that option with a value for each line. If you give more options than there are lines, the surplus is ignored. However if you give less, the options are repeated from the start. Although possibly a bit confusing this is very powerful because you can get a large number of combinations of colour and linestyle. For instance if you give 4 colours and 5 linestyles, you get a total of 20 distinct combinations and should you give 3 linewidths as well you will suddenly have 80 different styles to work with with very little typing. Note however that you need to make sure that the numbers you give are relatively prime -- otherwise you will get much less possibility, just think of the situation where you have 4 linestyles and 4 colours, they will just loop in harmony and result in only 4 combinations.
Anyway, let us see how it all works in practice by creating a plot of sine curves with different frequencies. This is a simple example where we want to colour all even frequencies with red and all odd with blue and vary the line-styles as well.
|
perldl> $pi=4*atan2(1,1); perldl> $x=zeroes(50)->xlinvals(0, $pi) perldl> $freq = sequence(10) perldl> $y = sin($freq*transpose($x)) perldl> line $x, $y, {Colour => ['Red', 'Blue'], Linestyle=>[0,1,2,3,4,5]} |
|
Small problem: If no options are given, the plot is not properly plotted
Have you ever created a good-looking plot on the command line of an interactive data program, be it PDL, IDL, Matlab, Octave or any other package, and wished that you could make a quick Postscript copy of it only to find that you need to redo all the commands? I certainly have.
In the newer versions of PDL this is thankfully not the case anymore. These have a recording facility built in. However this is not enabled by default (for reasons described later in this section), you need to turn it on yourself. The way to do this is to set the $PDL::Graphics::PGPLOT::RECORDING variable to a true value.
In the following I will focus my attention on using the recording and playback functions in the perldl shell as I envisage that it will be most useful there. There are a couple of potential uses in scripts as well which I will get back to below, but this is not well thought through yet.
Before we continue it should also be added that the recording facility is somewhat experimental. In particular it doesn't deal very well with multi-panel plotting where you jump back and forth between panels. If you want to do that, make sure you specify the Panel option for every call.
It is very easy to use the recording facilities with a few less obvious aspects. An example should go a long way to get you to understand the basics. First we set up a simple plot using the commands we learned above:
The retrieve_state commands retrieves the current state of the plot device and returns a variable to hold this in. This state contains references to the data plotted and plot commands executed and can be replayed, or re-executed, at a later stage using the replay command. You can also turn on and off recording temporarily with the turn_off_recording and turn_on_recording commands.
This suffices for most situations and should work for any complexity of plot constructed. There are however a few rules that needs to be observed and possible pitfalls.
What we covered now is the basic use of the recording facility, which hopefully will come in handy rather often (which is why I recommend enabling it permanently in the perldl shell as outlined above). However there are slightly less common uses of the facility that might come in handy:
The fact that the recording state contains references to the data enables a somewhat tricky but potentially very useful trick to be executed: Redoing the plot with adjusted data. Sometimes you make a complex plot only to discover that you had made an error with your data and you need to redo it. This is where you can use the recording functions: Retrieve the state, make adjustments to the data making sure not to break the link (see chapter XXXX) and run replay.
However, although this sounds quite easy it has a few subtleties that can give surprising results at times. It might therefore be a good idea to look at a few, very similar and very basic, examples and compare their effects. So let us first of all open a plot device:
NOTE: What I describe here is not well tested and is probably buggy. This needs to be sorted out before finishing -- at least I have had a few weird results when tryin this out.
We are going to use our age-old example of plotting a parabola, and replaying it with various parameter sets. Let us therefore define a couple of variables and plot this, first letting PDL decide on the plot limits:
In the previous example the limits in the plot window adjusted to the
new values for
&
because the line command sets the
plot limits if the plot is not held (such as with an explicit call to
env). But what happens if we redo the example with our own
chosen limits?
Finally you must remember that the reference is not to a variable name, but to a piddle which exists separately from the variable. Thus you cannot change your data at a whim, so the following change will change the data back to where we started
However sometimes you want to give a entirely new dataset to the plot. Say you wanted to plot a sine curve instead of a parabola. Is there any way to do that? The answer is yes, but it looks rather ugly, so you might want to consider whether this is something you want to do
In general the recording facility is of rather limited use in scripts because you can just as easily encapsulate your plot commands in a subroutine and just call the subroutine when need be. At present the only saving is probably in typing, but if the facility is extended to saving and restoring plot commands the situation would change.
Assume for once that you are developing a simulation. When you are testing the code (all written in PDL of course) you have to keep track of how some data changes at every time-step, but at the same time you want to look at time-averages. If you were to use what we discussed above you would probably want to display the time-steps in one panel and the time-averages in another panel in a plot window. The problem with this is of course that one panel is updated a lot more often than the other so you have to waste a lot of time re-plotting the time-average.
Clearly there are two possible ways to improve this: a) have a method which allows you to plot to a given panel when you want and b) have to plot windows. It is possible to use the first approach by giving the Panel option to the plot commands:
This can be practical, but it is rather limited given the requirement of giving the panel number every time. Instead an alternative approach would be to create several plot windows, and for this you really ought to use an object oriented approach4.3. In this approach every plot device is a separate object and you call every plot command via this object. So the previous example would be
So, you may say, what is the point with the OO interface except appeasing the OO fanatics around? It seems to require more typing and I can see no significant advantage.
In many situations these are valid arguments, if you are just plotting data on the command line in perldl, for instance, or do not need multiple plot windows. And at some level the OO interface is primarily a convenience for the programmer, and it is in fact how the PDL::Graphics::PGPLOT package is implemented. That said though there are some (possibly strong) arguments for using the OO interface:
Eventually an argument in favour of the OO interface will hopefully be that it would enable an easier mix of different plotting packages so that they can all be accessed in a similar way, but we are not there yet.
To use the OO interface one needs to create a new plot object and then call the plot routines through this object. If you want several windows, you just create more objects and switching between these should be straightforward as you should be able to see in the following examples.
Note that since the OO interface is less suited to use on the command line, I have opted to show the examples as small code-bits but they should all be possible to execute from the perldl command line. In addition this section will merely give several examples of use of the OO interface and no discuss (again) the different commands since they are the same as we went through above, it is just a different way of calling them.
To create a plot object we first need to use the PDL::Graphics2D package -- this is merely a shortcut for the true PDL::Graphics::PGPLOT::Window package, but why type more when it doesn't gain you anything? Then we create the object using the standard Perl notation PDL::Graphics2D->new():
Note how we use the window object ($w) when calling the points routine -- since we didn't
use the pgpkg package there isn't any function
called points in our namespace and we use the window object
to get hold of it. The structure is of course very similar to what we
did in section
above and there
really is little practical difference between the two interfaces when
plotting to only one window.
Therefore let us up the stakes somewhat and try a more practical example. In many situations you might have one plot where each point in the plot has many values associated to it (ie. your plot is a slice in a multidimensional space). When you examine such data you often would like to click on a point on your plot and bring up associated data for that point in a different display -- this is an obvious situation for the OO interface.
The logic for this project is easy: We first create two windows
The next step is to plot data (well, in this example I will merely create them):
That should now give you a sine wave in the second window with a frequency dependent on where along the X-axis you clicked. Of course it would be a lot easier to use $xin, but that wasn't what we tried to do after all.
This is of course a very simplified example, but it does provide a framework for a more comprehensive data explorer. From astronomy a typical example would be to plot scatter-plots for two variables and bringing up images of the objects by clicking at their data in the plot window. In other situations the data might be financial data for a set of companies and clicking on the points would bring up a comprehensive summary of that company. But this is of course where your imagination is supposed to run amok.
The bottom line is that whatever your requirements are, the OO approach is probably better when you need more than one plot window, but when you only use one window, and particularly on the perldl command line.
The Perl module PGPLOT4.4 contains interfaces to all PGPLOT functions. The majority of these functions have alternative interfaces in the PDL package, but there might be situations when you need to use these functions directly. And in addition if you are used to using PGPLOT from before you might prefer the interface, although it is rather inconvenient when dealing with PDL.
Full documentation for the PGPLOT functions can be found at Tim Pearson's WWW page: http://astro.caltech.edu/tjp/pgplot/. This is not the place to discuss the details of PGPLOT, but it is interesting to learn how to access these routines from PDL with piddles as arguments.
Typical PGPLOT drawing functions take as arguments the number of points and references to perl arrays to give x&y coordinates, thus
@x = (1,2,3); @y = (3,-1,7); pgpoint(3, \@x, \@y, 4);will plot three points with the x&y values indicates and using plotting symbol 4 (circle).
The complication for PDL users is that piddles are not perl arrays and hence have to be converted to array references before they can be passed to a PGPLOT function. This is achieved with the get_dataref command which returns a reference to the data in a piddle. Thus the example above would be written
In general you should use the provided wrapper routines for readability, but feel free to combine the two if you prefer. You should be able to pick'n'mix functions from the PDL interface and from PGPLOT directly, although a few subtle bugs might creep in (in particular the handling of several plot windows).
There are several situations where direct access to PGPLOT might be necessary. Although hopefully they are not very common, it can be useful to look at a few to see what the PDL::Graphics::PGPLOT module doesn't do. Since it is possible to mix PGPLOT commands with the PDL::Graphics::PGPLOT commands this is not a major problem though, although it might require you to learn some PGPLOT. So to turn to some examples, I have decided to list a few simple problems:
The bottom line is that as your plots get more and more complex you might end up in a situation where you need the finer control offered by the PGPLOT package, but for day-to-day use it is hoped that PDL::Graphics::PGPLOT will address most people's needs. And if doesn't we should be told!
![]() |
![]() |
There are lots of programs that let you plot so-called 3D surface graphs,
such as the one shown in Fig.
.
However, from the beginning, PDL's 3D graphics have had something
different that we feel is really useful:
motion, or as we call it ``twiddling''.
Dragging the 3D image with the mouse rotates the image, at the speed
allowed by your display hardware.
This turned out to be quite useful for displaying functions: the human
eye is able to grasp the presented 3D surface much better when
it moves, especially in response to the mouse.
Let's start with plotting the surface we showed using gnuplot in the beginning:
use PDL::Graphics::TriD; $x = xlinvals(zeroes(30), 0, 7); imag3d [ sin($x) * sin($x->dummy(0)) ];This should produce a new window with the image seen in Fig.
.
Notice that your console window is now frozen: it is waiting for
you to twiddle in the graphics window using the mouse and to press
q in that window once you're done.
If the above commands produce an error instead of a new window, it might be that your PDL wasn't compiled with the option to include the 3D library -- tough luck (XXX).
That above expression is a bit more difficult than the gnuplot version, and there's a simple reason for that: gnuplot is primarily meant for plotting functions; PDL is meant for handling and plotting numerical data. So to plot a function, we have to create the data for the function first which is a bit more difficult.
Now let's go through that part by part. The first line simply tells Perl to load the PDL::Graphics::TriD module. The name comes from the fact that you can't have parts of module names starting with numbers, unfortunately. The second line
$x = xlinvals(zeroes(30), 0, 7);creates a one-dimensional piddle with 30 elements that has linear values from 0 to 7:
perldl> p $x [0 0.24137931 0.48275862 0.72413793 0.96551724.....The xlinvals and the corresponding ylinvals and zlinvals are useful for exactly this purpose: creating piddles of equally spaced values. The final line,
imag3d [ sin($x) * sin($x->dummy(0)) ];is what draws the actual image. The expression inside, uses the variable $x for both the X and Y coordinates, via a clever use of the dummy operation. (XXX explanation?) This results in a 2-dimensional piddle with the values for the Z coordinate. So far you've already seen all this. And the final part, imag3d [vals] is the call that creates the 3D plot and opens the new window for it. The brackets around the parameter may be slightly surprising: the 2-D commands work well without those but there is a good reason for this, as you'll learn later on: otherwise there would be a bad ambiguity.
We alluded in the introduction that allowing
imag3d $piddle;could be ambiguous and should be written
imag3d [$piddle];if $piddle is intended to be the Z axis values of a rectangular 2D plot. Now is the time to find out why. The simple truth is that
imag3d $piddle;is in fact legal code - iff the first dimension of $piddle has exactly three elements. As you probably have already guessed, these three elements are X, Y and Z. So what you can do is pass $piddle with dimensions
Let's have an example: a curve that is not possible to plot with just Z axis values, say the surface of a torus, with colors coming from somewhere. First, set up the piddles and the parameter variables:
use PDL::Graphics::TriD;
$torus = zeroes(3, 60, 20);
$x = $torus->slice("(0)");
$y = $torus->slice("(1)");
$z = $torus->slice("(2)");
$t = xlinvals $x, 0, 6.28;
$u = ylinvals $x, 0, 6.28;
Note that the coordinate separation can be done in just one line:
($x, $y, $z) = map {$torus->slice("($_)")} 0..2;
Next, we color the torus. Let's put stripes on it:
$r = (1+sin(2*$t + $u))/2; $g = (1+cos(2*$t + 2*$u))/2; $b = (1+sin(2*$t + 3*$u))/2;
Then, we choose the outer and inner radii and put the coordinates into the slices. We'll let the torus lie in the XY plane so the parametric coordinates can be easily derived. (XXX diagram?)
$r_o = 3; $r_i = 1; $x .= ($r_o + $r_i * sin($u)) * sin($t) ; $y .= ($r_o + $r_i * sin($u)) * cos($t); $z .= $r_i * cos($u); imag3d_ns $torus, [$r, $g, $b];And there's our colorful torus! It looks a bit more like a barrel because TriD automatically scales the axes but there it is. Note how we use imag3d_ns to get the colors instead of the shaded version.
Now, there is more than one way to do it. If your data is not by default in the three-vector format (as ours wasn't above), it is probably easier to do
imag3d [$x, $y, $z], [$r, $g, $b];which will produce the same results. Also, we could concatenate the RGB piddles to form
Now, since PDL does its best to make dimensions usable anywhere, we can
easily plot several parametrics of the same parameters at once, if
we pack all the surfaces into a piddle of dimensions
where the three periods in the end indicate the beginning of the extra
parameters.
For example, we can plot a family of shrinking toruses by adding an extra dimension into $torus:
$cone = $torus->dummy(3, 4)->copy();
$fac = axisvals($cone, 3);
$cone *= $fac + 2;
($tmp = $cone->slice("2")) += 4 * $fac;
imag3d $cone;
And further, if we want to distort them, it's perfectly possible:
$x = $cone->slice("0");
($tmp = $cone->slice("2")) += 0.1 * $x ** 2;
imag3d $cone;
Any other kind of mutilation is also possible but we leave you to discovering the interesting things that are possible by yourself, because we have to move to something else that's important to cover: coordinate systems. So far, all the examples you've seen have happened in the Euclidean coordinate system where the coordinates are specified as measures X, Y and Z on three orthogonal axes.
Or actually this is not true: in fact, we have used two kinds of coordinates, the explicit X, Y and Z given in this section but in the preceding sections, only Z has been given and X and Y have been assumed by the system from the context.
Of course, since PDL tries to follow ``simple things simple, complicated things possible'', it is possible to override the default context.
So far, we've only been toying with surfaces. However, PDL can do much more. We can plot points; here's a picture of two samples from different (overlapping) probability distributions, plotted with different colors:
use PDL::Graphics::TriD; $i = zeroes(8000); $which = random($i) < 0.5; $x = grandom($i) * (1 + $which); $y = grandom($i) * (0.5 + $which); $z = grandom($i) * (2 - $which); $x += $which * $y; $y += $which * $z; # Make it oblique points3d [$x, $y, $z], [$which, 0.5*(1-$which), 1-$which];A lot of fun things can be done with points but we'll go into that later.
Then, there are -- of course -- lines. As a fun demo of lines, let's
plot a number of flowlines moving in the Lorenz attractor.
As you may know, the Lorenz attractor is described by
| (4.3) | |||
| (4.4) | |||
| (4.5) |
use PDL::Graphics::TriD;
$n = 500;
$nstart = 0;
$nc = 6;
$delta = 0.015;
# $x = pdl(1, 1, 1, 1, 1);
# $y = pdl(1, 1, 1, 1, 1);
# $z = pdl(1, 1.01, 1.02, 1.03, 1.04);
$xs = zeroes($n, $nc);
$ys = zeroes($n, $nc);
$zs = zeroes($n, $nc);
$x = -23 * ones($nc);
$y = -2 * ones($nc);
$z = 20 * ones($nc) + 0.02 * xvals($nc);
$sigma = 10; $r = 28; $b = 8.0/3.0;
for(-$nstart..$n-1) {
if($_