[202450]: plplot.pd Maximize Restore History

Download this file

plplot.pd    3865 lines (2965 with data), 107.7 kB

use Config;
use vars qw/$nomem $debug $plpoly3 $novect/;
my $ptrsize = length($Config{'byteorder'}); # 4 or 8 bytes
my $int_ptr_type = ($ptrsize == 4) ? 'I32' : 'long';

$debug = 0; # show generated pp_defs and other debug info

# Read in options determined by Makefile.PL and written to the OPTIONS! file
open (OPT, 'OPTIONS!');
my @opts = <OPT>;
close OPT;
$nomem = 0;
$nomem = 1 if (grep /NOMEM!/, @opts);

$novect = 0;
$novect = 1 if (grep /NOVECT!/, @opts);

$plpoly3 = 5;
$plpoly3 = 6 if (grep /PLPOLY3 = 6/, @opts);

$noalpha = 0;
$noalpha = 1 if (grep /NOALPHA!/, @opts);

$v59_or_earlier = 0;
$v59_or_earlier = 1 if (grep /NOPLSEED!/, @opts);

pp_addpm({At => Top}, <<'EOD');

BEGIN { 
$VERSION = '0.52'
};

=head1 NAME

PDL::Graphics::PLplot - Object-oriented interface from perl/PDL to the PLPLOT plotting library

=head1 SYNOPSIS

  use PDL;
  use PDL::Graphics::PLplot;

  my $pl = PDL::Graphics::PLplot->new (DEV => "png", FILE => "test.png");
  my $x  = sequence(10);
  my $y  = $x**2;
  $pl->xyplot($x, $y);
  $pl->close;

For more information on PLplot, see

 http://www.plplot.org/

Also see the test file, F<t/plplot.pl> in this distribution for some working examples.

=head1 DESCRIPTION

This is the PDL interface to the PLplot graphics library.  It provides
a familiar 'perlish' Object Oriented interface as well as access to
the low-level PLplot commands from the C-API.

=head1 OPTIONS

The following options are supported.  Most options can be used
with any function.  A few are only supported on the call to 'new'.

=head2 Options used upon creation of a PLplot object (with 'new'):

=head3 BACKGROUND

Set the color for index 0, the plot background

=head3 DEV

Set the output device type.  To see a list of allowed types, try:

  PDL::Graphics::PLplot->new();

=for example

   PDL::Graphics::PLplot->new(DEV => 'png', FILE => 'test.png');

=head3 FILE

Set the output file or display.  For file output devices, sets
the output file name.  For graphical displays (like C<'xwin'>) sets
the name of the display, eg (C<'hostname.foobar.com:0'>)

=for example

   PDL::Graphics::PLplot->new(DEV => 'png',  FILE => 'test.png');
   PDL::Graphics::PLplot->new(DEV => 'xwin', FILE => ':0');

=head3 OPTS

Set plotting options.  See the PLplot documentation for the complete
listing of available options.  The value of C<'OPTS'> must be a hash
reference, whose keys are the names of the options.  For instance, to obtain
PostScript fonts with the ps output device, use:

=for example

   PDL::Graphics::PLplot->new(DEV => 'ps', OPTS => {drvopt => 'text=1'});

=head3 MEM

This option is used in conjunction with C<< DEV => 'mem' >>.  This option
takes as input a PDL image and allows one to 'decorate' it using PLplot.
The 'decorated' PDL image can then be written to an image file using,
for example, L<PDL::IO::Pic|PDL::IO::Pic>.  This option may not be available if
plplot does not include the 'mem' driver.

=for example

  # read in Earth image and draw an equator.
  my $pl = PDL::Graphics::PLplot->new (MEM => $earth, DEV => 'mem');
  my $x  = pdl(-180, 180);
  my $y  = zeroes(2);
  $pl->xyplot($x, $y,
              BOX => [-180,180,-90,90],
              VIEWPORT => [0.0, 1.0, 0.0, 1.0],
              XBOX => '', YBOX => '',
              PLOTTYPE => 'LINE');
  $pl->close;

=head3 FRAMECOLOR

Set color index 1, the frame color

=head3 JUST

A flag used to specify equal scale on the axes.  If this is
not specified, the default is to scale the axes to fit best on
the page.

=for example

  PDL::Graphics::PLplot->new(DEV => 'png',  FILE => 'test.png', JUST => 1);

=head3 ORIENTATION

The orientation of the plot:

  0 --   0 degrees (landscape mode)
  1 --  90 degrees (portrait mode)
  2 -- 180 degrees (seascape mode)
  3 -- 270 degrees (upside-down mode)

Intermediate values (0.2) are acceptable if you are feeling daring.

=for example

  # portrait orientation
  PDL::Graphics::PLplot->new(DEV => 'png',  FILE => 'test.png', ORIENTATION => 1);

=head3 PAGESIZE

Set the size in pixels of the output page.

=for example

  # PNG 500 by 600 pixels
  PDL::Graphics::PLplot->new(DEV => 'png',  FILE => 'test.png', PAGESIZE => [500,600]);

=head3 SUBPAGES

Set the number of sub pages in the plot, [$nx, $ny]

=for example

  # PNG 300 by 600 pixels
  # Two subpages stacked on top of one another.
  PDL::Graphics::PLplot->new(DEV => 'png',  FILE => 'test.png', PAGESIZE => [300,600],
                                              SUBPAGES => [1,2]);

=head2 Options used after initialization (after 'new')

=head3 BOX

Set the plotting box in world coordinates.  Used to explicitly
set the size of the plotting area.

=for example

 my $pl = PDL::Graphics::PLplot->new(DEV => 'png',  FILE => 'test.png');
 $pl->xyplot ($x, $y, BOX => [0,100,0,200]);

=head3 CHARSIZE

Set the size of text in multiples of the default size.
C<< CHARSIZE => 1.5 >> gives characters 1.5 times the normal size.

=head3 COLOR

Set the current color for plotting and character drawing.
Colors are specified not as color indices but as RGB triples.
Some pre-defined triples are included:

  BLACK        GREEN        WHEAT        BLUE
  RED          AQUAMARINE   GREY         BLUEVIOLET
  YELLOW       PINK         BROWN        CYAN
  TURQUOISE    MAGENTA      SALMON       WHITE
  ROYALBLUE    DEEPSKYBLUE  VIOLET       STEELBLUE1
  DEEPPINK     MAGENTA      DARKORCHID1  PALEVIOLETRED2
  TURQUOISE1   LIGHTSEAGREEN SKYBLUE     FORESTGREEN
  CHARTREUSE3  GOLD2        SIENNA1      CORAL
  HOTPINK      LIGHTCORAL   LIGHTPINK1   LIGHTGOLDENROD

=for example

 # These two are equivalent:
 $pl->xyplot ($x, $y, COLOR => 'YELLOW');
 $pl->xyplot ($x, $y, COLOR => [0,255,0]);

=head3 LINEWIDTH

Set the line width for plotting.  Values range from 1 to a device dependent maximum.

=head3 LINESTYLE

Set the line style for plotting.  Pre-defined line styles use values 1 to 8, one being
a solid line, 2-8 being various dashed patterns.

=head3 MAJTICKSIZE

Set the length of major ticks as a fraction of the default setting.
One (default) means leave these ticks the normal size.

=head3 MINTICKSIZE

Set the length of minor ticks (and error bar terminals) as a fraction of the default setting.
One (default) means leave these ticks the normal size.

=head3 NXSUB

The number of minor tick marks between each major tick mark on the X axis.
Specify zero (default) to let PLplot compute this automatically.

=head3 NYSUB

The number of minor tick marks between each major tick mark on the Y axis.
Specify zero (default) to let PLplot compute this automatically.

=head3 PALETTE

Load pre-defined color map 1 color ranges.  Currently, values include:

  RAINBOW   -- from Red to Violet through the spectrum
  REVERSERAINBOW   -- Violet through Red
  GREYSCALE -- from black to white via grey.
  REVERSEGREYSCALE -- from white to black via grey.
  GREENRED  -- from green to red
  REDGREEN  -- from red to green

=for example

 # Plot x/y points with the z axis in color
 $pl->xyplot ($x, $y, PALETTE => 'RAINBOW', PLOTTYPE => 'POINTS', COLORMAP => $z);

=head3 PLOTTYPE

Specify which type of XY plot is desired:

  LINE       -- A line
  POINTS     -- A bunch of symbols
  LINEPOINTS -- both

=head3 SUBPAGE

Set which subpage to plot on.  Subpages are numbered 1 to N.
A zero can be specified meaning 'advance to the next subpage' (just a call to
L<pladv()|/pladv>).

=for example

  my $pl = PDL::Graphics::PLplot->new(DEV      => 'png',
                                        FILE     => 'test.png',
                                        SUBPAGES => [1,2]);
  $pl->xyplot ($x, $y, SUBPAGE => 1);
  $pl->xyplot ($a, $b, SUBPAGE => 2);


=head3 SYMBOL

Specify which symbol to use when plotting C<< PLOTTYPE => 'POINTS' >>.
A large variety of symbols are available, see:
http://plplot.sourceforge.net/examples-data/demo07/x07.*.png, where * is 01 - 17.

=head3 SYMBOLSIZE

Specify the size of symbols plotted in multiples of the default size (1).
Value are real numbers from 0 to large.

=head3 TEXTPOSITION

Specify the placement of text.  Either relative to border, specified as:

 [$side, $disp, $pos, $just]

Where

  side = 't', 'b', 'l', or 'r' for top, bottom, left and right
  disp is the number of character heights out from the edge
  pos  is the position along the edge of the viewport, from 0 to 1.
  just tells where the reference point of the string is: 0 = left, 1 = right, 0.5 = center.

or inside the plot window, specified as:

 [$x, $y, $dx, $dy, $just]

Where

  x  = x coordinate of reference point of string.
  y  = y coordinate of reference point of string.
  dx   Together with dy, this specifies the inclination of the string.
       The baseline of the string is parallel to a line joining (x, y) to (x+dx, y+dy).
  dy   Together with dx, this specifies the inclination of the string.
  just Specifies the position of the string relative to its reference point.
       If just=0, the reference point is at the left and if just=1,
       it is at the right of the string. Other values of just give
       intermediate justifications.

=for example

 # Plot text on top of plot
 $pl->text ("Top label",  TEXTPOSITION => ['t', 4.0, 0.5, 0.5]);

 # Plot text in plotting area
 $pl->text ("Line label", TEXTPOSITION => [50, 60, 5, 5, 0.5]);

=head3 TITLE

Add a title on top of a plot.

=for example

 # Plot text on top of plot
 $pl->xyplot ($x, $y, TITLE => 'X vs. Y');

=head3 VIEWPORT

Set the location of the plotting window on the page.
Takes a four element array ref specifying:

 xmin -- The coordinate of the left-hand edge of the viewport. (0 to 1)
 xmax -- The coordinate of the right-hand edge of the viewport. (0 to 1)
 ymin -- The coordinate of the bottom edge of the viewport. (0 to 1)
 ymax -- The coordinate of the top edge of the viewport. (0 to 1)

=for example

 # Make a small plotting window in the lower left of the page
 $pl->xyplot ($x, $y, VIEWPORT => [0.1, 0.5, 0.1, 0.5]);

 # Also useful in creating color keys:
 $pl->xyplot   ($x, $y, PALETTE => 'RAINBOW', PLOTTYPE => 'POINTS', COLORMAP => $z);
 $pl->colorkey ($z, 'v', VIEWPORT => [0.93, 0.96, 0.15, 0.85]);

=head3 XBOX

Specify how to label the X axis of the plot as a string of option letters:

  a: Draws axis, X-axis is horizontal line (y=0), and Y-axis is vertical line (x=0).
  b: Draws bottom (X) or left (Y) edge of frame.
  c: Draws top (X) or right (Y) edge of frame.
  f: Always use fixed point numeric labels.
  g: Draws a grid at the major tick interval.
  h: Draws a grid at the minor tick interval.
  i: Inverts tick marks, so they are drawn outwards, rather than inwards.
  l: Labels axis logarithmically. This only affects the labels, not the data,
     and so it is necessary to compute the logarithms of data points before
     passing them to any of the drawing routines.
  m: Writes numeric labels at major tick intervals in the
     unconventional location (above box for X, right of box for Y).
  n: Writes numeric labels at major tick intervals in the conventional location
     (below box for X, left of box for Y).
  s: Enables subticks between major ticks, only valid if t is also specified.
  t: Draws major ticks.

The default is C<'BCNST'> which draws lines around the plot, draws major and minor
ticks and labels major ticks.

=for example

 # plot two lines in a box with independent X axes labeled
 # differently on top and bottom
 $pl->xyplot($x1, $y, XBOX  => 'bnst',  # bottom line, bottom numbers, ticks, subticks
	              YBOX  => 'bnst'); # left line, left numbers, ticks, subticks
 $pl->xyplot($x2, $y, XBOX => 'cmst', # top line, top numbers, ticks, subticks
	              YBOX => 'cst',  # right line, ticks, subticks
	              BOX => [$x2->minmax, $y->minmax]);

=head3 XERRORBAR

Used only with L</xyplot>.  Draws horizontal error bars at all points (C<$x>, C<$y>) in the plot.
Specify a PDL containing the same number of points as C<$x> and C<$y>
which specifies the width of the error bar, which will be centered at (C<$x>, C<$y>).

=head3 XLAB

Specify a label for the X axis.

=head3 XTICK

Interval (in graph units/world coordinates) between major x axis tick marks.
Specify zero (default) to allow PLplot to compute this automatically.

=head3 YBOX

Specify how to label the Y axis of the plot as a string of option letters.
See L</XBOX>.

=head3 YERRORBAR

Used only for xyplot.  Draws vertical error bars at all points (C<$x>, C<$y>) in the plot.
Specify a PDL containing the same number of points as C<$x> and C<$y>
which specifies the width of the error bar, which will be centered at (C<$x>, C<$y>).

=head3 YLAB

Specify a label for the Y axis.

=head3 YTICK

Interval (in graph units/world coordinates) between major y axis tick marks.
Specify zero (default) to allow PLplot to compute this automatically.

=head3 ZRANGE

For L</xyplot> (when C<COLORMAP> is specified), for
L</shadeplot> and for L</colorkey>.
Normally, the range of the Z variable (color) is taken as
C<< $z->minmax >>.  If a different range is desired,
specify it in C<ZRANGE>, like so:

  $pl->shadeplot ($z, $nlevels, PALETTE => 'GREENRED', ZRANGE => [0,100]);

or

  $pl->xyplot ($x, $y, PALETTE  => 'RAINBOW', PLOTTYPE => 'POINTS',
	               COLORMAP => $z,        ZRANGE => [-90,-20]);
  $pl->colorkey  ($z, 'v', VIEWPORT => [0.93, 0.96, 0.13, 0.85],
                       ZRANGE => [-90,-20]);

=head1 METHODS

These are the high-level, object oriented methods for PLplot.

=head2 new

=for ref

Create an object representing a plot.

=for usage

 Arguments:
 none.

 Supported options:
 BACKGROUND
 DEV
 FILE
 FRAMECOLOR
 JUST
 PAGESIZE
 SUBPAGES

=for example

  my $pl = PDL::Graphics::PLplot->new(DEV => 'png',  FILE => 'test.png');


=head2 setparm

=for ref

Set options for a plot object.

=for usage

 Arguments:
 none.

 Supported options:
 All options except:

 BACKGROUND
 DEV
 FILE
 FRAMECOLOR
 JUST
 PAGESIZE
 SUBPAGES

 (These must be set in call to 'new'.)

=for example

  $pl->setparm (TEXTSIZE => 2);

=head2 xyplot

=for ref

Plot XY lines and/or points.  Also supports color scales for points.
This function works with bad values.  If a bad value is specified for
a points plot, it is omitted.  If a bad value is specified for a line
plot, the bad value makes a gap in the line.  This is useful for
drawing maps; for example C<$x> and C<$y> can be the continent boundary
latitude and longitude.

=for usage

 Arguments:
 $x, $y

 Supported options:
 All options except:

 BACKGROUND
 DEV
 FILE
 FRAMECOLOR
 JUST
 PAGESIZE
 SUBPAGES

 (These must be set in call to 'new'.)

=for example

  $pl->xyplot($x, $y, PLOTTYPE => 'POINTS', COLOR => 'BLUEVIOLET', SYMBOL => 1, SYMBOLSIZE => 4);
  $pl->xyplot($x, $y, PLOTTYPE => 'LINEPOINTS', COLOR => [50,230,30]);
  $pl->xyplot($x, $y, PALETTE => 'RAINBOW', PLOTTYPE => 'POINTS', COLORMAP => $z);

=head2 stripplots

=for ref

Plot a set of strip plots with a common X axis, but with different Y axes.
Looks like a stack of long, thin XY plots, all line up on the same X axis.

=for usage

 Arguments:
 $x  -- 1D PDL with common X axis values, length = N
 $ys -- 2D PDL with M Y-axis values: N x M
 %opts -- Options hash

 Supported options:
 All options except:

 BACKGROUND
 DEV
 FILE
 FRAMECOLOR
 JUST
 PAGESIZE
 SUBPAGES

 (These must be set in call to 'new'.)

=for example

  my $x  = sequence(20);
  my $y1  = $x**2;
  my $y2  = sqrt($x);
  my $y3  = $x**3;
  my $y4  = sin(($x/20) * 2 * $pi);
  $ys  = cat($y1, $y2, $y3, $y4);
  $pl->stripplots($x, $ys, PLOTTYPE => 'LINE', TITLE => 'functions',
                           YLAB     => ['x**2', 'sqrt(x)', 'x**3', 'sin(x/20*2pi)'],
                           COLOR    => ['GREEN', 'DEEPSKYBLUE', 'DARKORCHID1', 'DEEPPINK'], XLAB => 'X label');

  In addition, COLOR may be specified as a reference to a list of colors.  If
  this is done, the colors are applied separately to each plot.

  Also, the options Y_BASE and Y_GUTTER can be specified.  Y_BASE gives the Y offset
  of the bottom of the lowest plot (0-1, specified like a VIEWPORT, defaults to 0.1) and Y_GUTTER
  gives the gap between the graphs (0-1, default = 0.02).

=head2 colorkey

=for ref

Plot a color key showing which color represents which value

=for usage

 Arguments:
 $range   : A PDL which tells the range of the color values
 $orientation : 'v' for vertical color key, 'h' for horizontal

 Supported options:
 All options except:

 BACKGROUND
 DEV
 FILE
 FRAMECOLOR
 JUST
 PAGESIZE
 SUBPAGES

 (These must be set in call to 'new'.)

=for example

  # Plot X vs. Y with Z shown by the color.  Then plot
  # vertical key to the right of the original plot.
  $pl->xyplot ($x, $y, PALETTE => 'RAINBOW', PLOTTYPE => 'POINTS', COLORMAP => $z);
  $pl->colorkey ($z, 'v', VIEWPORT => [0.93, 0.96, 0.15, 0.85]);


=head2 shadeplot

=for ref

Create a shaded contour plot of 2D PDL 'z' with 'nsteps' contour levels.
Linear scaling is used to map the coordinates of Z(X, Y) to world coordinates
via the L</BOX> option.

=for usage

 Arguments:
 $z : A 2D PDL which contains surface values at each XY coordinate.
 $nsteps : The number of contour levels requested for the plot.

 Supported options:
 All options except:

 BACKGROUND
 DEV
 FILE
 FRAMECOLOR
 JUST
 PAGESIZE
 SUBPAGES

 (These must be set in call to 'new'.)

=for example

  # vertical key to the right of the original plot.
  # The BOX must be specified to give real coordinate values to the $z array.
  $pl->shadeplot ($z, $nsteps, BOX => [-1, 1, -1, 1], PALETTE => 'RAINBOW', ZRANGE => [0,100]);
  $pl->colorkey  ($z, 'v', VIEWPORT => [0.93, 0.96, 0.15, 0.85], ZRANGE => [0,100]);

=head2 histogram

=for ref

Create a histogram of a 1-D variable.

=for usage

 Arguments:
 $x : A 1D PDL
 $nbins : The number of bins to use in the histogram.

 Supported options:
 All options except:

 BACKGROUND
 DEV
 FILE
 FRAMECOLOR
 JUST
 PAGESIZE
 SUBPAGES

 (These must be set in call to 'new'.)

=for example

  $pl->histogram ($x, $nbins, BOX => [$min, $max, 0, 100]);

=head2 bargraph

=for ref

Simple utility to plot a bar chart with labels on the X axis.
The usual options can be specified, plus one other:  MAXBARLABELS
specifies the maximum number of labels to allow on the X axis.
The default is 20.  If this value is exceeded, then every other
label is plotted.  If twice MAXBARLABELS is exceeded, then only
every third label is printed, and so on.

=for usage

 Arguments:
 $labels -- A reference to a perl list of strings.
 $values -- A PDL of values to be plotted.

 Supported options:
 All options except:

 BACKGROUND
 DEV
 FILE
 FRAMECOLOR
 JUST
 PAGESIZE
 SUBPAGES

 (These must be set in call to 'new'.)

=for example

  $labels = ['one', 'two', 'three'];
  $values = pdl(1, 2, 3);

  # Note if TEXTPOSITION is specified, it must be in 4 argument mode (border mode):
  # [$side, $disp, $pos, $just]
  #
  # Where side = 't', 'b', 'l', or 'r' for top, bottom, left and right
  #              'tv', 'bv', 'lv' or 'rv' for top, bottom, left or right perpendicular to the axis.
  #
  #     disp is the number of character heights out from the edge
  #     pos  is the position along the edge of the viewport, from 0 to 1.
  #     just tells where the reference point of the string is: 0 = left, 1 = right, 0.5 = center.
  #
  # The '$pos' entry will be ignored (computed by the bargraph routine)
  $pl->bargraph($labels, $values, MAXBARLABELS => 30, TEXTPOSITION => ['bv', 0.5, 1.0, 1.0]);

=head2 text

=for ref

Write text on a plot.  Text can either be written
with respect to the borders or at an arbitrary location and angle
(see the L</TEXTPOSITION> entry).

=for usage

 Arguments:
 $t : The text.

 Supported options:
 All options except:

 BACKGROUND
 DEV
 FILE
 FRAMECOLOR
 JUST
 PAGESIZE
 SUBPAGES

 (These must be set in call to 'new'.)

=for example

  $pl->text("Count", COLOR => 'PINK',
	    TEXTPOSITION => ['t', 3, 0.5, 0.5]); # top, 3 units out, string ref. pt in
                                                 # center of string, middle of axis

=head2 close

=for ref

Close a PLplot object, writing out the file and cleaning up.

=for usage

Arguments:
None

Returns:
Nothing

This closing of the PLplot object can be done explicitly though the
'close' method.  Alternatively, a DESTROY block does an automatic
close whenever the PLplot object passes out of scope.

=for example

  $pl->close;

=cut

# pull in low level interface
use vars qw(%_constants %_actions);

# Colors (from rgb.txt) are stored as RGB triples
# with each value from 0-255
sub cc2t { [map {hex} split ' ', shift] }
%_constants = (
	       BLACK          => [  0,  0,  0],
	       RED            => [240, 50, 50],
	       YELLOW         => [255,255,  0],
	       GREEN          => [  0,255,  0],
	       AQUAMARINE     => [127,255,212],
	       PINK           => [255,192,203],
	       WHEAT          => [245,222,179],
	       GREY           => [190,190,190],
	       BROWN          => [165, 42, 42],
	       BLUE           => [  0,  0,255],
	       BLUEVIOLET     => [138, 43,226],
	       CYAN           => [  0,255,255],
	       TURQUOISE      => [ 64,224,208],
	       MAGENTA        => [255,  0,255],
	       SALMON         => [250,128,114],
	       WHITE          => [255,255,255],
               ROYALBLUE      => cc2t('2B 60 DE'),
               DEEPSKYBLUE    => cc2t('3B B9 FF'),
               VIOLET         => cc2t('8D 38 C9'),
               STEELBLUE1     => cc2t('5C B3 FF'),
               DEEPPINK       => cc2t('F5 28 87'),
               MAGENTA        => cc2t('FF 00 FF'),
               DARKORCHID1    => cc2t('B0 41 FF'),
               PALEVIOLETRED2 => cc2t('E5 6E 94'),
               TURQUOISE1     => cc2t('52 F3 FF'),
               LIGHTSEAGREEN  => cc2t('3E A9 9F'),
               SKYBLUE        => cc2t('66 98 FF'),
               FORESTGREEN    => cc2t('4E 92 58'),
               CHARTREUSE3    => cc2t('6C C4 17'),
               GOLD2          => cc2t('EA C1 17'),
               SIENNA1        => cc2t('F8 74 31'),
               CORAL          => cc2t('F7 65 41'),
               HOTPINK        => cc2t('F6 60 AB'),
               LIGHTCORAL     => cc2t('E7 74 71'),
               LIGHTPINK1     => cc2t('F9 A7 B0'),
               LIGHTGOLDENROD => cc2t('EC D8 72'),
	      );

# a hash of subroutines to invoke when certain keywords are specified
# These are called with arg(0) = $self (the plot object)
#                   and arg(1) = value specified for keyword
%_actions =
  (


   # Set color for index 0, the plot background
   BACKGROUND => sub {
     my $self  = shift;
     my $color = _color(shift);
     $self->{COLORS}[0] = $color;
     plscolbg (@$color);
   },

   # set plotting box in world coordinates
   BOX        => sub {
     my $self  = shift;
     my $box   = shift;
     die "Box must be a ref to a four element array" unless (ref($box) =~ /ARRAY/ and @$box == 4);
     $self->{BOX} = $box;
   },

   CHARSIZE   => sub { my $self = shift;
                       $self->{CHARSIZE} = $_[0];
                       plschr   (0, $_[0]) },  # 0 - N

   COLOR =>
   # maintain color map, set to specified rgb triple
   sub {
     my $self  = shift;
     my $color = _color(shift);

     # init.
     $self->{COLORS} = [] unless exists($self->{COLORS});

     my @idx = @{$self->{COLORS}}; # map of color index (0-15) to RGB triples
     my $found = 0;
     for (my $i=2;$i<@idx;$i++) {  # map entries 0 and 1 are reserved for BACKGROUND and FRAMECOLOR
       if (_coloreq ($color, $idx[$i])) {
	 $self->{CURRENT_COLOR_IDX} = $i;
	 $found = 1;
	 plscol0 ($self->{CURRENT_COLOR_IDX}, @$color);
       }
     }
     return if ($found);

     die "Too many colors used! (max 15)" if (@{$self->{COLORS}} > 14);

     # add this color as index 2 or greater (entries 0 and 1 reserved)
     my $idx = (@{$self->{COLORS}} > 1) ? @{$self->{COLORS}} : 2;
     $self->{COLORS}[$idx]      = $color;
     $self->{CURRENT_COLOR_IDX} = $idx;
     plscol0 ($self->{CURRENT_COLOR_IDX}, @$color);
   },

   # set output device type
   DEV        => sub { my $self = shift;
                       my $dev  = shift;
                       $self->{DEV} = $dev;
                       plsdev   ($dev)
                     },   # this must be specified with call to new!

   # set PDL to plot into (alternative to specifying DEV)
   MEM        => sub { my $self = shift;
		       my $pdl  = shift;
		       my $x    = $pdl->getdim(1);
		       my $y    = $pdl->getdim(2);
		       plsmem   ($x, $y, $pdl);
		     },

   # set output file
   FILE       => sub { plsfnam  ($_[1]) },   # this must be specified with call to new!

   # set color for index 1, the plot frame and text
   FRAMECOLOR =>
   # set color index 1, the frame color
   sub {
     my $self  = shift;
     my $color = _color(shift);
     $self->{COLORS}[1] = $color;
     plscol0 (1, @$color);
   },

   # Set flag for equal scale axes
   JUST => sub {
     my $self  = shift;
     my $just  = shift;
     die "JUST must be 0 or 1 (defaults to 0)" unless ($just == 0 or $just == 1);
     $self->{JUST} = $just;
   },

    LINEWIDTH  => sub {
      my $self = shift;
      my $wid  = shift;
      die "LINEWIDTH must range from 0 to LARGE8" unless ($wid >= 0);
      $self->{LINEWIDTH} = $wid;
    },

   LINESTYLE  => sub {
     my $self = shift;
     my $sty  = shift;
     die "LINESTYLE must range from 1 to 8" unless ($sty >= 1 and $sty <= 8);
     $self->{LINESTYLE} = $sty;
   },

   MAJTICKSIZE  => sub {
     my $self = shift;
     my $val  = shift;
     die "MAJTICKSIZE must be greater than or equal to zero"
       unless ($val >= 0);
     plsmaj (0, $val);
   },

   MINTICKSIZE  => sub {
     my $self = shift;
     my $val  = shift;
     die "MINTICKSIZE must be greater than or equal to zero"
       unless ($val >= 0);
     plsmin (0, $val);
   },

   NXSUB  => sub {
     my $self = shift;
     my $val  = shift;
     die "NXSUB must be an integer greater than or equal to zero"
       unless ($val >= 0 and int($val) == $val);
     $self->{NXSUB} = $val;
   },

   NYSUB  => sub {
     my $self = shift;
     my $val  = shift;
     die "NYSUB must be an integer greater than or equal to zero"
       unless ($val >= 0 and int($val) == $val);
     $self->{NYSUB} = $val;
   },

   # set driver options, example for ps driver, {text => 1} is accepted
   OPTS => sub {
     my $self = shift;
     my $opts = shift;

     foreach my $opt (keys %$opts) {
       plsetopt ($opt, $$opts{$opt});
     }
   },

   # set driver options, example for ps driver, {text => 1} is accepted
   ORIENTATION => sub {
     my $self   = shift;
     my $orient = shift;

     die "Orientation must be between 0 and 4" unless ($orient >= 0 and $orient <= 4);
     $self->{ORIENTATION} = $orient;
   },

   PAGESIZE   =>
     # set plot size in mm.  Only useful in call to 'new'
     sub {
       my $self = shift;
       my $dims = shift;

       die "plot size must be a 2 element array ref:  X size in pixels, Y size in pixels"
	 if ((ref($dims) !~ /ARRAY/) || @$dims != 2);
       $self->{PAGESIZE} = $dims;
     },

   PALETTE =>

   # load some pre-done color map 1 setups
   sub {
     my $self = shift;
     my $pal  = shift;

     my %legal = (REVERSERAINBOW => 1, REVERSEGREYSCALE => 1, REDGREEN => 1, RAINBOW => 1, GREYSCALE => 1, GREENRED => 1);
     if ($legal{$pal}) {
       $self->{PALETTE} = $pal;
       if      ($pal eq 'RAINBOW') {
	 plscmap1l (0, PDL->new(0,1), PDL->new(0,300), PDL->new(0.5, 0.5), PDL->new(1,1), PDL->new(0,0));
       } elsif ($pal eq 'REVERSERAINBOW') {
	 plscmap1l (0, PDL->new(0,1), PDL->new(270,-30), PDL->new(0.5, 0.5), PDL->new(1,1), PDL->new(0,0));
       } elsif ($pal eq 'GREYSCALE') {
	 plscmap1l (0, PDL->new(0,1), PDL->new(0,0),   PDL->new(0,1), PDL->new(0,0), PDL->new(0,0));
       } elsif ($pal eq 'REVERSEGREYSCALE') {
	 plscmap1l (0, PDL->new(0,1), PDL->new(0,0),   PDL->new(1,0), PDL->new(0,0), PDL->new(0,0));
       } elsif ($pal eq 'GREENRED') {
	 plscmap1l (0, PDL->new(0,1), PDL->new(120,0), PDL->new(0.5, 0.5), PDL->new(1,1), PDL->new(1,1));
       } elsif ($pal eq 'REDGREEN') {
	 plscmap1l (0, PDL->new(0,1), PDL->new(0,120), PDL->new(0.5, 0.5), PDL->new(1,1), PDL->new(1,1));
       }
     } else {
       die "Illegal palette name.  Legal names are: " . join (" ", keys %legal);
     }
   },

   PLOTTYPE =>
   # specify plot type (LINE, POINTS, LINEPOINTS)
   sub {
     my $self = shift;
     my $val  = shift;

     my %legal = (LINE => 1, POINTS => 1, LINEPOINTS => 1);
     if ($legal{$val}) {
       $self->{PLOTTYPE} = $val;
     } else {
       die "Illegal plot type.  Legal options are: " . join (" ", keys %legal);
     }
   },

   SUBPAGE =>
   # specify which subpage to plot on 1-N or 0 (meaning 'next')
   sub {
     my $self = shift;
     my $val  = shift;
     my $err  = "SUBPAGE = \$npage where \$npage = 1-N or 0 (for 'next subpage')";
     if ($val >= 0) {
       $self->{SUBPAGE} = $val;
     } else {
       die $err;
     }
   },

   SUBPAGES =>
   # specify number of sub pages [nx, ny]
   sub {
     my $self = shift;
     my $val  = shift;
     my $err  = "SUBPAGES = [\$nx, \$ny] where \$nx and \$ny are between 1 and 127";
     if (ref($val) =~ /ARRAY/ and @$val == 2) {
       my ($nx, $ny) = @$val;
       if ($nx > 0 and $nx < 128 and $ny > 0 and $ny < 128) {
	 $self->{SUBPAGES} = [$nx, $ny];
       } else {
	 die $err;
       }
     } else {
       die $err;
     }
   },

   SYMBOL =>
   # specify type of symbol to plot
   sub {
     my $self = shift;
     my $val  = shift;

     if ($val >= 0 && $val < 3000) {
       $self->{SYMBOL} = $val;
     } else {
       die "Illegal symbol number.  Legal symbols are between 0 and 3000";
     }
   },

   SYMBOLSIZE => sub {
     my ($self, $size) = @_;
     die "symbol size must be a real number from 0 to (large)" unless ($size >= 0);
     $self->{SYMBOLSIZE} = $size;
   },

   TEXTPOSITION =>
   # specify placement of text.  Either relative to border, specified as:
   # [$side, $disp, $pos, $just]
   # or
   # inside plot window, specified as:
   # [$x, $y, $dx, $dy, $just] (see POD doc for details)
   sub {
     my $self = shift;
     my $val  = shift;

     die "TEXTPOSITION value must be an array ref with either:
          [$side, $disp, $pos, $just] or [$x, $y, $dx, $dy, $just]"
       unless ((ref($val) =~ /ARRAY/) and ((@$val == 4) || (@$val == 5)));

     if (@$val == 4) {
       $self->{TEXTMODE} = 'border';
     } else {
       $self->{TEXTMODE} = 'plot';
     }
     $self->{TEXTPOSITION} = $val;
   },

   # draw a title for the graph
   TITLE      => sub {
     my $self = shift;
     my $text = shift;
     $self->{TITLE} = $text;
   },

   # set the location of the plotting window on the page
   VIEWPORT => sub {
     my $self  = shift;
     my $vp    = shift;
     die "Viewport must be a ref to a four element array"
       unless (ref($vp) =~ /ARRAY/ and @$vp == 4);
     $self->{VIEWPORT} = $vp;
   },

   XBOX       =>
     # set X axis label options.  See pod for definitions.
     sub {
       my $self = shift;
       my $opts = lc shift;

       my @opts = split '', $opts;
       map { 'abcdfghilmnst' =~ /$_/i || die "Illegal option $_.  Only abcdfghilmnst permitted" } @opts;

       $self->{XBOX} = $opts;
     },

   # draw an X axis label for the graph
   XLAB       => sub {
     my $self = shift;
     my $text = shift;
     $self->{XLAB} = $text;
   },

   XTICK  => sub {
     my $self = shift;
     my $val  = shift;
     die "XTICK must be greater than or equal to zero"
       unless ($val >= 0);
     $self->{XTICK} = $val;
   },

   YBOX       =>
     # set Y axis label options.  See pod for definitions.
     sub {
       my $self = shift;
       my $opts = shift;

       my @opts = split '', $opts;
       map { 'abcfghilmnstv' =~ /$_/i || die "Illegal option $_.  Only abcfghilmnstv permitted" } @opts;

       $self->{YBOX} = $opts;
     },

   # draw an Y axis label for the graph
   YLAB       => sub {
     my $self = shift;
     my $text = shift;
     $self->{YLAB} = $text;
   },

   YTICK  => sub {
     my $self = shift;
     my $val  = shift;
     die "YTICK must be greater than or equal to zero"
       unless ($val >= 0);
     $self->{YTICK} = $val;
   },

   ZRANGE  => sub {
     my $self = shift;
     my $val  = shift;
     die "ZRANGE must be a perl array ref with min and max Z values"
       unless (ref($val) =~ /ARRAY/ && @$val == 2);
     $self->{ZRANGE} = $val;
   },

);


#
## Internal utility routines
#

# handle color as string in _constants hash or [r,g,b] triple
# Input:  either color name or [r,g,b] array ref
# Output: [r,g,b] array ref or exception
sub _color {
  my $c = shift;
  if      (ref($c) =~ /ARRAY/) {
    return $c;
  } elsif ($c = $_constants{$c}) {
    return $c;
  } else {
    die "Color $c not defined";
  }
}

# return 1 if input [r,g,b] triples are equal.
sub _coloreq {
  my ($a, $b) = @_;
  for (my $i=0;$i<3;$i++) { return 0 if ($$a[$i] != $$b[$i]); }
  return 1;
}

# Initialize plotting window given the world coordinate box and
# a 'justify' flag (for equal axis scales).
sub _setwindow {

  my $self = shift;

  # choose correct subwindow
  pladv ($self->{SUBPAGE}) if (exists ($self->{SUBPAGE}));
  delete ($self->{SUBPAGE});  # get rid of SUBPAGE so future plots will stay on same
                              # page unless user asks for specific page

  my $box  = $self->{BOX} || [0,1,0,1]; # default window

  sub MAX { ($_[0] > $_[1]) ? $_[0] : $_[1]; }

  # get subpage offsets from page left/bottom of image
  my ($spxmin, $spxmax, $spymin, $spymax) = (PDL->new(0),PDL->new(0),PDL->new(0),PDL->new(0));
  plgspa($spxmin, $spxmax, $spymin, $spymax);
  $spxmin = $spxmin->at(0);
  $spxmax = $spxmax->at(0);
  $spymin = $spymin->at(0);
  $spymax = $spymax->at(0);
  my $xsize = $spxmax - $spxmin;
  my $ysize = $spymax - $spymin;

  my @vp = @{$self->{VIEWPORT}};  # view port xmin, xmax, ymin, ymax in fraction of image size

  # if JUSTify is zero, set to the user specified (or default) VIEWPORT
  if ($self->{JUST} == 0) {
    plvpor(@vp);

  # compute viewport to allow the same scales for both axes
  } else {
    my $p_def = PDL->new(0);
    my $p_ht  = PDL->new(0);
    plgchr ($p_def, $p_ht);
    $p_def = $p_def->at(0);
    my $lb = 8.0 * $p_def;
    my $rb = 5.0 * $p_def;
    my $tb = 5.0 * $p_def;
    my $bb = 5.0 * $p_def;
    my $dx = $$box[1] - $$box[0];
    my $dy = $$box[3] - $$box[2];
    my $xscale = $dx / ($xsize - $lb - $rb);
    my $yscale = $dy / ($ysize - $tb - $bb);
    my $scale  = MAX($xscale, $yscale);
    my $vpxmin = MAX($lb, 0.5 * ($xsize - $dx / $scale));
    my $vpxmax = $vpxmin + ($dx / $scale);
    my $vpymin = MAX($bb, 0.5 * ($ysize - $dy / $scale));
    my $vpymax = $vpymin + ($dy / $scale);
    plsvpa($vpxmin, $vpxmax, $vpymin, $vpymax);
    $self->{VIEWPORT} = [$vpxmin/$xsize, $vpxmax/$xsize, $vpymin/$ysize, $vpymax/$ysize];
  }

  # set up world coords in window
  plwind (@$box);

}

# Add title and axis labels.
sub _drawlabels {

  my $self = shift;

  plcol0  (1); # set to frame color
  plmtex   (2.5, 0.5, 0.5, 't', $self->{TITLE}) if ($self->{TITLE});
  plmtex   (3.0, 0.5, 0.5, 'b', $self->{XLAB})  if ($self->{XLAB});
  plmtex   (3.5, 0.5, 0.5, 'l', $self->{YLAB})  if ($self->{YLAB});
  plcol0  ($self->{CURRENT_COLOR_IDX}); # set back

}


#
## user-visible routines
#

# Pool of PLplot stream numbers.  One of these stream numbers is taken when 'new' is called
# and when the corresponding 'close' is called, it is returned to the pool.  The pool is
# just a queue:  'new' shifts stream numbers from the top of the queue, 'close' pushes them
# back on the bottom of the queue.
my @plplot_stream_pool = (0..99);

# This routine starts out a plot.  Generally one specifies
# DEV and FILE (device and output file name) as options.
sub new {
  my $type = shift;
  my $self = {};

  # set up object
  $self->{PLOTTYPE} = 'LINE';
  # $self->{CURRENT_COLOR_IDX} = 1;
  $self->{COLORS} = [];

  bless $self, $type;

  # set stream number first
  $self->{STREAMNUMBER} = shift @plplot_stream_pool;
  die "No more PLplot streams left, too many open PLplot objects!" if (!defined($self->{STREAMNUMBER}));
  plsstrm($self->{STREAMNUMBER});

  # set background and frame color first
  $self->setparm(BACKGROUND => 'WHITE',
		 FRAMECOLOR => 'BLACK');

  # set defaults, allow input options to override
  my %opts = (
	      COLOR      => 'BLACK',
	      XBOX       => 'BCNST',
	      YBOX       => 'BCNST',
	      JUST       => 0,
	      SUBPAGES   => [1,1],
	      VIEWPORT   => [0.1, 0.87, 0.13, 0.82],
	      SUBPAGE    => 0,
	      PAGESIZE   => [600, 500],
	      LINESTYLE  => 1,
              LINEWIDTH  => 0,
              SYMBOL     => 751, # a small square
	      NXSUB      => 0,
	      NYSUB      => 0,
	      ORIENTATION=> 0,
	      XTICK      => 0,
	      YTICK      => 0,
	      CHARSIZE   => 1,
	      @_);


  # apply options
  $self->setparm(%opts);

  # Do initial setup
  plspage (0, 0, @{$self->{PAGESIZE}}, 0, 0) if (defined($self->{PAGESIZE}));
  plssub (@{$self->{SUBPAGES}});
  plfontld (1); # extented symbol pages
  plscmap0n (16);   # set up color map 0 to 16 colors.  Is this needed?
  plscmap1n (128);  # set map 1 to 128 colors (should work for devices with 256 colors)
  plinit ();

  # set page orientation
  plsdiori ($self->{ORIENTATION});

  # set up plotting box
  $self->_setwindow;

  return $self;
}

# set parameters.  Called from user directly or from other routines.
sub setparm {
  my $self = shift;

  my %opts = @_;

  # Set PLplot to right output stream
  plsstrm($self->{STREAMNUMBER});

  # apply all options
 OPTION:
  foreach my $o (keys %opts) {
    unless (exists($_actions{$o})) {
      warn "Illegal option $o, ignoring";
      next OPTION;
    }
    &{$_actions{$o}}($self, $opts{$o});
  }
}

# handle 2D plots
sub xyplot {
  my $self = shift;
  my $x    = shift;
  my $y    = shift;

  my %opts = @_;

  # Set PLplot to right output stream
  plsstrm($self->{STREAMNUMBER});

  # only process COLORMAP entries once
  my $z = $opts{COLORMAP};
  delete ($opts{COLORMAP});

  # handle ERRORBAR options
  my $xeb = $opts{XERRORBAR};
  my $yeb = $opts{YERRORBAR};
  delete ($opts{XERRORBAR});
  delete ($opts{YERRORBAR});

  # apply options
  $self->setparm(%opts);

  unless (exists($self->{BOX})) {
    $self->{BOX} = [$x->minmax, $y->minmax];
  }

  # set up viewport, subpage, world coordinates
  $self->_setwindow;

  # draw labels
  $self->_drawlabels;

  # plot box
  plcol0  (1); # set to frame color
  plbox ($self->{XTICK}, $self->{NXSUB}, $self->{YTICK}, $self->{NYSUB},
	 $self->{XBOX}, $self->{YBOX}); # !!! note out of order call

  # set the color according to the color specified in the object
  # (we don't do this as an option, because then the frame might
  # get the color requested for the line/points
  plcol0  ($self->{CURRENT_COLOR_IDX});

  # set line style for plot only (not box)
  pllsty ($self->{LINESTYLE});

  # set line width for plot only (not box)
  plwid  ($self->{LINEWIDTH});

  # Plot lines if requested
  if  ($self->{PLOTTYPE} =~ /LINE/) {
    plline ($x, $y);
  }

  # set line width back
  plwid  (0);

  # plot points if requested
  if ($self->{PLOTTYPE} =~ /POINTS/) {
    my $c = $self->{SYMBOL};
    unless (defined($c)) {

      # the default for $c is a PDL of ones with shape
      # equal to $x with the first dimension removed
      my $z = PDL->zeroes($x->nelem);
      $c = PDL->ones($z->zcover) unless defined($c);
    }
    plssym   (0, $self->{SYMBOLSIZE}) if (defined($self->{SYMBOLSIZE}));

    if (defined($z)) {  # if a color range plot requested
      my ($min, $max) = exists ($self->{ZRANGE}) ? @{$self->{ZRANGE}} : $z->minmax;
      plcolorpoints ($x, $y, $z, $c, $min, $max);
    } else {
      plsym ($x, $y, $c);
    }
  }

  # Plot error bars, if requested
  if (defined($xeb)) {
    # horizontal (X) error bars
    plerrx ($x->nelem, $x - $xeb/2, $x + $xeb/2, $y);
  }

  if (defined($yeb)) {
    # vertical (Y) error bars
    plerry ($y->nelem, $x, $y - $yeb/2, $y + $yeb/2);
  }

  # Flush the PLplot stream.
  plflush();
}


# Handle sets of 2D strip plots sharing one X axis.  Input is
# $self -- PLplot object with existing options
# $xs   -- Ref to list of 1D PDLs with X values
# $ys   -- Ref to list of 1D PDLs with Y values
# %opts -- Options values
sub stripplots {

  my $self = shift;
  my $xs   = shift;
  my $ys   = shift;

  my %opts = @_;

  # NYTICK => number of y axis ticks
  my $nytick = $opts{NYTICK} || 2;
  delete ($opts{NYTICK});

  # only process COLORMAP entries once
  my $zs = $opts{COLORMAP};
  delete ($opts{COLORMAP});

  # handle XLAB, YLAB and TITLE options
  my $title = $opts{TITLE} || '';
  my $xlab  = $opts{XLAB}  || '';
  my @ylabs = defined($opts{YLAB}) && (ref($opts{YLAB}) =~ /ARRAY/) ? @{$opts{YLAB}} : ();
  delete @opts{qw(TITLE XLAB YLAB)};

  my $nplots = @$ys;

  # Use list of colors, or single color.  If COLOR not specified, default to BLACK for each graph
  my @colors = (defined ($opts{COLOR}) && ref($opts{COLOR}) =~ /ARRAY/) ? @{$opts{COLOR}}
             :  defined ($opts{COLOR})                                  ? ($opts{COLOR}) x $nplots
             : ('BLACK') x $nplots;
  delete @opts{qw(COLOR)};

  my $y_base   = defined($opts{Y_BASE})   ? $opts{Y_BASE}   : 0.1;  # Y offset to start bottom plot
  my $y_gutter = defined($opts{Y_GUTTER}) ? $opts{Y_GUTTER} : 0.02; # Y gap between plots
  delete @opts{qw(Y_BASE Y_GUTTER)};

  # apply options
  $self->setparm(%opts);

  my $xmin = pdl(map { $_->min } @$xs)->min;
  my $xmax = pdl(map { $_->max } @$xs)->max;

  SUBPAGE:
    for (my $subpage=0;$subpage<$nplots;$subpage++) {

      my $y = $ys->[$subpage];
      my $x = $xs->[$subpage];
      my $mask = $y->isgood;
      $y = $y->where($mask);
      $x = $x->where($mask);
      my $z = $zs->slice(":,($subpage)")->where($mask)      if (defined($zs));
      my $yeb  = $yebs->slice(":,($subpage)")->where($mask) if (defined($yebs));
      my $ylab = $ylabs[$subpage];

      my $bottomplot = ($subpage == 0);
      my $topplot    = ($subpage == $nplots-1);

      my $xbox = 'bc';
      $xbox = 'cstnb' if ($bottomplot);

      my $box = $opts{BOX};
      my $yrange = defined($box) ? $$box[3] - $$box[2] : $y->max - $y->min;
      my $del = $yrange ? $yrange * 0.05 : 1;
      my @ybounds = ($y->min - $del, $y->max + $del);
      my $ytick = ($yrange/$nytick);
      my @COLORMAP  = (COLORMAP => $z)    if defined($z);
      $self->xyplot($x, $y,
		  COLOR     => $colors[$subpage],
		  BOX       => defined($box) ? $box : [$xmin, $xmax, @ybounds],
		  XBOX      => $xbox,
		  YBOX      => 'BCNT',
                  YTICK     => $ytick,
                  MAJTICKSIZE => 0.6,
		  CHARSIZE  => 0.4,
                  @COLORMAP,
		  VIEWPORT  => [
				0.15,
				0.9,
                                $y_base             + ($subpage     * (0.8/$nplots)),
                                $y_base - $y_gutter + (($subpage+1) * (0.8/$nplots)),
				],
		  );

      $self->text($ylab,  TEXTPOSITION => ['L', 4, 0.5, 0.5], COLOR => 'BLACK', CHARSIZE => 0.6) if (defined($ylab));
      $self->text($xlab,  TEXTPOSITION => ['B', 3, 0.5, 0.5], COLOR => 'BLACK', CHARSIZE => 0.6) if ($xlab && $bottomplot);
      $self->text($title, TEXTPOSITION => ['T', 2, 0.5, 0.5], COLOR => 'BLACK', CHARSIZE => 1.3) if ($title && $topplot);

    }

}


# Draw a color key or wedge showing the scale of map1 colors
sub colorkey {
  my $self = shift;
  my $var  = shift;
  my $orientation = shift; # 'v' (for vertical) or 'h' (for horizontal)

  my %opts = @_;

  # Set PLplot to right output stream
  plsstrm($self->{STREAMNUMBER});

  # apply options
  $self->setparm(%opts);

  # set up viewport, subpage, world coordinates
  $self->_setwindow;

  # draw labels
  $self->_drawlabels;

  # Allow user to set X, Y box type for color key scale.  D. Hunt 1/7/2009
  my $xbox = exists($self->{XBOX}) ? $self->{XBOX} : 'TM';
  my $ybox = exists($self->{YBOX}) ? $self->{YBOX} : 'TM';

  my @box;

  plcol0  (1); # set to frame color

  my ($min, $max) = exists ($self->{ZRANGE}) ? @{$self->{ZRANGE}} : $var->minmax;

  # plot box
  if      ($orientation eq 'v') {
    # set world coordinates based on input variable
    @box = (0, 1, $min, $max);
    plwind (@box);
    plbox (0, 0, 0, 0, '', $ybox);  # !!! note out of order call
  } elsif ($orientation eq 'h') {
    @box = ($min, $max, 0, 1);
    plwind (@box);
    plbox (0, 0, 0, 0, $xbox, '');  # !!! note out of order call
  } else {
    die "Illegal orientation value: $orientation.  Should be 'v' (vertical) or 'h' (horizontal)";
  }

  # restore color setting
  plcol0  ($self->{CURRENT_COLOR_IDX});

  # This is the number of colors shown in the color wedge.  Make
  # this smaller for gif images as these are limited to 256 colors total.
  # D. Hunt 8/9/2006
  my $ncols = ($self->{DEV} =~ /gif/) ? 32 : 128;

  if ($orientation eq 'v') {
    my $yinc = ($box[3] - $box[2])/$ncols;
    my $y0 = $box[2];
    for (my $i=0;$i<$ncols;$i++) {
      $y0 = $box[2] + ($i * $yinc);
      my $y1 = $y0 + $yinc;
      PDL::Graphics::PLplot::plcol1($i/$ncols);

      # Instead of using plfill (which is not supported on some devices)
      # use multiple calls to plline to color in the space. D. Hunt 8/9/2006
      foreach my $inc (0..9) {
        my $frac = $inc * 0.1;
        my $y = $y0 + (($y1 - $y0) * $frac);
        PDL::Graphics::PLplot::plline (PDL->new(0,1), PDL->new($y,$y));
      }

    }
  } else {
    my $xinc = ($box[1] - $box[0])/$ncols;
    my $x0 = $box[0];
    for (my $i=0;$i<$ncols;$i++) {
      $x0 = $box[0] + ($i * $xinc);
      my $x1 = $x0 + $xinc;
      PDL::Graphics::PLplot::plcol1($i/$ncols);

      # Instead of using plfill (which is not supported on some devices)
      # use multiple calls to plline to color in the space. D. Hunt 8/9/2006
      foreach my $inc (0..9) {
        my $frac = $inc * 0.1;
        my $x = $x0 + (($x1 - $x0) * $frac);
        PDL::Graphics::PLplot::plline (PDL->new($x,$x), PDL->new(0,1));
      }

    }
  }

  # Flush the PLplot stream.
  plflush();
}

# handle shade plots of gridded (2D) data
sub shadeplot {
  my $self   = shift;
  my $z      = shift;
  my $nsteps = shift;

  my %opts = @_;

  # Set PLplot to right output stream
  plsstrm($self->{STREAMNUMBER});

  # apply options
  $self->setparm(%opts);

  my ($nx, $ny) = $z->dims;

  unless (exists($self->{BOX})) {
    $self->{BOX} = [0, $nx, 0, $ny];
  }

  # set up plotting box
  $self->_setwindow;

  # draw labels
  $self->_drawlabels;

  # plot box
  plcol0  (1); # set to frame color
  plbox ($self->{XTICK}, $self->{NXSUB}, $self->{YTICK}, $self->{NYSUB},
	 $self->{XBOX}, $self->{YBOX}); # !!! note out of order call

  my ($min, $max) = exists ($self->{ZRANGE}) ? @{$self->{ZRANGE}} : $z->minmax;
  my $clevel = ((PDL->sequence($nsteps)*(($max - $min)/($nsteps-1))) + $min);

  # may add as options later.  Now use constants
  my $fill_width = 2;
  my $cont_color = 0;
  my $cont_width = 0;

  my $rectangular = 1; # only false for non-linear coord mapping (not done yet in perl)

  # map X coords linearly to X range, Y coords linearly to Y range
  my $xmap = ((PDL->sequence($nx)*(($self->{BOX}[1] - $self->{BOX}[0])/($nx - 1))) + $self->{BOX}[0]);
  my $ymap = ((PDL->sequence($ny)*(($self->{BOX}[3] - $self->{BOX}[2])/($ny - 1))) + $self->{BOX}[2]);

  my $grid = plAllocGrid ($xmap, $ymap);

  plshades($z, @{$self->{BOX}}, $clevel, $fill_width,
           $cont_color, $cont_width, $rectangular,
	   0, \&pltr1, $grid);

  plFreeGrid ($grid);

  # Flush the PLplot stream.
  plflush();
}

# handle histograms
sub histogram {
  my $self   = shift;
  my $x      = shift;
  my $nbins  = shift;

  my %opts = @_;

  # Set PLplot to right output stream
  plsstrm($self->{STREAMNUMBER});

  # apply options
  $self->setparm(%opts);

  my ($min, $max) = $x->minmax;

  unless (exists($self->{BOX})) {
    $self->{BOX} = [$min, $max, 0, $x->nelem]; # box probably too tall!
  }

  # set up plotting box
  $self->_setwindow;

  # draw labels
  $self->_drawlabels;

  # plot box
  plcol0  (1); # set to frame color
  plbox ($self->{XTICK}, $self->{NXSUB}, $self->{YTICK}, $self->{NYSUB},
	 $self->{XBOX}, $self->{YBOX}); # !!! note out of order call

  # set line style for plot only (not box)
  pllsty ($self->{LINESTYLE});

  # set line width for plot only (not box)
  plwid  ($self->{LINEWIDTH});

  # set color for histograms
  plcol0  ($self->{CURRENT_COLOR_IDX});

  plhist ($x, $min, $max, $nbins, 1);  # '1' is oldbins parm:  dont call plenv!

  # set line width back
  plwid  (0);

  # Flush the PLplot stream.
  plflush();
}

# Draw bar graphs
sub bargraph {
  my $self   = shift;
  my $labels = shift; # ref to perl list of labels for bars
  my $values = shift; # pdl of values for bars

  my %opts = @_;

  # max number of readable labels on x axis
  my $maxlab = defined($opts{MAXBARLABELS}) ? $opts{MAXBARLABELS} : 20;
  delete ($opts{MAXBARLABELS});

  # Set PLplot to right output stream
  plsstrm($self->{STREAMNUMBER});
  my $xmax = scalar(@$labels);

  # apply options
  $self->setparm(%opts);

  my ($ymin, $ymax) = $values->minmax;

  unless (exists($self->{BOX})) {
    $self->{BOX} = [0, $xmax, $ymin, $ymax]; # box probably too tall!
  }

  # set up plotting box
  $self->_setwindow;

  # draw labels
  $self->_drawlabels;

  # plot box
  plcol0  (1); # set to frame color
  plbox ($self->{XTICK}, $self->{NXSUB}, $self->{YTICK}, $self->{NYSUB},
	 'bc', $self->{YBOX}); # !!! note out of order call

  # Now respect TEXTPOSITION setting if TEXTMODE eq 'border'
  # This allows the user to tweak the label placement.  D. Hunt 9/4/2007
  my ($side, $disp, $foo, $just) = ('BV', 0.2, 0, 1.0);
  if (defined($self->{TEXTMODE}) && $self->{TEXTMODE} eq 'border') {
    ($side, $disp, $foo, $just) = @{$self->{TEXTPOSITION}};
  }

  # plot labels
  plschr   (0, $self->{CHARSIZE} * 0.7); # use smaller characters
  my $pos = 0;
  my $skip   = int($xmax/$maxlab) + 1;
  for (my $i=0;$i<$xmax;$i+=$skip) {
    $pos = ((0.5+$i)/$xmax);
    my $lab = $$labels[$i];
    plmtex ($disp, $pos, $just, $side, $lab); # !!! out of order parms
  }

  plcol0  ($self->{CURRENT_COLOR_IDX}); # set back to line color

  # set line style for plot only (not box)
  pllsty ($self->{LINESTYLE});

  # set line width for plot only (not box)
  plwid  ($self->{LINEWIDTH});

  # draw bars
  plfbox (PDL->sequence($xmax)+0.5, $values);

  # set line width back
  plwid  (0);

  # set char size back
  plschr (0, $self->{CHARSIZE});

  # Flush the PLplot stream.
  plflush();
}

# Add text to a plot
sub text {
  my $self = shift;
  my $text = shift;

  # Set PLplot to right output stream
  plsstrm($self->{STREAMNUMBER});

  # apply options
  $self->setparm(@_);

  # set the color according to the color specified in the object
  plcol0  ($self->{CURRENT_COLOR_IDX});

  # plot either relative to border, or inside view port
  if      ($self->{TEXTMODE} eq 'border') {
    my ($side, $disp, $pos, $just) = @{$self->{TEXTPOSITION}};
    plmtex ($disp, $pos, $just, $side, $text); # !!! out of order parms
  } elsif ($self->{TEXTMODE} eq 'plot') {
    my ($x, $y, $dx, $dy, $just) = @{$self->{TEXTPOSITION}};
    plptex ($x, $y, $dx, $dy, $just, $text);
  }

  # Flush the PLplot stream.
  plflush();
}

# Clear the current page. This should only be used with interactive devices!
sub clear {
  my $self = shift;

  # Set PLplot to right output stream
  plsstrm($self->{STREAMNUMBER});

  plclear();
  return;
}

# Get mouse click coordinates (OO version). This should only be used with interactive devices!
sub cursor {
  my $self = shift;

  # Set PLplot to right output stream
  plsstrm($self->{STREAMNUMBER});

  # Flush the stream, to make sure the plot is visible & current
  plflush();

  # Get the cursor position
  my %gin = plGetCursor();

  # Return an array with the coordinates of the mouse click
  return ($gin{"wX"}, $gin{"wY"}, $gin{"pX"}, $gin{"pY"}, $gin{"dX"}, $gin{"dY"});
}

# Explicitly close a plot and free the object
sub close {
  my $self = shift;

  # Set PLplot to right output stream
  plsstrm($self->{STREAMNUMBER});

  plend1 ();

  # Return this stream number to the pool.
  push (@plplot_stream_pool, $self->{STREAMNUMBER});
  delete $self->{STREAMNUMBER};

  return;
}
EOD

# Used throughout when generating documentation.
my $doc;

# Necessary includes for .xs file
pp_addhdr(<<'EOH');
#include <plplot.h>
#include <plplotP.h>
#include <plevent.h>
#include <stdio.h>
EOH

# The create_low_level_constants function is used to make the #define'd
# constants in plplot.h available in Perl in the form of functions.  It
# should be then possible to write code like this:
#
#    plParseOpts (\@ARGV, PL_PARSE_SKIP | PL_PARSE_NOPROGRAM);

sub create_low_level_constants {
  my $defn = shift;
  my @lines = split (/\n/, $defn);

  foreach my $line (@lines) {
    next if (($line =~ /^\#/) or ($line =~ /^\s*$/));
    foreach my $const ($line =~ /([^\s]+)/g) {
      my $func = <<"EOC";
int
$const()
PROTOTYPE:
CODE:
  RETVAL = $const;
OUTPUT:
  RETVAL
EOC
    pp_addxs ($func);
    pp_add_exported ($const);
    }
  }
}

create_low_level_constants (<<'EODEF');

# Definitions used in plParseOpts

PL_PARSE_PARTIAL
PL_PARSE_FULL
PL_PARSE_QUIET
PL_PARSE_NODELETE
PL_PARSE_SHOWALL
PL_PARSE_OVERRIDE
PL_PARSE_NOPROGRAM
PL_PARSE_NODASH
PL_PARSE_SKIP

# Definitions for plmesh and plsurf3d

DRAW_LINEX
DRAW_LINEY
DRAW_LINEXY
MAG_COLOR
BASE_CONT
TOP_CONT
SURF_CONT
DRAW_SIDES
FACETED
MESH

# Input event (especially keyboard) definitions for use from plplot
# event handlers.

PLK_BackSpace PLK_Tab PLK_Linefeed PLK_Return PLK_Escape PLK_Delete
PLK_Clear PLK_Pause PLK_Scroll_Lock PLK_Home PLK_Left PLK_Up PLK_Right
PLK_Down PLK_Prior PLK_Next PLK_End PLK_Begin PLK_Select PLK_Print
PLK_Execute PLK_Insert PLK_Undo PLK_Redo PLK_Menu PLK_Find PLK_Cancel
PLK_Help PLK_Break PLK_Mode_switch PLK_script_switch PLK_Num_Lock
PLK_KP_Space PLK_KP_Tab PLK_KP_Enter PLK_KP_F1 PLK_KP_F2 PLK_KP_F3
PLK_KP_F4 PLK_KP_Equal PLK_KP_Multiply PLK_KP_Add PLK_KP_Separator
PLK_KP_Subtract PLK_KP_Decimal PLK_KP_Divide PLK_KP_0 PLK_KP_1
PLK_KP_2 PLK_KP_3 PLK_KP_4 PLK_KP_5 PLK_KP_6 PLK_KP_7 PLK_KP_8
PLK_KP_9 PLK_F1 PLK_F2 PLK_F3 PLK_F4 PLK_F5 PLK_F6 PLK_F7 PLK_F8
PLK_F9 PLK_F10 PLK_F11 PLK_L1 PLK_F12 PLK_L2 PLK_F13 PLK_L3 PLK_F14
PLK_L4 PLK_F15 PLK_L5 PLK_F16 PLK_L6 PLK_F17 PLK_L7 PLK_F18 PLK_L8
PLK_F19 PLK_L9 PLK_F20 PLK_L10 PLK_F21 PLK_R1 PLK_F22 PLK_R2 PLK_F23
PLK_R3 PLK_F24 PLK_R4 PLK_F25 PLK_R5 PLK_F26 PLK_R6 PLK_F27 PLK_R7
PLK_F28 PLK_R8 PLK_F29 PLK_R9 PLK_F30 PLK_R10 PLK_F31 PLK_R11 PLK_F32
PLK_R12 PLK_R13 PLK_F33 PLK_F34 PLK_R14 PLK_F35 PLK_R15 PLK_Shift_L
PLK_Shift_R PLK_Control_L PLK_Control_R PLK_Caps_Lock PLK_Shift_Lock
PLK_Meta_L PLK_Meta_R PLK_Alt_L PLK_Alt_R PLK_Super_L PLK_Super_R
PLK_Hyper_L PLK_Hyper_R

# Type of gridding algorithm for plgriddata ()

GRID_CSA
GRID_DTLI
GRID_NNI
GRID_NNIDW
GRID_NNLI
GRID_NNAIDW

EODEF


# Read in a modified plplot.h file.  Define
# a low-level perl interface to PLplot from these definitions.
# This could be cleaner!!
#
# A sample output for one PLplot function:
#
# Input:
#
# void c_plenv(PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax, PLINT just, PLINT axis);
#
# Output:
#
# pp_def ('plenv',
#	  Pars => 'xmin(); xmax(); ymin(); ymax(); int just(); int axis();',
#	  GenericTypes => [D],
#	  Code => 'c_plenv($xmin(),$xmax(),$ymin(),$ymax(),$just(),$axis());',
#
# In 'get' routines, (prefix = plg) all parameters have [o] added before, ie:
#
# void c_plglevel(PLINT *p_level);
#
# leads to:
# pp_def ('plglevel',
#	  Pars => 'int [o]p_level;',
#	  GenericTypes => [D],
#	  Code => 'c_plglevel($P(p_level));',
#
sub create_low_level {

# return; # use to short circuit creation of rest of interface for testing new additions.

# The input lines below are cut from plplot.h, but:
# -- Only needed functions are included (none of the C/C++ only stuff)
# -- all C function declarations are put onto one line for ease of parsing

  my $defn = shift;
  my @lines = split (/\n/, $defn);

  foreach (@lines) {

    next if (/^\#/);  # Skip commented out lines
    next if (/^\s*$/); # Skip blank lines

    # some functions change with plplot version.  If this function
    # has a version specified, note it down.
    my $req_vers = '';
    if (/PLPLOT VERSION = (.*)/) {
      $req_vers = $1;
      print "This line: $_ only applies to version $req_vers\n" if ($debug);
    }

    print "$_\n" if ($debug);

    my ($return_type, $func_name, $parms) = /^(\w+\**)\s+(\w+)\((.+)\)\;/;
    (my $pfunc_name = $func_name) =~ s/c_//; # get rid of c_ in perl func names

    my @parms = split (/,/, $parms);

    my @vars  = ();
    my @types = ();
    my %output = ();
    foreach $parm (@parms) {

      my ($varname) = ($parm =~ /(\w+)$/);
      $parm =~ s/$varname//; # parm now contains the full C type
      $varname =~ s/0/zero/;
      $varname =~ s/1/one/;
      $varname =~ s/2/two/;
      $varname =~ s/3/three/; # PP doe not like variable names containing numbers
      $varname =~ s/int/in/;  # PP has trouble with variables starting with 'int'
      $parm =~ s/const //;    # get rid of 'const' in C type
      $parm =~ s/^\s+//;
      $parm =~ s/\s+$//;      # get rid of white space from 'parm'

      next if ($varname eq 'void');
      push (@vars, $varname);
      push (@types, $parm);

    }

    # skip bad version of plpoly3
    next if (($pfunc_name eq 'plpoly3') and (@vars != $plpoly3));

    # Now we have enough info to write out the dd_def.  All variables names are in
    # @vars.  Matching types are in @types.  The name of the function is in
    # $func_name and the perl name to call it is in $pfunc_name.

    my @pars      = ();
    my @code      = ();
    my @otherpars = ();
    my $output = ($pfunc_name =~ /^plg/); # flag: 1 = output routine.
    for (my $i=0;$i<@vars;$i++) {

      # determine Pars and OtherPars sections
      my $var  = $vars[$i];
      $var = "[o]$var" if ($output);
      my $type = $types[$i];
      my $dim  = ($type =~ tr/*/*/); # count of stars in type (PLFLT ** = 2D)
      my $dimstr;
      if    ($output || $dim == 0) { $dimstr = '()'; }
      elsif ($dim == 1) { $dimstr = '(dima)'; }
      elsif ($dim == 2) { $dimstr = '(dima,dimb)'; }

      if ($type =~ /PLFLT/) {
        push (@pars, "double $var$dimstr"); # double input variable
      } elsif ($type =~ /PLINT/) {
        push (@pars, "int $var$dimstr");    # integer input variable
      } elsif ($type =~ /char\s*\*/) {  # char * input or output
	push (@otherpars, "char *$vars[$i]");
      } else {
        die "unsupported type: $type";
      }

      # Determine Code section
      if      ($type =~ /char\s*\*/) { # char * input or output
        push (@code, "\$COMP($vars[$i])");
      } elsif ($dim == 0) { # pass by value
        push (@code, "\$$vars[$i]()");
      } else {
        push (@code, "\$P($vars[$i])");
      }

    }

    # if there are no PDL parameters, some compilers cannot handle the
    # pp_def ('foo', Pars => '') type definition.
    # For these cases, we use plain XS.
    if (@pars == 0) {
      my $pars = join (',', @vars);
      my $decl = '';
      for (my $i=0;$i<@vars;$i++) {
        $decl .= "\n\t$types[$i]\t$vars[$i]";
      }
      my $xsout = <<"EOC";
void
$pfunc_name($pars)$decl
CODE:
	$func_name($pars);
EOC
      print "$xsout" if ($debug);
      pp_addxs ('', $xsout);
      pp_add_exported('', $pfunc_name);

    } else {

      my $pars      = join (';', @pars);
      my $otherpars = join (';', @otherpars);
      my $code      = "$func_name(" . join (',', @code) . ");";

      # do the definition
      print "pp_def (\'$pfunc_name\',
             GenericTypes => [D],
             Pars => \'$pars\',
             OtherPars => \'$otherpars\',
             Code => \'$code\'
            );

            " if ($debug);

      pp_def ($pfunc_name,
 	      GenericTypes => [D],
              Pars => $pars,
              OtherPars => $otherpars,
              Code => $code);
    }

  }

}

=head1 LOW-LEVEL INTERFACE
=cut

pp_addpm (<<'EOPM');

=pod

The PDL low-level interface to the PLplot library closely mimics the C API.
Users are referred to the PLplot User's Manual, distributed with the source
PLplot tarball.  This manual is also available on-line at the PLplot web
site (L<http://www.plplot.org/>).

There are though two differences in way the functions are called.  The first
one is due to a limitation in the pp_def wrapper of PDL, which forces all
the non-piddle arguments to be at the end of the arguments list.  It is
the case of strings (C<char *>) arguments in the C API.  This affects the
following functions [shown below with their prototypes in PDL, with
arguments preceded by "(pdl)" are piddle-convertible; see the PLplot manual
for the meaning of the arguments]:

  plaxes ((pdl) x0, (pdl) y0, (pdl) xtick, (pdl) nxsub, (pdl) ytick,
          (pdl) nysub, (string) xopt, (string (yopt))
  plbox ((pdl) xtick, (pdl) nxsub, (pdl) ytick, (pdl) nysub,
         (string) xopt, (string) yopt)
  plbox3 ((pdl) xtick, (pdl) nsubx, (pdl) ytick, (pdl) nsuby,
          (pdl) ztick, (pdl) nsubz, (string) xopt, (string) xlabel,
	  (string) yopt, (string) ylabel, (string) zopt,
	  (string) zlabel)
  plmtex ((pdl) disp, (pdl) pos, (pdl) just, (string) side),
          (string) text);
  plstart ((pdl) nx, (pdl) ny, (string) devname);

The second notable different between the C and the PDL APIs is that many of
the PDL calls do not need arguments to specify the size of the the vectors
and/or matrices being passed.  This size parameters are deduced from the
size of the piddles, when possible.  For now, the following interfaces are
affected:

  plcont (f, kx, lx, ky, ly, clevel)
  plfill (x, y)
  plhist (data, datmin, datmax, nbin, oldwin)
  plline (x, y)
  plline3 (x, y, z)
  plpoly3 (x, y, z, draw, ifcc)
  plmesh (x, y, z, opt)
  plmeshc (x, y, z, opt, clevel)
  plot3d (x, y, z, opt, side)
  plpoin (x, y, code)
  plpoin3 (x, y, z, code)
  plscmap1l (itype, intensity, coord1, coord2, coord3, rev)
  plstyl (mark, space)
  plsym (x, y, code)

Some of the API functions implemented in PDL have other specificities in
comparison with the C API and will be discussed below.

=cut

EOPM

#-------------------------------------------------------------------------
# Create low level interface from edited PLplot header file.
#-------------------------------------------------------------------------

create_low_level (<<'EODEF');
void c_pladv(PLINT page);
void plarrows(PLFLT *u, PLFLT *v, PLFLT *x, PLFLT *y, PLINT n, PLFLT scale, PLFLT dx, PLFLT dy);
void c_plaxes(PLFLT x0, PLFLT y0, const char *xopt, PLFLT xtick, PLINT nxsub, const char *yopt, PLFLT ytick, PLINT nysub);
void c_plbin(PLINT nbin, PLFLT *x, PLFLT *y, PLINT center);
void c_plbop(void);
void c_plbox(const char *xopt, PLFLT xtick, PLINT nxsub, const char *yopt, PLFLT ytick, PLINT nysub);
void c_plbox3(const char *xopt, const char *xlabel, PLFLT xtick, PLINT nsubx, const char *yopt, const char *ylabel, PLFLT ytick, PLINT nsuby, const char *zopt, const char *zlabel, PLFLT ztick, PLINT nsubz);
#void c_plxormod(PLINT mode, PLINT *status);
void c_plclear(void);
void c_plcol0(PLINT icol0);
void c_plcol1(PLFLT col1);
void c_plcpstrm(PLINT iplsr, PLINT flags);
void pldid2pc(PLFLT *xmin, PLFLT *ymin, PLFLT *xmax, PLFLT *ymax);
void pldip2dc(PLFLT *xmin, PLFLT *ymin, PLFLT *xmax, PLFLT *ymax);
void c_plend(void);
void c_plend1(void);
void c_plenv(PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax, PLINT just, PLINT axis);
void c_plenv0(PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax, PLINT just, PLINT axis);
void c_pleop(void);
void c_plerrx(PLINT n, PLFLT *xmin, PLFLT *xmax, PLFLT *y);
void c_plerry(PLINT n, PLFLT *x, PLFLT *ymin, PLFLT *ymax);
void c_plfamadv(void);
#void c_plfill(PLINT n, PLFLT *x, PLFLT *y);
void c_plfill3(PLINT n, PLFLT *x, PLFLT *y, PLFLT *z);
void c_plflush(void);
void c_plfont(PLINT ifont);
void c_plfontld(PLINT fnt);
void c_plgchr(PLFLT *p_def, PLFLT *p_ht);
void c_plgcompression(PLINT *compression);
#void c_plgdev(char *p_dev);
void c_plgdidev(PLFLT *p_mar, PLFLT *p_aspect, PLFLT *p_jx, PLFLT *p_jy);
void c_plgdiori(PLFLT *p_rot);
void c_plgdiplt(PLFLT *p_xmin, PLFLT *p_ymin, PLFLT *p_xmax, PLFLT *p_ymax);
void c_plgfam(PLINT *p_fam, PLINT *p_num, PLINT *p_bmax);
#void c_plgfnam(char *fnam);
void c_plglevel(PLINT *p_level);
void c_plgpage(PLFLT *p_xp, PLFLT *p_yp,PLINT *p_xleng, PLINT *p_yleng, PLINT *p_xoff, PLINT *p_yoff);
void c_plgra(void);
void c_plgspa(PLFLT *xmin, PLFLT *xmax, PLFLT *ymin, PLFLT *ymax);
void c_plgvpd(PLFLT *p_xmin, PLFLT *p_xmax, PLFLT *p_ymin, PLFLT *p_ymax);
void c_plgvpw(PLFLT *p_xmin, PLFLT *p_xmax, PLFLT *p_ymin, PLFLT *p_ymax);
#void c_plgstrm(PLINT *p_strm);
#void c_plgver(char *p_ver);
void c_plgxax(PLINT *p_digmax, PLINT *p_digits);
void c_plgyax(PLINT *p_digmax, PLINT *p_digits);
void c_plgzax(PLINT *p_digmax, PLINT *p_digits);
#void c_plhist(PLINT n, PLFLT *data, PLFLT datmin, PLFLT datmax, PLINT nbin, PLINT oldwin);
void c_plhls(PLFLT h, PLFLT l, PLFLT s);
#void c_plhlsrgb(PLFLT h, PLFLT l, PLFLT s, PLFLT *p_r, PLFLT *p_g, PLFLT *p_b); # implemented below
void c_plinit(void);
void c_pljoin(PLFLT x1, PLFLT y1, PLFLT x2, PLFLT y2);
void c_pllab(const char *xlabel, const char *ylabel, const char *tlabel);
void c_pllightsource(PLFLT x, PLFLT y, PLFLT z);
#void c_plline(PLINT n, PLFLT *x, PLFLT *y); # defined below with bad value support
#void c_plline3(PLINT n, PLFLT *x, PLFLT *y, PLFLT *z);
void c_pllsty(PLINT lin);
#void c_plmesh(PLFLT *x, PLFLT *y, PLFLT **z, PLINT nx, PLINT ny, PLINT opt); # must handle ** parms separately
#void c_plmkstrm(PLINT *p_strm);
void c_plmtex(const char *side, PLFLT disp, PLFLT pos, PLFLT just, const char *text);
void c_plmtex3(const char *side, PLFLT disp, PLFLT pos, PLFLT just, const char *text);
#void c_plot3d(PLFLT *x, PLFLT *y, PLFLT **z, PLINT nx, PLINT ny, PLINT opt, PLINT side); # must handle ** parms separately
#void c_plotsh3d(PLFLT *x, PLFLT *y, PLFLT **z, PLINT nx, PLINT ny, PLINT side); # must handle ** parms separately
void c_plpat(PLINT nlin, PLINT *inc, PLINT *del);
#void c_plpoin(PLINT n, PLFLT *x, PLFLT *y, PLINT code);
#void c_plpoin3(PLINT n, PLFLT *x, PLFLT *y, PLFLT *z, PLINT code);
#void c_plpoly3(PLINT n, PLFLT *x, PLFLT *y, PLFLT *z, PLINT *draw, PLINT ifcc);
void c_plprec(PLINT setp, PLINT prec);
void c_plpsty(PLINT patt);
void c_plptex(PLFLT x, PLFLT y, PLFLT dx, PLFLT dy, PLFLT just, const char *text);
void c_plptex3(PLFLT x, PLFLT y, PLFLT z, PLFLT dx, PLFLT dy, PLFLT dz, PLFLT sx, PLFLT sy, PLFLT sz, PLFLT just, const char *text);
void c_plreplot(void);
void c_plrgb(PLFLT r, PLFLT g, PLFLT b);
void c_plrgb1(PLINT r, PLINT g, PLINT b);
void c_plschr(PLFLT def, PLFLT scale);
void c_plscmap0n(PLINT ncol0);
void c_plscmap1n(PLINT ncol1);
void c_plscol0(PLINT icol0, PLINT r, PLINT g, PLINT b);
void c_plscolbg(PLINT r, PLINT g, PLINT b);
void c_plscolor(PLINT color);
void c_plscompression(PLINT compression);
void c_plsdev(const char *devname);
void c_plsdidev(PLFLT mar, PLFLT aspect, PLFLT jx, PLFLT jy);
void c_plsdimap(PLINT dimxmin, PLINT dimxmax, PLINT dimymin, PLINT dimymax, PLFLT dimxpmm, PLFLT dimypmm);
void c_plsdiori(PLFLT rot);
void c_plsdiplt(PLFLT xmin, PLFLT ymin, PLFLT xmax, PLFLT ymax);
void c_plsdiplz(PLFLT xmin, PLFLT ymin, PLFLT xmax, PLFLT ymax);
void c_pl_setcontlabelparam(PLFLT offset, PLFLT size, PLFLT spacing, PLINT active);
void c_pl_setcontlabelformat(PLINT lexp, PLINT sigdig);
void c_plsfam(PLINT fam, PLINT num, PLINT bmax);
void c_plsfnam(const char *fnam);
void c_plsmaj(PLFLT def, PLFLT scale);
void c_plsmin(PLFLT def, PLFLT scale);
void c_plsori(PLINT ori);
void c_plspage(PLFLT xp, PLFLT yp, PLINT xleng, PLINT yleng, PLINT xoff, PLINT yoff);
void c_plspause(PLINT pause);
void c_plsstrm(PLINT strm);
void c_plssub(PLINT nx, PLINT ny);
void c_plssym(PLFLT def, PLFLT scale);
void c_plstar(PLINT nx, PLINT ny);
void c_plstart(const char *devname, PLINT nx, PLINT ny);
void c_plstripa(PLINT id, PLINT pen, PLFLT x, PLFLT y);
void c_plstripd(PLINT id);
#void c_plstyl(PLINT nms, PLINT *mark, PLINT *space);
void c_plsvpa(PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax);
void c_plsxax(PLINT digmax, PLINT digits);
void plsxwin(PLINT window_id);
void c_plsyax(PLINT digmax, PLINT digits);
#void c_plsym(PLINT n, PLFLT *x, PLFLT *y, PLINT code);
void c_plszax(PLINT digmax, PLINT digits);
void c_pltext(void);
void c_plvasp(PLFLT aspect);
void c_plvpas(PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax, PLFLT aspect);
void c_plvpor(PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax);
void c_plvsta(void);
void c_plw3d(PLFLT basex, PLFLT basey, PLFLT height, PLFLT xmin0, PLFLT xmax0, PLFLT ymin0, PLFLT ymax0, PLFLT zmin0, PLFLT zmax0, PLFLT alt, PLFLT az);
void c_plwid(PLINT width);
void c_plwind(PLFLT xmin, PLFLT xmax, PLFLT ymin, PLFLT ymax);
void c_plsetopt(char *opt, char *optarg);
void plP_gpixmm(PLFLT *p_x, PLFLT *p_y);
#void c_pltimefmt(const char *fmt);
EODEF

#-------------------------------------------------------------------------
# take care of low-level funcs available in 5.9.0 (not all are
# alpha-specific, but it's an adequate canary. Eliminate this chunk
# and uncomment the lines above when we require at least PLplot 5.9.0.
# -------------------------------------------------------------------------
unless ($noalpha){
  create_low_level (<<'EODEF');
void c_pltimefmt(const char *fmt);
void c_plscolbga(PLINT r, PLINT g, PLINT b, PLFLT a);
void c_plscol0a(PLINT icol0, PLINT r, PLINT g, PLINT b, PLFLT a);
EODEF
}

# C routine to draw lines with gaps.  This is useful for map continents and other things.
=head2 plline
=cut

$doc = <<'EOD';
=for ref

Draws line segments along (x1,y1)->(x2,y2)->(x3,y3)->...

=for bad

If the nth value of either x or y are bad, then it will be skipped, breaking
the line.  In this way, you can specify multiple line segments with a single
pair of x and y piddles.

The usage is straight-forward:

=for usage

 plline($x, $y);

For example:

=for example

 # Draw a sine wave
 $x = sequence(100)/10;
 $y = sin($x);
 
 # Draws the sine wave:
 plline($x, $y);
 
 # Set values above 3/4 to 'bad', effectively drawing a bunch of detached,
 # capped waves
 $y->setbadif($y > 3/4);
 plline($x, $y);

=cut

EOD

pp_def ('plline',
         Pars => 'x(n); y(n)',
         GenericTypes => [D],
         HandleBad => 1,
         NoBadifNaN => 1,
         Code => 'c_plline($SIZE(n),$P(x),$P(y));',
         BadCode => 'int i;
                     int j;
                     for (i=1;i<$SIZE(n);i++) {
                       j = i-1;	/* PP does not like using i-1 in a PDL ref.  Use j instead. */
                       if ($ISGOOD(x(n=>i)) && $ISGOOD(x(n=>j))) {
                         c_pljoin ($x(n=>j), $y(n=>j), $x(n=>i), $y(n=>i));
                       }
                     }',
         Doc => $doc,
        );

=head2 plcolorpoints
=cut

$doc = <<'EOD';
=for ref

PDL-specific: Implements what amounts to a threaded version of plsym.

=for bad

Bad values for z are simply skipped; all other bad values are not processed.

In the following usage, all of the piddles must have the same dimensions:

=for usage

 plcolorpoints($x, $y, $z, $symbol_index, $minz, $maxz)

For example:

=for example

 # Generate a parabola some points
 my $x = sequence(30) / 3;   # Regular sampling
 my $y = $x**2;              # Parabolic y
 my $z = 30 - $x**3;         # Cubic coloration
 my $symbols = floor($x);    # Use different symbols for each 1/3 of the plot
                             #  These should be integers.
 
 plcolorpoints($x, $y, $z, $symbols, -5, 20);  # Thread over everything
 plcolorpoints($x, $y, 1, 1, -1, 2);           # same color and symbol for all

=cut
EOD

# C routine to draw points with a color scale
pp_def ('plcolorpoints',
         Pars => 'x(n); y(n); z(n); int sym(); minz(); maxz()',
         GenericTypes => [D],
         HandleBad => 1,
         Code => 'int i;
                  int j;
                  int ns = $SIZE(n);
                  PLFLT zrange, ci;

                  zrange  = $maxz() - $minz();

                  for (i=0;i<ns;i++) {
                    ci = (zrange == 0.0) ? 0.5 : ($z(n=>i) - $minz()) / zrange;  /* get color idx in 0-1 range */
                    if (ci < 0) ci = 0; /* enforce bounds */
                    if (ci > 1) ci = 1;
                    c_plcol1 (ci); /* set current color */
                    c_plsym (1, &$x(n=>i), &$y(n=>i), $sym()); /* plot it */
                  }',
         BadCode =>
	         'int i;
                  int j;
                  int ns = $SIZE(n);
                  PLFLT zrange, ci;

                  zrange  = $maxz()  - $minz();

                  for (i=0;i<ns;i++) {
                    if ($ISBAD(z(n=>i))) continue;
                    ci = (zrange == 0.0) ? 0.5 : ($z(n=>i) - $minz()) / zrange;  /* get color idx in 0-1 range */
                    if (ci < 0) ci = 0; /* enforce bounds */
                    if (ci > 1) ci = 1;
                    c_plcol1 (ci); /* set current color */
                    c_plsym (1, &$x(n=>i), &$y(n=>i), $sym()); /* plot it */
         
                  }',
         Doc => $doc,
       );



pp_def ('plsmem',
	     GenericTypes => [B],
             Pars => 'int maxx();int maxy();image(3,x,y)',
             Code => 'c_plsmem($maxx(),$maxy(),$P(image));'
            ) unless ($nomem);

#
## Box drawing primitive, taken from PLPLOT bar graph example
#

pp_def ('plfbox',
         Pars => 'xo(); yo()',
         GenericTypes => [D],
         Code => 'PLFLT x[4], y[4];
                  x[0] = $xo() - 0.5;
                  y[0] = 0.;
                  x[1] = $xo() - 0.5;
                  y[1] = $yo();
                  x[2] = $xo() + 0.5;
                  y[2] = $yo();
                  x[3] = $xo() + 0.5;
                  y[3] = 0.;
                  plfill(4, x, y);',
        );

#
## Parse PLplot options given in @ARGV-like arrays
#

pp_def ('plParseOpts',
        GenericTypes => [D],
        Pars => 'int [o] retval()',
        OtherPars => 'SV* argv; int mode',
        Doc => 'FIXME: documentation here!',
        Code => '
                SV* sv = $COMP (argv);
                AV* arr;
                int argc, newargc, i, retval;
                char** args;

                if ( !(SvROK (sv) && SvTYPE (SvRV (sv)) == SVt_PVAV)) {
                        barf("plParseOpts requires an array ref");
                }

                arr = (AV*) SvRV (sv);
                newargc = argc = av_len (arr) + 1;
                if (argc > 0) {
                  args = calloc (argc , sizeof (char*));

                  for (i = 0; i < argc; i++) {
                          STRLEN len;
                          args[i] = SvPV (* av_fetch (arr, i, 0), len);
                  }

                  $retval() = c_plparseopts (&newargc, args, $COMP (mode));

                  for (i = 0; i < newargc; i++)
                          av_push (arr, newSVpv (args[i], 0));

                  for (i = 0; i < argc; i++)
                          av_shift (arr);

                  free (args);
                }
        ',
);

# Plots a character at the specified points

pp_def ('plpoin',
         Pars => 'x(n); y(n); int code()',
         GenericTypes => [D],
         Code => 'c_plpoin($SIZE(n),$P(x),$P(y),$code());'
        );

# Plots a character at the specified points in 3 space

pp_def ('plpoin3',
         Pars => 'x(n); y(n); z(n); int code()',
         GenericTypes => [D],
         Code => 'c_plpoin3($SIZE(n),$P(x),$P(y),$P(z),$code());'
        );

# Draw a line in 3 space

pp_def ('plline3',
         Pars => 'x(n); y(n); z(n)',
         GenericTypes => [D],
         Code => 'c_plline3($SIZE(n),$P(x),$P(y),$P(z));'
        );

# Draws a polygon in 3 space

pp_def ('plpoly3',
         Pars => 'x(n); y(n); z(n); int draw(m); int ifcc()',
         GenericTypes => [D],
         Code => 'c_plpoly3($SIZE(n),$P(x),$P(y),$P(z),$P(draw),$ifcc());'
        );

# Plot a histogram from unbinned data

pp_def ('plhist',
         Pars => 'data(n); datmin(); datmax(); int nbin(); int oldwin()',
         GenericTypes => [D],
         Code => 'c_plhist($SIZE(n),$P(data),$datmin(),$datmax(),$nbin(),$oldwin());'
        );

# Area fill

pp_def ('plfill',
         Pars => 'x(n); y(n)',
         GenericTypes => [D],
         Code => 'c_plfill($SIZE(n),$P(x),$P(y));'
        );

# Plots a symbol at the specified points

pp_def ('plsym',
         Pars => 'x(n); y(n); int code()',
         GenericTypes => [D],
         Code => 'c_plsym($SIZE(n),$P(x),$P(y),$code());'
        );

# Plot shaded 3-d surface plot

pp_def ('plsurf3d',
         Pars => 'x(nx); y(ny); z(nx,ny); int opt(); clevel(nlevel);',
         GenericTypes => [D],
         Code => '
           int i, j, size_x, size_y;
           PLFLT** zz;

           size_x =  $SIZE(nx);
           size_y =  $SIZE(ny);
           plAlloc2dGrid (&zz, size_x, size_y);
           for (i = 0; i < size_x; i++)
             for (j = 0; j < size_y; j++)
               zz[i][j] = $z(nx => i, ny => j);
           c_plsurf3d ($P(x), $P(y), zz, size_x, size_y, $opt(),
                       $P(clevel), $SIZE(nlevel));
           plFree2dGrid (zz, size_x, size_y);'
        );

# Set line style

pp_def ('plstyl',
         Pars => 'int mark(nms); int space(nms)',
         GenericTypes => [D],
         Code => 'c_plstyl ($SIZE(nms), $P(mark), $P(space));'
       );

# PLplot standard random number generation.  Using this
# helps to keep the demo plots identical.

if (!$v59_or_earlier) {
  pp_def ('plseed',
          Pars => 'int seed()',
          Code => 'unsigned int useed = (unsigned int)$seed(); c_plseed(useed);'
        );

  pp_def ('plrandd',
          Pars => 'double [o]rand()',
          Code => '$rand() = c_plrandd();'
        );
}

# Plot contours


# pltr0: Identity transformation
# pltr1: Linear interpolation from singly dimensioned coord arrays
# Linear interpolation from doubly dimensioned coord arrays

for my $func ('pltr0', 'pltr1', 'pltr2') {

  pp_addxs (<<"EOC");
void
$func (x, y, grid)
  double x
  double y
  long grid
PPCODE:
  PLFLT tx, ty;

  $func (x, y, &tx, &ty, (PLPointer) grid);

  EXTEND (SP, 2);
  PUSHs (sv_2mortal (newSVnv ((double) tx)));
  PUSHs (sv_2mortal (newSVnv ((double) ty)));
EOC

  pp_add_exported ($func);
}


# Allocates a PLcGrid object for use in pltr1

pp_def ('plAllocGrid',
        Pars => 'double xg(nx); double yg(ny); int [o] grid()',
        GenericTypes => [D],
        Doc => 'FIXME: documentation here!',
        Code => '
          PLcGrid *grid;
          int i, nx, ny;

          nx = $SIZE(nx);
          ny = $SIZE(ny);

          grid = (PLcGrid*) malloc (sizeof (PLcGrid));
          grid->xg = (PLFLT*) calloc (nx, sizeof (PLFLT));
          grid->yg = (PLFLT*) calloc (ny, sizeof (PLFLT));
          grid->nx = nx;
          grid->ny = ny;

          for (i = 0; i < nx; i++)
            grid->xg[i] = $xg(nx => i);

          for (i = 0; i < ny; i++)
            grid->yg[i] = $yg(ny => i);

          $grid() = ('.$int_ptr_type.') grid;'
        );


# Free a PLcGrid object

pp_addxs (<<"EOC");
void
plFreeGrid (pg)
  long pg
PPCODE:
  PLcGrid* grid = (PLcGrid*) pg;
  free (grid->xg);
  free (grid->yg);
  free (grid);
EOC

pp_add_exported (plFreeGrid);


# Allocates a PLcGrid2 object for use in pltr2

pp_def ('plAlloc2dGrid',
        Pars => 'double xg(nx,ny); double yg(nx,ny); int [o] grid()',
        GenericTypes => [D],
        Doc => 'FIXME: documentation here!',
        Code => '
          PLcGrid2 *grid;
          int i, j, nx, ny;

          nx = $SIZE(nx);
          ny = $SIZE(ny);

          grid = (PLcGrid2*) malloc (sizeof (PLcGrid2));
          plAlloc2dGrid (&(grid->xg), nx, ny);
          plAlloc2dGrid (&(grid->yg), nx, ny);

          for (i = 0; i < nx; i++)
            for (j = 0; j < ny; j++) {
              grid->xg[i][j] = $xg(nx => i, ny => j);
              grid->yg[i][j] = $yg(nx => i, ny => j);
            }

          grid->nx = nx;
          grid->ny = ny;

          $grid() = ('.$int_ptr_type.') grid;'
        );


# Free a PLcGrid2 object

pp_addxs (<<"EOC");
void
plFree2dGrid (pg)
  long pg
PPCODE:
  PLcGrid2* grid = (PLcGrid2*) pg;
  plFree2dGrid (grid->xg, grid->nx, grid->ny);
  plFree2dGrid (grid->yg, grid->nx, grid->ny);
  free (grid);
EOC

pp_add_exported (plFree2dGrid);

pp_addhdr (<<'EOH');

#define check_sub_pointer(subptr, errmsg) \
  if (SvTRUE (subptr) \
      && (! SvROK (subptr) || SvTYPE (SvRV (subptr)) != SVt_PVCV)) \
    croak (errmsg);

static SV* pltr_subroutine;

static IV pltr0_iv;
static IV pltr1_iv;
static IV pltr2_iv;

static void
pltr_callback (PLFLT x, PLFLT y, PLFLT* tx, PLFLT* ty, PLPointer pltr_data)
{
  I32 count;
  dSP;

  ENTER;
  SAVETMPS;

  PUSHMARK (SP);
  XPUSHs (sv_2mortal (newSVnv ((double) x)));
  XPUSHs (sv_2mortal (newSVnv ((double) y)));
  XPUSHs ((SV*) pltr_data);
  PUTBACK;

  count = call_sv (pltr_subroutine, G_ARRAY);

  SPAGAIN;

  if (count != 2)
    croak ("pltr: must return two scalars");

  *ty = (PLFLT) POPn;
  *tx = (PLFLT) POPn;

  PUTBACK;
  FREETMPS;
  LEAVE;
}

static void*
get_standard_pltrcb (SV* cb)
{
  if ( !SvROK(cb) ) return NULL; /* Added to prevent bug in plshades for 0 input. D. Hunt 12/18/2008 */
  IV sub = (IV) SvRV (cb);

  if (sub == pltr0_iv)
    return (void*) pltr0;
  else if (sub == pltr1_iv)
    return (void*) pltr1;
  else if (sub == pltr2_iv)
    return (void*) pltr2;
  else
    return SvTRUE (cb) ? (void*) pltr_callback : NULL;
}

static SV* defined_subroutine;

static PLINT
defined_callback (PLFLT x, PLFLT y)
{
  I32 count, retval;
  dSP;

  ENTER;
  SAVETMPS;

  PUSHMARK (SP);
  XPUSHs (sv_2mortal (newSVnv ((double) x)));
  XPUSHs (sv_2mortal (newSVnv ((double) y)));
  PUTBACK;

  count = call_sv (defined_subroutine, G_SCALAR);

  SPAGAIN;

  if (count != 1)
    croak ("defined: must return one scalar");

  retval = POPi;

  PUTBACK;
  FREETMPS;
  LEAVE;

  return retval;
}

static SV* mapform_subroutine;

static void default_magic (pdl *p, int pa) { p->data = 0; }

static void
mapform_callback (PLINT n, PLFLT* x, PLFLT* y)
{
  pdl *x_pdl, *y_pdl;
  PLFLT *tx, *ty;
  SV *x_sv, *y_sv;
  int dims, i;
  I32 count, ax;
  dSP;

  ENTER;
  SAVETMPS;

  dims = n;

  x_pdl = PDL->pdlnew ();
  PDL->add_deletedata_magic(x_pdl, default_magic, 0);
  PDL->setdims (x_pdl, &dims, 1);
  x_pdl->datatype = PDL_D;
  x_pdl->data = x;
  x_pdl->state |= PDL_DONTTOUCHDATA | PDL_ALLOCATED;
  x_sv = sv_newmortal ();
  PDL->SetSV_PDL (x_sv, x_pdl);

  y_pdl = PDL->pdlnew ();
  PDL->add_deletedata_magic(y_pdl, default_magic, 0);
  PDL->setdims (y_pdl, &dims, 1);
  y_pdl->datatype = PDL_D;
  y_pdl->data = y;
  y_pdl->state |= PDL_DONTTOUCHDATA | PDL_ALLOCATED;
  y_sv = sv_newmortal ();
  PDL->SetSV_PDL (y_sv, y_pdl);

  PUSHMARK (SP);
  XPUSHs (x_sv);
  XPUSHs (y_sv);
  PUTBACK;

  count = call_sv (mapform_subroutine, G_ARRAY);

  SPAGAIN;
  SP -= count ;
  ax = (SP - PL_stack_base) + 1;

  if (count != 2)
    croak ("mapform: must return two piddles");

  tx = (PLFLT*) ((PDL->SvPDLV(ST(0)))->data);
  ty = (PLFLT*) ((PDL->SvPDLV(ST(1)))->data);

  for (i = 0; i < n; i++) {
    *(x + i) = *(tx + i);
    *(y + i) = *(ty + i);
  }

  PUTBACK;
  FREETMPS;
  LEAVE;
}

EOH

# The init_pltr is used internally by the PLD::Graphics::PLplot
# module to set the variables pltr{0,1,2}_iv to the "pointers"
# of the Perl subroutines pltr{1,2,3}.  These variables are later used by
# get_standard_pltrcb to provide the pointers to the C function pltr{0,1,2}.
# This accelerates functions like plcont and plshades when those standard
# transformation functions are used.

pp_def ('init_pltr',
         GenericTypes => [D],
         Pars => '',
         OtherPars => 'SV* p0; SV* p1; SV* p2;',
         Doc => 'FIXME: documentation here!',
         Code => '
           pltr0_iv = (IV) SvRV ($COMP(p0));
           pltr1_iv = (IV) SvRV ($COMP(p1));
           pltr2_iv = (IV) SvRV ($COMP(p2));');

pp_addpm (<<'EOPM');
init_pltr (\&pltr0, \&pltr1, \&pltr2);
EOPM

# plot continental outline in world coordinates

pp_def ('plmap',
        Pars => 'minlong(); maxlong(); minlat(); maxlat();',
	OtherPars => 'SV* mapform; char* type;',
        GenericTypes => [D],
        Code => '
           mapform_subroutine = $COMP(mapform);
	   check_sub_pointer (mapform_subroutine,
	     "plmap: mapform must be either 0 or a subroutine pointer");

           plmap (SvTRUE ($COMP(mapform)) ? mapform_callback : NULL,
	     $COMP(type), $minlong(), $maxlong(), $minlat(), $maxlat());'
       );

# Plot the latitudes and longitudes on the background

pp_def ('plmeridians',
        Pars => 'dlong(); dlat(); minlong(); maxlong(); minlat(); maxlat();',
	OtherPars => 'SV* mapform;',
        GenericTypes => [D],
        Code => '
           mapform_subroutine = $COMP(mapform);
	   check_sub_pointer (mapform_subroutine,
	     "plmeridians: mapform must be either 0 or a subroutine pointer");

           plmeridians (SvTRUE ($COMP(mapform)) ? mapform_callback : NULL,
	     $dlong(), $dlat(), $minlong(), $maxlong(), $minlat(), $maxlat());'
       );

# Shade regions on the basis of value

pp_def ('plshades',
         Pars => 'z(x,y); xmin(); xmax(); ymin(); ymax();
                  clevel(l); int fill_width(); int cont_color();
                  int cont_width(); int rectangular()',
         OtherPars => 'SV* defined; SV* pltr; SV* pltr_data;',
         GenericTypes => [D],
         Code => '
           int nx    = $SIZE(x);
           int ny    = $SIZE(y);
           int nlvl  = $SIZE(l);
           int i, j;
           PLFLT **z;
 	   void (*pltrcb) ();
           PLPointer pltrdt;

           plAlloc2dGrid (&z, nx, ny);

           for (i = 0; i < nx; i++)
             for (j = 0; j < ny; j++)
               z[i][j] = (PLFLT) $z(x => i, y => j);

           defined_subroutine = $COMP(defined);
	   check_sub_pointer (defined_subroutine,
	     "plshades: defined must be either 0 or a subroutine pointer");

           pltr_subroutine = $COMP(pltr);
	   check_sub_pointer (pltr_subroutine,
	     "plshades: pltr must be either 0 or a subroutine pointer");

	   pltrcb = get_standard_pltrcb ($COMP(pltr));
           if (pltrcb != pltr_callback)
             pltrdt = (PLPointer) SvIV ($COMP(pltr_data));
           else
             pltrdt = $COMP(pltr_data);

           c_plshades (z, nx, ny,
             SvTRUE ($COMP(defined)) ? defined_callback : NULL,
             $xmin(), $xmax(), $ymin(), $ymax(),
             $P(clevel), nlvl, $fill_width(), $cont_color(), $cont_width(),
             plfill, $rectangular(), pltrcb, pltrdt);

           free(z);',
       );

pp_def ('plcont',
         GenericTypes => [D],
         Pars => 'f(nx,ny); int kx(); int lx(); int ky(); int ly(); '
                 . 'clevel(nlevel)',
         OtherPars => 'SV* pltr; SV* pltr_data;',
         Doc => 'FIXME: documentation here!',
         Code => '
           int i, j, size_x, size_y;
           PLFLT** ff;
	   void (*pltrcb) ();
           PLPointer pltrdt;

           size_x = $SIZE(nx);
           size_y = $SIZE(ny);

           plAlloc2dGrid (&ff, size_x, size_y);

           for (i = 0; i < size_x; i++)
             for (j = 0; j < size_y; j++)
               ff[i][j] = $f(nx => i, ny => j);

           pltr_subroutine = $COMP(pltr);
	   check_sub_pointer (pltr_subroutine,
	     "plcont: pltr must be either 0 or a subroutine pointer");

	   pltrcb = get_standard_pltrcb ($COMP(pltr));
           if (pltrcb != pltr_callback)
             pltrdt = (PLPointer) SvIV ($COMP(pltr_data));
           else
             pltrdt = $COMP(pltr_data);

           c_plcont (ff, size_x, size_y, $kx(), $lx(), $ky(), $ly(),
                     $P(clevel), $SIZE(nlevel),
                     pltrcb, pltrdt);

           plFree2dGrid (ff, size_x, size_y);'
        );


# Surface mesh

pp_def ('plmesh',
         Pars => 'x(nx); y(ny); z(nx,ny); int opt()',
         GenericTypes => [D],
         Doc => 'FIXME: documentation here!',
         Code => '
           int i, j, size_x, size_y;
           PLFLT** zz;

           size_x = $SIZE(nx);
           size_y = $SIZE(ny);

           plAlloc2dGrid (&zz, size_x, size_y);

           for (i = 0; i < size_x; i++)
             for (j = 0; j < size_y; j++)
               zz[i][j] = $z(nx => i, ny => j);

           c_plmesh ($P(x), $P(y), zz, size_x, size_y, $opt());

           plFree2dGrid (zz, size_x, size_y);'
        );

# Magnitude colored plot surface mesh with contour

pp_def ('plmeshc',
         Pars => 'x(nx); y(ny); z(nx,ny); int opt(); clevel(nlevel)',
         GenericTypes => [D],
         Doc => 'FIXME: documentation here!',
         Code => '
           int i, j, size_x, size_y;
           PLFLT** zz;

           size_x = $SIZE(nx);
           size_y = $SIZE(ny);

           plAlloc2dGrid (&zz, size_x, size_y);

           for (i = 0; i < size_x; i++)
             for (j = 0; j < size_y; j++)
               zz[i][j] = $z(nx => i, ny => j);

           c_plmeshc ($P(x), $P(y), zz, size_x, size_y, $opt(),
                      $P(clevel), $SIZE(nlevel));

           plFree2dGrid (zz, size_x, size_y);'
        );

# 3-d surface plot

pp_def ('plot3d',
         Pars => 'x(nx); y(ny); z(nx,ny); int opt(); int side()',
         GenericTypes => [D],
         Doc => 'FIXME: documentation here!',
         Code => '
           int i, j, size_x, size_y;
           PLFLT** zz;

           size_x = $SIZE(nx);
           size_y = $SIZE(ny);

           plAlloc2dGrid (&zz, size_x, size_y);

           for (i = 0; i < size_x; i++)
             for (j = 0; j < size_y; j++)
               zz[i][j] = $z(nx => i, ny => j);

           c_plot3d ($P(x), $P(y), zz, size_x, size_y, $opt(), $side());

           plFree2dGrid (zz, size_x, size_y);'
        );


# Plots a 3-d representation of the function z[x][y] with contour

pp_def ('plot3dc',
         Pars => 'x(nx); y(ny); z(nx,ny); int opt(); clevel(nlevel)',
         GenericTypes => [D],
         Doc => 'FIXME: documentation here!',
         Code => '
           int i, j, size_x, size_y;
           PLFLT** zz;

           size_x = $SIZE(nx);
           size_y = $SIZE(ny);

           plAlloc2dGrid (&zz, size_x, size_y);

           for (i = 0; i < size_x; i++)
             for (j = 0; j < size_y; j++)
               zz[i][j] = $z(nx => i, ny => j);

           c_plot3dc ($P(x), $P(y), zz, size_x, size_y, $opt(),
                      $P(clevel), $SIZE(nlevel));

           plFree2dGrid (zz, size_x, size_y);'
        );


# Set color map1 colors using a piece-wise linear relationship

pp_def ('plscmap1l',
         Pars => 'int itype(); isty(n); coord1(n); coord2(n); coord3(n);'
	         . ' int rev(nrev)',
         GenericTypes => [D],
         Doc => 'FIXME: documentation here!',
         Code => '
	   PLINT* rev;

	   if ($SIZE(nrev) == 0)
	     rev = NULL;
	   else if ($SIZE(nrev) == $SIZE(n))
   	     rev = $P(rev);
           else
             croak ("plscmap1l: rev must have either lenght == 0 or have the same length of the other input arguments");

	   c_plscmap1l ($itype(), $SIZE(n), $P(isty), $P(coord1),
	                       $P(coord2), $P(coord3), rev);'
        );

# Shade individual region on the basis of value

pp_def ('plshade1',
         GenericTypes => [D],
         Pars => 'a(nx,ny); left(); right(); bottom(); top(); shade_min();'
                 . 'shade_max(); sh_cmap(); sh_color(); sh_width();'
                 . 'min_color(); min_width(); max_color(); max_width();'
                 . 'rectangular()',
         OtherPars => 'SV* defined; SV* pltr; SV* pltr_data;',
         Doc => 'FIXME: documentation here!',
         Code => '
           int i, j, size_x, size_y;
           PLFLT* a;
	   void (*pltrcb) ();
           PLPointer pltrdt;

           size_x = $SIZE(nx);
           size_y = $SIZE(ny);

           a = (PLFLT *) calloc (size_x * size_y, sizeof(PLFLT));

           for (i = 0; i < size_x; i++)
             for (j = 0; j < size_y; j++)
               a[i * size_y + j] = (PLFLT) $a(nx => i, ny => j);

           defined_subroutine = $COMP(defined);
	   check_sub_pointer (defined_subroutine,
	     "plshade1: defined must be either 0 or a subroutine pointer");

           pltr_subroutine = $COMP(pltr);
	   check_sub_pointer (pltr_subroutine,
	     "plshade1: pltr must be either 0 or a subroutine pointer");

	   pltrcb = get_standard_pltrcb ($COMP(pltr));
           if (pltrcb != pltr_callback)
             pltrdt = (PLPointer) SvIV ($COMP(pltr_data));
           else
             pltrdt = $COMP(pltr_data);

           c_plshade1 (a, size_x, size_y,
             SvTRUE ($COMP(defined)) ? defined_callback : NULL,
             $left(), $right(), $bottom(), $top(),
             $shade_min(), $shade_max(), $sh_cmap(), $sh_color(), $sh_width(),
             $min_color(), $min_width(), $max_color(), $max_width(),
             plfill, $rectangular(), pltrcb, pltrdt);

           free ((void *) a);'
        );

# Plot gray-level image

pp_def ('plimage',
         GenericTypes => [D],
         Pars => 'idata(nx,ny); xmin(); xmax(); ymin(); ymax();'
	         . 'zmin(); zmax(); Dxmin(); Dxmax(); Dymin(); Dymax();',
         Code => '
           int i, j, size_x, size_y;
           PLFLT** idata;

           size_x = $SIZE(nx);
           size_y = $SIZE(ny);

           plAlloc2dGrid (&idata, size_x, size_y);

           for (i = 0; i < size_x; i++)
             for (j = 0; j < size_y; j++)
               idata[i][j] = $idata(nx => i, ny => j);

           plimage (idata, size_x, size_y,
	     $xmin(), $xmax(), $ymin(), $ymax(), $zmin(), $zmax(),
             $Dxmin(), $Dxmax(), $Dymin(), $Dymax());

           plFree2dGrid (idata, size_x, size_y);'
	);


# Plot image with transformation

if (!$v59_or_earlier) {

  pp_def ('plimagefr',
          GenericTypes => [D],
          #  plimagefr (idata, nx, ny, xmin, xmax, ymin, ymax, zmin, zmax, valuemin, valuemax, pltr, pltr_data);
          #  plimagefr ($img,          0,    $width, 0,  $height, 0,    0, $img_min, $img_max, \&pltr2, $grid);
          Pars => 'idata(nx,ny); xmin(); xmax(); ymin(); ymax();'
          . 'zmin(); zmax(); valuemin(); valuemax();', # here!!!
          OtherPars => 'SV* pltr; SV* pltr_data;',
          Code => '
           int i, j, size_x, size_y;
           PLFLT** idata;
 	   void (*pltrcb) ();
           PLPointer pltrdt;

           size_x = $SIZE(nx);
           size_y = $SIZE(ny);

           pltr_subroutine = $COMP(pltr);
	   check_sub_pointer (pltr_subroutine, "plimagefr: pltr must be either 0 or a subroutine pointer");

	   pltrcb = get_standard_pltrcb ($COMP(pltr));
           if (pltrcb != pltr_callback)
             pltrdt = (PLPointer) SvIV ($COMP(pltr_data));
           else
             pltrdt = $COMP(pltr_data);

           plAlloc2dGrid (&idata, size_x, size_y);

           for (i = 0; i < size_x; i++)
             for (j = 0; j < size_y; j++)
               idata[i][j] = $idata(nx => i, ny => j);

           c_plimagefr (idata, size_x, size_y,
	     $xmin(), $xmax(), $ymin(), $ymax(), $zmin(), $zmax(),
             $valuemin(), $valuemax(), 
             (SvTRUE ($COMP(pltr)) ? pltrcb : NULL),
             (SvTRUE ($COMP(pltr)) ? pltrdt : NULL));

           plFree2dGrid (idata, size_x, size_y);'
	);
}

# Set xor mode:
# mode = 1-enter, 0-leave, status = 0 if not interactive device

pp_addpm (<<'EOPM');
=head2 plxormod

=for sig

  $status = plxormod ($mode)

=for ref

See the PLplot manual for reference.

=cut
EOPM

pp_addxs (<<"EOC");
int
plxormod (mode)
  int mode
CODE:
  PLINT status;
  c_plxormod (mode, &status);
  RETVAL = status;
OUTPUT:
  RETVAL
EOC

pp_add_exported ('plxormod');

# Wait for graphics input event and translate to world coordinates

pp_addpm (<<'EOPM');
=head2 plGetCursor

=for sig

  %gin = plGetCursor ()

=for ref

plGetCursor waits for graphics input event and translate to world
coordinates and returns a hash with the following keys:

    type:      of event (CURRENTLY UNUSED)
    state:     key or button mask
    keysym:    key selected
    button:    mouse button selected
    subwindow: subwindow (alias subpage, alias subplot) number
    string:    translated string
    pX, pY:    absolute device coordinates of pointer
    dX, dY:    relative device coordinates of pointer
    wX, wY:    world coordinates of pointer

Returns an empty hash if no translation to world coordinates is possible.

=cut
EOPM

pp_addxs (<<"EOC");
void
plGetCursor ()
PPCODE:
  PLGraphicsIn gin;
  if (plGetCursor (&gin)) {
    EXTEND (SP, 24);
    PUSHs (sv_2mortal (newSVpv ("type", 0)));
    PUSHs (sv_2mortal (newSViv ((IV) gin.type)));
    PUSHs (sv_2mortal (newSVpv ("state", 0)));
    PUSHs (sv_2mortal (newSVuv ((UV) gin.state)));
    PUSHs (sv_2mortal (newSVpv ("keysym", 0)));
    PUSHs (sv_2mortal (newSVuv ((UV) gin.keysym)));
    PUSHs (sv_2mortal (newSVpv ("button", 0)));
    PUSHs (sv_2mortal (newSVuv ((UV) gin.button)));
    PUSHs (sv_2mortal (newSVpv ("subwindow", 0)));
    PUSHs (sv_2mortal (newSViv ((IV) gin.subwindow)));
    PUSHs (sv_2mortal (newSVpv ("string", 0)));
    PUSHs (sv_2mortal (newSVpv (gin.string, 0)));
    PUSHs (sv_2mortal (newSVpv ("pX", 0)));
    PUSHs (sv_2mortal (newSViv ((IV) gin.pX)));
    PUSHs (sv_2mortal (newSVpv ("pY", 0)));
    PUSHs (sv_2mortal (newSViv ((IV) gin.pY)));
    PUSHs (sv_2mortal (newSVpv ("dX", 0)));
    PUSHs (sv_2mortal (newSVnv ((double) gin.dX)));
    PUSHs (sv_2mortal (newSVpv ("dY", 0)));
    PUSHs (sv_2mortal (newSVnv ((double) gin.dY)));
    PUSHs (sv_2mortal (newSVpv ("wX", 0)));
    PUSHs (sv_2mortal (newSVnv ((double) gin.wX)));
    PUSHs (sv_2mortal (newSVpv ("wY", 0)));
    PUSHs (sv_2mortal (newSVnv ((double) gin.wY)));
  }
EOC

pp_add_exported ('plGetCursor');

pp_addpm (<<'EOPM');
=head2 plgstrm

=for sig

  $strm = plgstrm ()

=for ref

Returns the number of the current output stream.

=cut
EOPM

pp_addxs (<<"EOC");
int
plgstrm ()
CODE:
  PLINT strm;
  c_plgstrm (&strm);
  RETVAL = strm;
OUTPUT:
  RETVAL
EOC

pp_add_exported ('plgstrm');

pp_addpm (<<'EOPM');
=head2 plgsdev

=for sig

  $driver = plgdev ()

=for ref

Returns the current driver name.

=cut
EOPM

pp_addxs (<<"EOC");
char*
plgdev ()
CODE:
  char driver[80];
  c_plgdev (driver);
  RETVAL = driver;
OUTPUT:
  RETVAL
EOC

pp_add_exported ('plgdev');

pp_addxs (<<"EOC");
char*
plgfnam ()
CODE:
  char driver[80];
  c_plgfnam (driver);
  RETVAL = driver;
OUTPUT:
  RETVAL
EOC

pp_add_exported ('plgfnam');

pp_addpm (<<'EOPM');
=head2 plmkstrm

=for sig

  $strm = plmkstrm ()

=for ref

Creates a new stream and makes it the default.  Returns the number of
the created stream.

=cut
EOPM

pp_addxs (<<"EOC");
int
plmkstrm ()
CODE:
  PLINT strm;
  c_plmkstrm (&strm);
  RETVAL = strm;
OUTPUT:
  RETVAL
EOC

pp_add_exported ('plmkstrm');

# Get the current library version number

pp_addpm (<<'EOPM');
=head2 plgver

=for sig

  $version = plgver ()

=for ref

See the PLplot manual for reference.

=cut
EOPM

pp_addxs (<<"EOC");
char*
plgver ()
CODE:
  char ver[80];
  c_plgver (ver);
  RETVAL = ver;
OUTPUT:
  RETVAL
EOC

pp_add_exported ('plgver');

pp_def ('plstripc',
         GenericTypes => [D],
         Pars => 'xmin(); xmax(); xjump(); ymin(); ymax();'
	         . 'xlpos(); ylpos(); int y_ascl(); int acc();'
                 . 'int colbox(); int collab();'
                 . 'int colline(n); int styline(n);  int [o] id()',
         OtherPars => 'char* xspec; char* yspec; SV* legline;'
                      . 'char* labx; char* laby; char* labtop',
         Doc => 'FIXME: documentation here!',
         Code => '
           I32 i;
           PLINT id;
           char* legline[4];
           SV* sv_legline = $COMP(legline);
           AV* av_legline;

           if (! SvROK (sv_legline)
               || SvTYPE (SvRV (sv_legline)) != SVt_PVAV)
             croak ("plstripc: legline must be a reference to an array");

           av_legline = (AV*) SvRV (sv_legline);

           if (av_len (av_legline) != 3)
             croak ("plstripc: legline must have four elements");

           if ($SIZE(n) != 4)
             croak ("plstripc: colline and styline must have four elements");

           for (i = 0; i < 4; i++) {
             SV** elem = av_fetch (av_legline, i, 0);
             legline[i] = (char *) SvPV_nolen (*elem);
           }

           c_plstripc (&id, $COMP(xspec), $COMP(yspec),
	     $xmin(), $xmax(), $xjump(), $ymin(), $ymax(),
	     $xlpos(), $ylpos(),$y_ascl(), $acc(), $colbox(), $collab(),
	     $P(colline), $P(styline), legline,
	     $COMP(labx), $COMP(laby), $COMP(labtop));

           $id() = (int) id;'
        );


pp_def ('plgriddata',
         GenericTypes => [D],
         Pars => 'x(npts); y(npts); z(npts); xg(nptsx); yg(nptsy);'
	         . 'int type(); data(); [o] zg(nptsx,nptsy)',
         Doc => 'FIXME: documentation here!',
         Code => '
           int i, j, size_x, size_y;
           PLFLT** zg;

           size_x = $SIZE(nptsx);
           size_y = $SIZE(nptsy);

           plAlloc2dGrid (&zg, size_x, size_y);

           c_plgriddata ($P(x), $P(y), $P(z), $SIZE(npts),
	                 $P(xg), size_x, $P(yg), size_y,
	                 zg, $type(), $data());

           for (i = 0; i < size_x; i++)
             for (j = 0; j < size_y; j++)
               $zg(nptsx => i, nptsy => j) = zg[i][j];

           plFree2dGrid (zg, size_x, size_y);
         '
        );



unless ($novect) {

# Vector field plots

  pp_def ('plvect',
	  GenericTypes => [D],
	  Pars => 'u(nx,ny); v(nx,ny); scale();',
	  OtherPars => 'SV* pltr; SV* pltr_data;',
	  Doc => 'FIXME: documentation here!',
	  Code => '
           int i, j, size_x, size_y;
           PLFLT** u;
           PLFLT** v;
	   void (*pltrcb) ();
           PLPointer pltrdt;

           size_x = $SIZE(nx);
           size_y = $SIZE(ny);

           plAlloc2dGrid (&u, size_x, size_y);
           plAlloc2dGrid (&v, size_x, size_y);

           for (i = 0; i < size_x; i++)
             for (j = 0; j < size_y; j++) {
               u[i][j] = $u(nx => i, ny => j);
               v[i][j] = $v(nx => i, ny => j);
             }

           pltr_subroutine = $COMP(pltr);
	   check_sub_pointer (pltr_subroutine,
	     "plvect: pltr must be either 0 or a subroutine pointer");

	   pltrcb = get_standard_pltrcb ($COMP(pltr));
           if (pltrcb != pltr_callback)
             pltrdt = (PLPointer) SvIV ($COMP(pltr_data));
           else
             pltrdt = $COMP(pltr_data);

           plvect (u, v, size_x, size_y, $scale(), pltrcb, pltrdt);

           plFree2dGrid (u, size_x, size_y);
           plFree2dGrid (v, size_x, size_y);'
	 );

  pp_def ('plsvect',
	  Pars => 'arrowx(npts); arrowy(npts); int fill()',
	  GenericTypes => [D],
	  Code => 'c_plsvect ($P(arrowx), $P(arrowy), $SIZE(npts), $fill());'
	 );

  pp_def ('plhlsrgb',
	  GenericTypes => [D],
	  Pars => 'double h();double l();double s();double [o]p_r();double [o]p_g();double [o]p_b()',
	  Code => 'c_plhlsrgb($h(),$l(),$s(),$P(p_r),$P(p_g),$P(p_b));'
	 );

  # void c_plgcol0(PLINT icol0, PLINT *r, PLINT *g, PLINT *b);
  pp_def ('plgcol0',
	  Pars => 'int icolzero(); int [o]r(); int [o]g(); int [o]b()',
	  Code => 'c_plgcol0($icolzero(),$P(r),$P(g),$P(b));'
	 );

  # void c_plgcolbg(PLINT *r, PLINT *g, PLINT *b);
  pp_def ('plgcolbg',
	  Pars => 'int [o]r(); int [o]g(); int [o]b()',
	  Code => 'c_plgcolbg($P(r),$P(g),$P(b));'
	 );

  # void c_plscmap0(PLINT *r, PLINT *g, PLINT *b, PLINT ncol0);
  pp_def ('plscmap0',
	  Pars => 'int r(n); int g(n); int b(n)',
	  Code => 'c_plscmap0($P(r),$P(g),$P(b), $SIZE(n));'
	 );

  # void c_plscmap1(PLINT *r, PLINT *g, PLINT *b, PLINT ncol1);
  pp_def ('plscmap1',
	  Pars => 'int r(n); int g(n); int b(n)',
	  Code => 'c_plscmap1($P(r),$P(g),$P(b), $SIZE(n));'
	 );

  if (!$noalpha) {

    # void c_plgcol0a(PLINT icol0, PLINT *r, PLINT *g, PLINT *b, PLFLT *a);
    pp_def ('plgcol0a',
	    Pars => 'int icolzero(); int [o]r(); int [o]g(); int [o]b(); double [o]a()',
	    Code => 'c_plgcol0a($icolzero(),$P(r),$P(g),$P(b),$P(a));'
	   );

    # void c_plgcolbga(PLINT *r, PLINT *g, PLINT *b, PLFLT *a);
    pp_def ('plgcolbga',
	    Pars => 'int [o]r(); int [o]g(); int [o]b(); double [o]a()',
	    Code => 'c_plgcolbga($P(r),$P(g),$P(b),$P(a));'
	   );

    # void c_plscmap0a(PLINT *r, PLINT *g, PLINT *b, PLFLT *a, PLINT ncol0);
    pp_def ('plscmap0a',
	    Pars => 'int r(n); int g(n); int b(n); double a(n)',
	    Code => 'c_plscmap0a($P(r),$P(g),$P(b),$P(a),$SIZE(n));'
	   );

    # void c_plscmap1a(PLINT *r, PLINT *g, PLINT *b, PLFLT *a, PLINT ncol1);
    pp_def ('plscmap1a',
	    Pars => 'int r(n); int g(n); int b(n); double a(n)',
	    Code => 'c_plscmap1a($P(r),$P(g),$P(b),$P(a),$SIZE(n));'
	   );

    # Set color map1 colors using a piece-wise linear relationship, include alpha channel

    pp_def ('plscmap1la',
	    Pars => 'int itype(); isty(n); coord1(n); coord2(n); coord3(n); coord4(n);'
	    . ' int rev(nrev)',
	    GenericTypes => [D],
	    Doc => 'FIXME: documentation here!',
	    Code => '
	      PLINT* rev;

	      if ($SIZE(nrev) == 0)
	        rev = NULL;
	      else if ($SIZE(nrev) == $SIZE(n))
   	        rev = $P(rev);
              else
                croak ("plscmap1la: rev must have either length == 0 or have the same length of the other input arguments");

	      c_plscmap1la ($itype(), $SIZE(n), $P(isty), $P(coord1),
	                    $P(coord2), $P(coord3), $P(coord4), rev);'
	   );


    #
    ## UNICODE font manipulation
    #

    if (!$v59_or_earlier) {

      # plgfont(PLINT *p_family, PLINT *p_style, PLINT *p_weight);
      pp_def ('plgfont',
	      Pars => 'int [o]p_family(); int [o]p_style(); int [o]p_weight();',
	      Code => 'c_plgfont($P(p_family),$P(p_style),$P(p_weight));'
	     );
      
      #  plsfont (PLINT family, PLINT style, PLINT weight);
      pp_def ('plsfont',
	      Pars => 'int family(); int style(); int weight();',
	      Code => 'c_plsfont($family(),$style(),$weight());'
	     );
    }



    #  plcalc_world (PLFLT rx, PLFLT ry, PLFLT *wx, PLFLT *wy, PLINT *window);
    pp_def ('plcalc_world',
	    Pars => 'double rx(); double ry(); double [o]wx(); double [o]wy(); int [o]window()',
	    Code => 'c_plcalc_world($rx(), $ry(), $P(wx), $P(wy), $P(window));'
	   );


pp_addxs (<<"EOC");
unsigned int plgfci ()
    CODE:
    {
	unsigned int	RETVAL;
        unsigned int    fci;
	c_plgfci(&fci);
        RETVAL = fci;

	XSprePUSH; PUSHu((UV)RETVAL);
    }
    XSRETURN(1);
EOC
pp_add_exported('', 'plgfci');

pp_addxs (<<'EOC');
void 
plsfci(fci)
        unsigned int fci
    CODE:
        c_plsfci(fci);
EOC
pp_add_exported('', 'plsfci');
 
  }

}

pp_addpm (<<'EOPM');

=pod

=head1 WARNINGS AND ERRORS

PLplot gives many errors and warnings.  Some of these are given by the
PDL interface while others are internal PLplot messages.  Below are
some of these messages, and what you need to do to address them:

=over

=item *
Box must be a ref to a four element array

When specifying a box, you must pass a reference to a
four-element array, or use an anonymous four-element array.

 # Gives trouble:
 $pl->xyplot($x, $y, BOX => (0, 0, 100, 200) );
 # What you meant to say was:
 $pl->xyplot($x, $y, BOX => [0, 0, 100, 200] );

=item *
Too many colors used! (max 15)


=back

=head1 AUTHORS

  Doug Hunt <dhunt@ucar.edu>
  Rafael Laboissiere <rlaboiss@users.sourceforge.net>
  David Mertens <mertens2@illinois.edu>

=head1 SEE ALSO

perl(1), PDL(1), L<http://www.plplot.org/>

The other common graphics packages include L<PDL::PGPLOT>
and L<PDL::TriD>.

=cut

EOPM

pp_done();

# Local Variables:
# mode: cperl
# End: