// pgraf.r -- RLaB implementation of D. Raymond's portable graphics.
// Please send comments and bug reports to raymond@kestrel.nmt.edu.
//
// ******************************************************************
// ***************** For the impatient user: ************************
// ******************************************************************
//
// For the following I assume that you have rlab running under X11
// with a 3 button mouse.  If portable graphics hasn't been
// loaded automatically, you will have to load it yourself by doing
// ``rfile pgraf''.  Be sure that pgraf.r in the search path of rlab.
// Get information on portable graphics by doing ``help pgraf''
// once it is loaded.  (If you are seeing this, you have probably
// already gotten that far :-).)
//
// ------------------------------------------------------------------
// Plotting two independent variables against one dependent variable:
// ------------------------------------------------------------------
//
//     x = [0:15:.2]';          // create a column matrix as indep var
//     s = sin(x);              // create first dep var
//     c = cos(x);              // create second dep var
//     xpgplot([x, s, c]);       // plot s and c against x
//
// A window will pop up and the plot will be made.  To dismiss
// the plot, click the middle or right mouse button with the cursor in
// the plot window.  (If the plot has more than one page, the left
// button will backup a page, the middle button will advance a page
// or exit if at the end, and the right button will exit
// unconditionally.)
//
// ------------------------------------------------------------------
// ----- Making a contour plot of a function of two variables: ------
// ------------------------------------------------------------------
//
//     x = ones(21, 1)*[0:30];  // make a matrix of x values
//     y = [0:20]'*ones(1,31);  // make a matrix of y values
//     f = cos(x/3).*sin(y/3);  // define a function of x and y
//     xpgplot2(f);              // plot it
//
// The line of text at the top of the plot gives dz, the contour
// interval, zlo, the value of f below which horizontal hatching
// is done, and zhi, the value of f above which vertical hatching
// is done.
//
// ------------------------------------------------------------------
// - Making a perspective view plot of a function of two variables: -
// ------------------------------------------------------------------
//
//     x = ones(31, 1)*[0:20];  // make a matrix of x values
//     y = [0:30]'*ones(1, 21); // make a matrix of y values
//     r = sqrt(x.*x + y.*y) + 1.e-10; // compute distance from origin
//     f = sin(r)./r;           // define function of x and y
//     xpgplot3(f);              // plot it
//
// The perspective can be changed by depressing various keyboard
// keys in conjunction with the left and middle mouse buttons.
// For instance, the plot can be rotated by pressing ``a'' (for
// azimuth) and pressing the left or middle mouse button repeatedly.
// The key ``e'' controls the elevation, ``z'' controls the zoom,
// and ``r'' controls the distance from the focus point, which is
// taken as the center of the plot.  Note that changes in distance
// cause a compensating change in zoom, so the apparent size of
// the plot won't change -- there will simply be changes in
// perspective.
//
// ------------------------------------------------------------------
// ------- Printing the last plot to a postscript printer: ----------
// ------------------------------------------------------------------
//
//     xpgps("outfile.ps");      // creates a file named outfile.ps.
//
// This produces a postscript rendering of a plot in landscape mode
// that roughly fills a standard 8.5 by 11 or A4 page.  Multi-page
// metacode files produce multiple pages of plots.  The postscript
// file is in a form that can be printed on a postscript printer.
//
// ------------------------------------------------------------------
// -- Creating an encapsulated postscript file from the last plot: --
// ------------------------------------------------------------------
//
//     xpgeps("outfile.eps");    // creates a file named outfile.eps.
//
// While this function will work for multi-page metacode files, it
// doesn't make much sense to translate these into encapsulated
// postscript.  The plot comes out in portrait mode with dimensions
// of about 5 by 3 inches.
//
// ******************************************************************
// ****************** Other examples of use *************************
// ******************************************************************
//
// For other examples of how to use portable graphics in rlab, see
// the accompanying demonstration file pgdemo.r.  Load this by
// doing ``rfile pgdemo''.  Then try running the demonstration:
// ``pgdemo()''.  Finally, see how pgdemo does it by doing
// ``help pgdemo''.
//
// ******************************************************************
// ************** Now for the full documentation ********************
// ******************************************************************
//
// General description: The portable graphics system consists of
// two parts, a program that creates a metacode file containing plotting
// information (the metacode generator) and one or more programs
// that read the metacode file and create a plot on a particular
// graphics device (metacode reader).  This rlab file is a metacode
// generator.  There is also a library of C functions to generate
// portable graphics metacode.
//
// The metacode file created by the portable graphics system has
// the name pgraf3.out.  It is always created in the current working
// directory.  It may be read multiple times by one or more metacode
// readers once it is created.  Furthermore, once a metacode reader
// reads a metacode file, it squirrels away a temporary copy of the form
// pgraf3.number, where the number is the process id of the metacode
// reader.  This means that once a metacode reader is running, the
// original metacode file can be overwritten by a subsequent metacode
// generator without causing the existing metacode reader any problems.
// This allows multiple metacode readers to operate simultaneously
// on different metacode files without interfering with each other.
// However, when a metacode reader terminates, the temporary file
// is deleted.
//
// A given metacode file contains one or more pages of graphical
// images.  Each page is divided up into up to 100 windows, each
// containing a plot of some kind.  The types of plots supported
// are (1) ordinary plots of one or more dependent variables against
// a single independent variable, (2) contour, fill, vector, and
// number grid plots of one or more functions of two variables, and
// (3) perspective view plots of a function of two variables.
// Plots of consistent types can be overlayed on each other.
//
// ------------------------------------------------------------------
// --------------- Dependencies and call ordering -------------------
// ------------------------------------------------------------------
//
// The general sequence of calls to create one or more pages of
// graphics takes the following form:
//
//     xpgopen();                     // this is always needed --
//                                   // a new metacode file is started
//     xpgclear();                    // this starts the first page
//     xpgwindow(...);                // each call to xpgwindow
//                                   // creates a window on the
//                                   // current page
//     per-window graphics commands
//
//     xpgpause();                    // this closes the current page
//     xpgclear();                    // starts a new page
//     ...
//     xpgpause();
//     xpgclose();                    // this always needed -- the
//                                   // current metacode file is closed
//
// In each window the following sequence of calls should be used.
// For an ordinary plot or a contour-type plot:
//
//     xpgaxes(...);                  // this is always needed
//
//     some combination of calls to xpglabel(...), xpgdraw(...), and
//     xpgmark(...)
//
//     --- or ---
//
//     some combination of calls to xpglabel(...), xpgconto(...),
//     xpgfill(...), xpggrid(...), and xpgvector(...)
//
// For a perspective view plot:
//
//     xpgpview(...);                 // this is always needed
//     xpgpaxes(...);                 // not needed, but desirable
//
//     some combination of calls to xpgpline(...), xpgplabel(...),
//     and xpgpsurface(...)
//
// ------------------------------------------------------------------
// --------- Calling sequences for convenience functions ------------
// ------------------------------------------------------------------
//
// xpgplot(a, sxl, sxh, xtics, xl, syl, syh, ytics, yl): Make a single
// plot of one or more dependent variables (up to 16) against a
// single dependent variable.  Note that all arguments but the
// first are optional.  However, if one of the optional arguments
// is defined, they all must be defined.  The x and y axes are scaled
// so that the plotted functions just fit into the plot.  Use of
// the optional arguments allows more elegant scaling to be defined.
//     a: A real matrix with the first column being the independent variable
//          and the subsequent columns being the dependent variables.
//     sxl, sxh: The lower and upper limits of the x axis.
//     xtics: The number of tics and numbers to be used on the x axis.
//     xl: A string containing no newlines for labeling the x axis.
//     syl, syh, ytics, yl: Analogous variables for the y axis.
//
// xpgplot2(b, dz, zlo, zhi, xvals, xt, xl, yvals, yt, yl): Make a
// contour-fill plot of a function of two real variables.  Note that all
// arguments but the first are optional.  However, only the following
// combinations of arguments can be used: the first by itself,
// the first four together, or all of the arguments together.
// The default values of zlo, zhi, and dz are printed at the top
// of the plot.
//     b: The matrix defining the points of the function of two
//          variables to be plotted.
//     dz: The contour interval to be used.  The default value is
//          0.1 times the difference between the max and min values
//          of b.
//     zlo, zhi: Horizontal hatching is done where b < zlo and
//          vertical hatching is done where b > zhi.  Default values
//          are roughly (min(b) + max(b))/2 +- dz for zhi and zlo.
//     xvals: A row matrix defining the locations where b is defined
//          in the horizontal (row-wise) direction.  See xpgconto.
//     xt: The number of tics on the horizontal axis.
//     xl: The label for the horizontal axis.
//     yvals, yt, yl: Similar quantities for the vertical axis; yvals
//          a column vector, as described in xpgconto.
//
// xpgplot3(b, xl, yl, zl, xp, yp, zp, xf, yf, zf, zoom): Make a
// single perspective view plot.  Note that all arguments but the
// first are optional.  However, if one of the optional arguments
// is defined, they all must be defined.  The x and y axes are scaled
// so that the function domain is [0, 1] in x and y.  The scaling
// in z makes the maximum minus minimum value of b appear to
// equal to 0.1.
//     b: The matrix defining the points of the real function of two
//          variables to be plotted.
//     xl, yl, zl: Labels in the form of strings with no embedded
//          newlines for the x, y, and z axes.
//     xp, yp, zp: The viewpoint for the perspective view.
//     xf, yf, zf: The focal point for the perspective view.
//     zoom: The zoom factor.
//
// xpgps(outfile): Create a postscript file from the last metacode
// file suitable for printing on a postscript printer.
//     outfile: The desired name of the postscript file in the
//          form of a string.
//
// xpgeps(outfile): Create an encapsulated postscript file from the
// last metacode file suitable for inclusion in a document.
//     outfile: The desired name of the encapsulated postscript
//          file in the form of a string.
//
// ------------------------------------------------------------------
// ------------ Calling sequences for basic functions ---------------
// ------------------------------------------------------------------
//
// xpgopen(metareader, outfile): Open a metacode file.
//     metareader: The name (in string form) of the desired metacode
//          reader program.  Possible options are "x11", the
//          X11 reader, "tek", for Tektronix graphics terminals and
//          friends, "ps", which creates a postscript file
//          suitable for sending to a postscript printer, and
//          "eps", which creates an embedded postscript file
//          for inclusion in documents.  This argument is optional,
//          with the default being "x11".
//     outfile: This is the name of the file to be created if the
//          "ps" or "eps" metacode readers are specified.  This
//          argument is optional, with the default name being
//          "pgoutfile.ps".
//
// xpgclose(): Close a metacode file.
//
// xpgrender(metareader, outfile): Invoke a metacode reader on an
// existing metacode file.
//     metareader, outfile: Same meaning as in xpgopen.
//
// xpgclear(): Clear the screen and prepare for a new page of graphics.
//
// xpgpause(): Finish a page of graphics.
//
// xpgwindow(nw, xl, yl, xh, yh): Create a window on the current
// graphics page.
//     nw: This is an integer in the range [1, 100] which specifies
//          the number of this window.  Subsequent graphics calls
//          refer to this number in order to plot something in this
//          window.
//     xl, yl, xh, yh: Coordinates of the lower left and upper right
//          corners of the window.  The coordinate space on each
//          page ranges over [0, 15] in x (the horizontal direction)
//          and [0, 10] in y (the vertical direction).  This
//          is commensurate with the ratio of horizontal to vertical
//          dimensions of the page on the screen, which is 1.5 to 1.
//
// xpgaxes(nw, sxl, sxh, ntx, xlabel, syl, syh, nty, ylabel): This
// function draws horizontal and vertical axes, defines a scale for
// subsequent plotting commands, and writes a label for each axis.
//     nw: The window in which the axes are to be drawn.
//     sxl, sxh: The lower and upper limits of the x coordinate.
//     ntx: The number of tics to be placed on the x axis.
//     xlabel: A string containing no newlines to be used as an
//          x axis label.
//     syl, syh, nty, ylabel: Analogous parameters for the y axis.
//
// xpgline(nw, ltype, x, y): Plot a line defined by points specified
// by the x and y vectors.
//     nw: The window in which the line is to be plotted.
//     ltype: Line types 1, 2, 3, 4 yield black lines of type solid,
//          long-dashed, short-dashed, and long-short-dashed
//          respectively.  Line types 5-8, 9-12, and 13-16 yield
//          identical patterns in the colors red, green, and blue
//          respectively on devices that support color.  On others,
//          they just repeat the 1-4 patterns in black.
//     x, y: Vectors of equal size defining the x and y locations of
//          the points defining the line.
//
// xpgmark(nw, mtype, x, y, size): Make a single mark in the specified
// window.
//     nw: The window in which the mark is to be made.
//     mtype: Defines the type of mark: 1 = plus sign, 2 = cross,
//          3 = square, 4 = diamond.
//     x, y: Location of the mark.
//     size: A value of 1.0 makes the mark about twice the size
//          of a single character.
//
// xpglabel(nw, sx, sy, text): Make a label in the specified window.
//     nw: The window in which the label is to be made.
//     sx, sy: The x and y coordinates defining the left end of the
//          label.
//     text: A string containing no newlines defining the label.
//
// xpgconto(nw, dz, ltype, field, xvals, yvals): Make a contour plot
// of a function of two variables in the specified window.
//     nw: The window in which the contour plot is to be made.
//     dz: The contour interval to be used.  Contours are made where
//          the function equals n*dz -- n is an integer.
//     ltype: The line type to use in making the contours, as defined
//          in xpgline.
//     field: A matrix defining the function of two variables to be
//          contoured.
//     xvals, yvals: A row vector and a column vector defining the
//          x and y positions of each specified value of field.  Xvals
//          must have the same number of columns as field and yvals
//          must have the same number of rows.
//
// xpgfill(nw, low, high, ltype, field, xvals, yvals): Create hatching
// where a function of two variables takes on values outside of
// a specified interval.  Best used in conjunction with xpgconto, but
// may be used alone.
//     nw: The window in which to make the fill plot.
//     low, high: Create horizontal hatching where field < low and
//          vertical hatching where field > high.
//     ltype: The line type to be used in the hatching, as specified
//          in xpgline.
//     field: A matrix defining the function of two variables as in
//          xpgconto.
//     xvals, yvals: A row vector and a column vector as in xpgconto.
//
// xpggrid(nw, jx, jy, field, xvals, yvals): Plot a grid of numbers
// defining a function of two variables.
//     nw: The window in which to make the grid.
//     jx, jy: These integers specify that every jxth value be plotted
//          in the x direction and every jyth value in the y direction.
//          The purpose is to avoid overcrowding of plotted numbers.
//     field: A matrix defining the function of two variables as in
//          xpgconto.
//     xvals, yvals: A row vector and a column vector as in xpgconto.
//
// xpgvector(nw, sx, sy, vx, vy, xvals, yvals): Plot a vector field drawing
// arrows of size proportional to the length of the vector at each
// point.
//     nw: The window in which to make the vector plot.
//     sx, sy: Scaling factors relating apparent vector lengths
//          to their physical lengths.  The apparent length, in units
//          defined by the scaling of xpgaxes, of x and y components
//          is obtained by multiplying the physical length of
//          each component by sx and sy respectively.  Except
//          in very odd circumstances, one should have sx = sy.
//     vx, vy: Matrices representing the x and y components of the
//          vector field.
//     xvals, yvals: A row vector and a column vector defining the
//          locations of the points in the vector field, as in xpgconto.
//
// xpgpview(nw, xp, yp, zp, xf, yf, zf, zoom): Set up for a perspective
// view plot in the specified window.
//     nw: The window in which the plot is to be made.
//     xp, yp, zp: The coordinates of the observer, or the ``viewpoint''.
//     xf, yf, zf: The coordinates of the center of the region being
//          viewed, or the ``focal point''.
//     zoom: The magnification to be used.  A value of 2 yields a field
//          of view comparable to that of an ordinary camera.
//
// xpgpaxes(nw, sxl, sxh, xstr, xlabel, syl, syh, ystr, ylabel,
// szl, szh, zstr, zlabel): Draw 3-D coordinate axes in perspective
// and label them.
//     nw: The window in which the axes are to be drawn.
//     sxl, sxh: The lower and upper values of x for the x axis.
//     xstr: The amount that the x axis is stretched in physical
//          space.  A value of 1 makes the axis coordinates the same
//          as physical space coordinates.  This is used to adjust
//          the scaling of perspective plots for best viewing.
//     xlabel: A string without newlines defining the label for the
//          x axis.
//     syl, syh, ystr, ylabel: Similar quantities for the y axis.
//     szl, szh, zstr, zlabel: Similar quantities for the z axis.
//
// xpgpline(nw, ltype, x, y, z): Draw a line in 3-D perspective view.
//     nw: The window in which to draw the line.
//     ltype: The line type used as in xpgline.
//     x, y, z: Three vectors of the same number of components
//          specifying the points that define the line.
//
// xpgpsurface(nw, ltype, field, xvals, yvals): Make a perspective
// view plot of a function of two variables.
//     nw: The window in which to make the plot.
//     ltype: The line type used as defined in xpgline.
//     field: A matrix defining the values of the function of two
//          variables, as in xpgconto.
//     xvals, yvals: A row matrix and a column matrix as in xpgconto.
//
// xpgplabel(nw, sx, sy, sz, text): Make a label in perspective view.
//     nw: The window in which to make the label.
//     sx, sy, sz: The location of the left end of the label in space.
//     text: A string containing no newlines that defines the label.
//
// ------------------------------------------------------------------
// ----------- Calling sequences for auxiliary functions ------------
// ------------------------------------------------------------------
//
// xpgbadset(badlim): Set an upper limit for data considered to be
// valid.  The default value is 1.e30.
//     badlim: Data values greater in absolute value than
//          badlim are considered to be bad or missing and are
//          not plotted.
//
// xpgscale(v): Returns a list containing suggested minimum and maximum
// scaling values and a suggested number of tics, all for xpgaxes.
//     v: A vector or matrix to be plotted.
// Returns a list of the form <<loval; hival; ntics>>, where slo is
// near the minimum value of v, shi is near the maximum value, and
// ntics is a nice number of tics to be used with xpgaxes.
//
// xpgparam2(v): Returns a list containing information useful for
// scaling two dimensional plots.
//     v: A matrix to be plotted using 2-D tools.
// Returns a list of the form <<dcon; lofill; hifill; xskip; yskip>>,
// where dcon is a suggested contour interval for xpgconto, lofill and
// hifill are suggested fill limits for xpgfill, and xskip and yskip
// are suggested skip parameters for xpggrid.
//
// xpgvscale(ax, ay, xvals, yvals): Returns a suggested scaling parameter
// for vector plots.
//     ax, ay: Matrices containing the x and y components of vectors
//          to be plotted using xpgvector.
//     xvals, yvals: Row and column matrices as used by xpgvector.
// Returns a suggested value for sx and sy in xpgvector.
//
// ********************************************************************
// ************* The code for pgraf.r follows. ************************
// ********************************************************************

static(fname, baddata, axl, ayl, axh, ayh, mrd, ofile);
static(onedcheck, twodcheck, argcheck);

// check to see if two vectors have same sizes
onedcheck = function(vec1, vec2)
{
  local(len1, len2);
  if (min(size(vec1)) != 1 || min(size(vec2)) != 1) {
    error("pgraf: an argument supposed to be a vector isn't");
  }
  len1 = max(size(vec1));
  len2 = max(size(vec2));
  if (len1 != len2) {
    error("pgraf: two vector arguments are not the same length");
  }
  return(len1);
};

// check to see if the proper number of arguments are present
argcheck = function(nactual, nexpected, routine)
{
  if (nactual != nexpected) {
    error("pgraf: improper number of arguments in " + routine);
  }
};

// check the validity of a 2-d array and its index arrays
twodcheck = function(field, xvals, yvals)
{
  if (field.nr != yvals.nr || field.nc != xvals.nc) {
    error("pgraf: size mismatch between 2-D array and index arrays\n");
  }
  if (yvals.nc != 1 || xvals.nr != 1) {
    error("pgraf: index array is a matrix, should be a vector\n");
  }
};

// check to be sure a matrix is real
realcheck = function(mat)
{
  if (mat.type != "real" || mat.class != "num") {
    error("pgraf: matrices for plotting must be type real\n");
  }
}

// open a pgraf3.out file and set defaults
xpgopen = function(metareader, outfile)
{
  global(fname, baddata, mrd, ofile);
  if (nargs > 0) {
    mrd = metareader;
  else
    mrd = "x11";
  }
  if (nargs > 1) {
    ofile = outfile;
  else
    ofile = "pgoutfile.ps";
  }
  fname = "/tmp/pgraf3.out";
  baddata = 1.e30;
  open(fname, "w");
};

// close the output file and run a rendering program
xpgclose = function()
{
  global(fname, mrd, ofile);
  close(fname);
  system("mv /tmp/pgraf3.out pgraf3.out");
  xpgrender(mrd, ofile);
};

// render a metacode output file
xpgrender = function(metareader, outfile)
{
  global(mrd, ofile);
  local(mrd1, ofile1, mprog);
  if (nargs == 0) {
    mrd1 = mrd;
  else
    mrd1 = metareader;
  }
  if (nargs == 1) {
    ofile1 = ofile;
  else
    ofile1 = outfile;
  }
  if (mrd1 == "x11") {
    mprog = "pgx";
  else if (mrd1 == "ps") {
    mprog = "pgps" + " > " + ofile1;
  else if (mrd1 == "eps") {
    mprog = "pgeps" + " > " + ofile1;
  else if (mrd1 == "tek") {
    mprog = "pgtek" + " > " + ofile1;
  else
    error("pgraf: output device " + mrd1 + " not recognized");
  }}}}
  system(mprog + "&");
  }
  

// clear the screen
xpgclear = function()
{
  global(fname, axl, ayl, axh, ayh);
  local(i);
  for (i in 1:10) {
    axl[i] = 0.;
    ayl[i] = 0.;
    axh[i] = 0.;
    ayh[i] = 0.;
  }
  fprintf(fname, " i\n");
};

// wait for response
xpgpause = function()
{
  global(fname);
  fprintf(fname, " f\n");
};

// set bad data limit
xpgbadset = function(badval)
{
  global(fname, baddata);
  argcheck(nargs, 1, "xpgbadset");
  baddata = badval;
  fprintf(fname, " x\n %g\n", baddata);
};

// draw a visible window
xpgwindow = function(nw, xl, yl, xh, yh)
{
  global(fname);
  argcheck(nargs, 5, "xpgwindow");
  fprintf(fname, " w\n%d %g %g %g %g 1\n", nw, xl, yl, xh, yh);
};

// draw a set of axes in a window
xpgaxes = function(nw, sxl, sxh, ntx, xlabel, syl, syh, nty, ylabel)
{
  global(fname, axl, ayl, axh, ayh);
  argcheck(nargs, 9, "xpgaxes");
  axl[nw] = sxl;
  ayl[nw] = syl;
  axh[nw] = sxh;
  ayh[nw] = syh;
  fprintf(fname, " a\n");
  fprintf(fname,"%d %g %g %g %g %d %d 1 1\n", nw,sxl,sxh,syl,syh,ntx,nty);
  fprintf(fname,"%s\n%s\n",xlabel,ylabel);
};

// find approximate minimum and maximum values of a matrix
// and suggest a number of tics for plotting
xpgscale = function(v)
{
  local(minval,maxval,ntics);
  argcheck(nargs, 1, "xpgscale");
  minval = min(min(v));
  maxval = max(max(v));
  ntics = 6;
  return <<loval = minval; hival = maxval; ntics = ntics>>;
};

// suggest a contour interval and appropriate fill limits for 2-D plots
xpgparam2 = function(v)
{
  local(scale,dcon,midval,lofill,hifill,xskip,yskip);
  argcheck(nargs, 1, "xpgparam2");
  scale = xpgscale(v);
  dcon = (scale.hival - scale.loval)/10;
  if (dcon < 1.e-20) {
    dcon = 1.e-20;
  }
  midval = dcon*int((scale.loval + scale.hival)/(2*dcon) + 0.5);
  lofill = midval - dcon;
  hifill = midval + dcon;
  xskip = int(v.nr/9 + 1);
  yskip = int(v.nc/9 + 1);
  return <<dcon = dcon; lofill = lofill; hifill = hifill; xskip = xskip; yskip = yskip>>;
};

// suggest scaling parameters for vector plots
xpgvscale = function(ax, ay, xvals, yvals)
{
  argcheck(nargs, 4, "xpgvscale");
  local(xscale,yscale,maxval,nx,ny,xrange,yrange,maxrange);
  twodcheck(ax, xvals, yvals);
  twodcheck(ay, xvals, yvals);
  xscale = xpgscale(ax);
  yscale = xpgscale(ay);
  maxval = max([abs(xscale.loval), abs(xscale.hival), abs(yscale.loval), abs(yscale.hival)]);
  if (maxval < 1.e-20) {
    maxval = 1.e-20;
  }
  nx = xvals.nc;
  ny = xvals.nr;
  xrange = abs(xvals[1] - xvals[nx]);
  yrange = abs(yvals[1] - yvals[ny]);
  maxrange = max([xrange, yrange]);
  return 0.05*maxrange/maxval;
};

// make a contour plot
xpgconto = function(nw, dz, ltype, field, xvals, yvals)
{
  global(fname, axl, ayl, axh, ayh);
  local(ix, iy);
  local(nx, ny);
  argcheck(nargs, 6, "xpgconto");
  twodcheck(field, xvals, yvals);
  realcheck(field);
  realcheck(xvals);
  realcheck(yvals);
  nx = xvals.nc;
  ny = yvals.nr;
  fprintf(fname, " c\n");
  fprintf(fname, "%d %d %d %g %d\n", nw,nx,ny,dz,ltype);
  for (ix in 1:nx) {
    fprintf(fname, "%g\n", xvals[ix]);
  }
  for (iy in 1:ny) {
    fprintf(fname, "%g\n", yvals[iy]);
  }
  for (iy in 1:ny) {
    for (ix in 1:nx) {
      fprintf(fname, "%g\n", field[iy;ix]);
    }
  }
};

// make a fill plot
xpgfill = function(nw, low, high, ltype, field, xvals, yvals)
{
  global(fname);
  local(ix, iy);
  local(nx, ny);
  argcheck(nargs, 7, "xpgfill");
  twodcheck(field, xvals, yvals);
  realcheck(field);
  realcheck(xvals);
  realcheck(yvals);
  nx = xvals.nc;
  ny = yvals.nr;
  fprintf(fname, " d\n");
  fprintf(fname, "%d %d %d %g %g %d\n", nw,nx,ny,low,high,ltype);
  for (ix in 1:nx) {
    fprintf(fname, "%g\n", xvals[ix]);
  }
  for (iy in 1:ny) {
    fprintf(fname, "%g\n", yvals[iy]);
  }
  for (iy in 1:ny) {
    for (ix in 1:nx) {
      fprintf(fname, "%g\n", field[iy;ix]);
    }
  }
};

// make a grid of numbers
xpggrid = function(nw, jx1, jy1, field, xvals, yvals)
{
  global(fname);
  local(ix, iy);
  local(nx, ny);
  local(x1, x2, y1, y2);
  argcheck(nargs, 6, "xpggrid");
  twodcheck(field, xvals, yvals);
  realcheck(field);
  realcheck(xvals);
  realcheck(yvals);
  nx = xvals.nc;
  ny = yvals.nr;
  jx = jx1;
  jy = jy1;
  if (jx <= 0) {jx = 1}
  if (jy <= 0) {jy = 1}
  fprintf(fname, " g\n");
  fprintf(fname, "%d %d %d %d %d\n", nw,nx,ny,jx,jy);
  maxval = max(max(abs(field)));
  x1 = 2 + int(jx/2);
  x2 = nx - 1;
  y1 = 2 + int(jy/2);
  y2 = ny - 1;
  for (ix in x1: x2: jx) {
    fprintf(fname, "%g\n", xvals[ix]);
  }
  for (iy in y1: y2: jy) {
    fprintf(fname, "%g\n", yvals[iy]);
  }
  for (iy in y1: y2: jy) {
    for (ix in x1: x2: jx) {
      if (maxval < 10.) {
	fprintf(fname, "%5.2f\n",field[iy;ix]);
	else if (maxval < 100.) {
	  fprintf(fname, "%5.1f\n",field[iy;ix]);
	  else
	    fprintf(fname, "%5.0f\n",field[iy;ix]);
	}
      }
    }
  }
};

// make a vector plot
xpgvector = function(nw, sx, sy, vx, vy, xvals, yvals)
{
  global(fname);
  local(ix, iy);
  local(nx, ny);
  local(x1, x2, y1, y2);
  argcheck(nargs, 7, "xpgvector");
  twodcheck(vx, xvals, yvals);
  twodcheck(vy, xvals, yvals);
  realcheck(xvals);
  realcheck(yvals);
  nx = xvals.nc;
  ny = yvals.nr;
  fprintf(fname, " v\n");
  fprintf(fname, "%d %d %d %g %g\n", nw,nx,ny,sx,sy);
  for (ix in 1:nx) {
    fprintf(fname, "%g\n", xvals[ix]);
  }
  for (iy in 1:ny) {
    fprintf(fname, "%g\n", yvals[iy]);
  }
  for (iy in 1:ny) {
    for (ix in 1:nx) {
      fprintf(fname, "%g %g\n", vx[iy;ix], vy[iy;ix]);
    }
  }
};

// draw a line
xpgline = function(nw, ltype, x, y)
{
  global(fname);
  local(i);
  argcheck(nargs, 4, "xpgline");
  num = onedcheck(x, y);
  realcheck(x);
  realcheck(y);
  fprintf(fname, " l\n");
  fprintf(fname, "%d %d %d\n", nw, ltype, num);
  for (i in 1:num) {
    fprintf(fname, "%g %g\n",x[i],y[i]);
  }
};

// make a mark
xpgmark = function(nw, mtype, x, y, size)
{
  global(fname);
  argcheck(nargs, 5, "xpgmark");
  realcheck(x);
  realcheck(y);
  fprintf(fname, " m\n");
  fprintf(fname, "%d %d %g %g %g\n", nw,mtype,x,y,size);
};

// make a label (the label should contain no newlines)
xpglabel = function(nw, sx, sy, text)
{
  global(fname);
  argcheck(nargs, 4, "xpglabel");
  fprintf(fname, " b\n");
  fprintf(fname, "%d %g %g\n", nw,sx,sy);
  fprintf(fname, "%s\n", text);
};

// make a perspective view
xpgpview = function(nw, xp, yp, zp, xf, yf, zf, zoom)
{
  global(fname);
  argcheck(nargs, 8, "xpgpview");
  fprintf(fname, " p\n");
  fprintf(fname, "%d %g %g %g %g %g %g %g\n", nw,xp,yp,zp,xf,yf,zf,zoom);
};

// draw 3-D structure in perspective
xpgpline = function(nw, ltype, x, y, z)
{
  global(fname);
  local(i, length);
  onedcheck(x, y);
  num = onedcheck(y, z);
  realcheck(x);
  realcheck(y);
  realcheck(z);
  fprintf(fname, " s\n");
  fprintf(fname, "%d %d %d\n", nw,ltype,num);
  for (i in 1:num) {
    fprintf(fname, "%g %g %g\n", x[i],y[i],z[i]);
  }
};

// make a label in perspective
xpgplabel = function(nw, sx, sy, sz, text)
{
  global(fname);
  argcheck(nargs, 5, "xpgplabel");
  fprintf(fname, " e\n");
  fprintf(fname, "%d %g %g %g\n", nw,sx,sy,sz);
  fprintf(fname, "%s\n", text);
};

// make 3-D axes in perspective
xpgpaxes = function(nw, sxl, sxh, xstr, xlabel, syl, syh, ystr, ylabel, szl, szh, zstr, zlabel)
{
  global(fname);
  argcheck(nargs, 13, "xpgpaxes");
  fprintf(fname, " h\n");
  fprintf(fname, "%d %g %g %g %g %g %g %g %g %g\n", nw,sxl,sxh,xstr,syl,syh,ystr,szl,szh,zstr);
  fprintf(fname, "%s\n", xlabel);
  fprintf(fname, "%s\n", ylabel);
  fprintf(fname, "%s\n", zlabel);
};

// make a surface plot of a 2-D function
xpgpsurface = function(nw, ltype, field, xvals, yvals)
{
  global(fname);
  local(nx, ny);
  argcheck(nargs, 5, "xpgpsurface");
  twodcheck(field, xvals, yvals);
  realcheck(field);
  realcheck(xvals);
  realcheck(yvals);
  nx = xvals.nc;
  ny = yvals.nr;
  fprintf(fname, " j\n");
  fprintf(fname, "%d %d %d %d\n", nw,nx,ny,ltype);
  for (ix in 1:nx) {
    fprintf(fname, "%g\n", xvals[ix]);
  }
  for (iy in 1:ny) {
    fprintf(fname, "%g\n", yvals[iy]);
  }
  for (iy in 1:ny) {
    for (ix in 1:nx) {
      fprintf(fname, "%g\n", field[iy;ix]);
    }
  }
};

// ******************************************************************
// **************** Convenience functions. **************************
// ******************************************************************

// simple convenience plot -- make a window and plot one or more lines
// defined by the columns of a matrix -- the 2nd, etc. columns against
// the first column
xpgplot = function(a, sxl, sxh, xtics, xl, syl, syh, ytics, yl)
{
  global(xpgopen,xpgclear,xpgpause,xpgclose,xpgwindow,xpgaxes,xpgline);
  local(icol,ncol,minx,maxx,miny,maxy);
  if (nargs != 1 && nargs != 9) {
    error("Usage: xpgplot(a[,sxl,sxh,xtics,xl,syl,syh,ytics,yl])");
  }
  if (a.nr < 2) {
    error("xpgplot: matrix must have at least two rows");
  }
  if (a.nc < 2) {
    error("xpgplot: matrix must have at least two columns");
  }
  ncol = a.nc;
  if (ncol > 17) {
    error("xpgplot: matrix must have 17 or fewer columns");
  }
  realcheck(a);
  xpgopen();
  xpgclear();
  xpgwindow(1, 0, 0, 15, 10);
  maxx = max(max(a[; 1]));
  minx = min(min(a[; 1]));
  maxy = max(max(a[; 2:ncol]));
  miny = min(min(a[; 2:ncol]));
  if (nargs == 1) {
    sxl = minx;
    sxh = maxx;
    xtics = 6;
    xl = "x-axis";
    syl = miny;
    syh = maxy;
    ytics = 6;
    yl = "y-axis";
  }
  xpgaxes(1, sxl, sxh, xtics, xl, syl, syh, ytics, yl);
  for (icol in 2:ncol) {
    xpgline(1, icol - 1, a[; 1], a[; icol]);
  }
  xpgpause();
  xpgclose();
};

// simple convenience plot -- make a contour plot with fill for
// a function of two variables, represented as a matrix
xpgplot2 = function(b, dz, zlo, zhi, xvals, xt, xl, yvals, yt, yl)
{
  global(xpgopen,xpgclear,xpgpause,xpgclose,xpgwindow,xpgaxes,xpgconto,xpgfill,xpglabel);
  local(nx,ny,minz,maxz,middle,sx,sy,label);
  if (nargs != 1 && nargs != 4 && nargs != 10) {
    error("Usage: xpgplot2(b[,dz,zlo,zhi[,xvals,xt,xl,yvals,yt,yl]])");
  }
  realcheck(b);
  nx = b.nc;
  ny = b.nr;
  if (nx < 2) {
    error("xpgplot2: must have more than 1 column in input");
  }
  if (ny < 2) {
    error("xpgplot2: must have more than 1 row in input");
  }
  if (nargs < 10) {
    xvals = [1:nx];
    yvals = [1:ny]';
    xt = 6;
    yt = 6;
    xl = "x-axis";
    yl = "y-axis";
  }
  if (nargs < 4) {
    minz = min(min(b));
    maxz = max(max(b));
    dz = (maxz - minz)/10;
    if (dz < 1.e-20) {
      dz = 1.e-20;
    }
    middle = int(.5*(maxz + minz)/dz + .5)*dz;
    zlo = middle - dz;
    zhi = middle + dz;
  }
  sx = xvals[1] + .05*(xvals[nx] - xvals[1]);
  sy = yvals[ny] - .05*(yvals[ny] - yvals[1]);
  xpgopen();
  xpgclear();
  xpgwindow(1,0,0,15,10);
  xpgaxes(1,xvals[1],xvals[nx],xt,xl,yvals[1],yvals[ny],yt,yl);
  xpgconto(1,dz,5,b,xvals,yvals);
  xpgfill(1,zlo,zhi,13,b,xvals,yvals);
  sprintf(label,"zlo, zhi, dz: %g %g %g",zlo,zhi,dz);
  xpglabel(1,sx,sy,label);
  xpgpause();
  xpgclose();
};

// simple convenience plot -- make a perspective view plot of a
// function of two variables, represented as a matrix
xpgplot3 = function(b, xl, yl, zl, xp, yp, zp, xf, yf, zf, zoom)
{
  global(xpgopen,xpgclear,xpgpause,xpgclose,xpgwindow,xpgpaxes,xpgpsurface);
  local(nx,ny,x,y,minz,maxz,zrange,xs,ys,zs);
  if (nargs != 1 && nargs != 11) {
    error("Usage: xpgplot3(b[,xl,yl,zl,xp,yp,zp,xf,yf,zf,zoom])");
  }
  realcheck(b);
  nx = b.nc;
  ny = b.nr;
  if (nx < 2) {
    error("xpgplot3: must have more than 1 column in input");
  }
  if (ny < 2) {
    error("xpgplot3: must have more than 1 row in input");
  }
  x = [0:nx - 1];
  y = [0:ny - 1]';
  maxz = max(max(b));
  minz = min(min(b));
  zrange = maxz - minz;
  if (zrange < 1.e-20) {
    zrange = 1.e-20;
  }
  xs = 1./nx;
  ys = 1./ny;
  zs = 0.2/zrange;
  if (nargs == 1) {
    xl = "X";
    yl = "Y";
    zl = "Z";
    xp = 5.;
    yp = -5.;
    zp = 2.;
    xf = 0.5;
    yf = 0.5;
    zf = 0.5*(minz + maxz)*zs;
    zoom = 8.;
  }
  xpgopen();
  xpgclear();
  xpgwindow(1, 0, 0, 15, 10);
  xpgpview(1, xp, yp, zp, xf, yf, zf, zoom);
  xpgpaxes(1, 0, nx, xs, xl, 0, ny, ys, yl, minz, maxz, zs, zl);
  xpgpsurface(1, 5, b, x, y);
  xpgpause();
  xpgclose();
};

// create a postscript file suitable for printing from the current
// metacode file
xpgps = function(outfile)
{
  global(xpgrender);
  argcheck(nargs, 1, "xpgps");
  xpgrender("ps", outfile);
};

// create an encapsulated postscript file from the current metacode file
xpgeps = function(outfile)
{
  global(xpgrender);
  argcheck(nargs, 1, "xpgeps");
  xpgrender("eps", outfile);
};
