#
# New plot.r for use with PLPLOT library.
# The help files for these functions are in
# misc/plhelp
#

# plplot.r

# This file is a part of RLaB ("Our"-LaB)
# Copyright (C) 1994  Ian R. Searle

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

# See the file ../COPYING

#
# If your system does not deal with Infs and NaNs well, then
# uncomment the following lines.
#

# static (isinf, isnan)
#
# isinf = function ( A ) { return (0); };
# isnan = function ( A ) { return (0); };

static (WIN)		# The static plot window structure
static (P)		# The active/current plot window

#
# Maintain the transformations for 3-D plots.
#

static (basex, basey, height)
basex = 2;
basey = 2;
height = 4;

#
# Static (private) functions. For use from within
# this file only.
#

static (create_plot_object)
static (check_plot_object)
static (xy_scales)
static (x_scales)
static (y_scales)
static (z_scales)
static (XYZ_scales)
static (list_scales)
static (list_sort)
static (hist_scales)
static (plot_matrix)
static (plot_list)
static (check_3d_list)
static (find_char)
static (get_style)
static (make_legend)
static (plhold_first)

#
# Defaults
#

static (grid_x_default, grid_y_default)
static (grid_3x_default, grid_3y_default, grid_3z_default)

grid_x_default = "bcnst";
grid_y_default = "bcnstv";
grid_3x_default = "bnstu";
grid_3y_default = "bnstu";
grid_3z_default = "bcdmnstuv";

static (subplot_f)
subplot_f = 0;

#
# Create the default plot-object.
# Initialize to all the default values
#

if (!exist (WIN)) 
{
  # Create the plot-object list
  WIN = <<>>;
}

create_plot_object = function ( N, nx, ny )
{
  if (!exist (N)) { N = 0; }
  
  pobj = <<>>;
  pobj.subplot = 0;		# The current subplot no.
  pobj.nplot = nx*ny;		# Total no. of plots on window
  
  pobj.fontld = 0;		# Loaded extended fonts?
  
  for (i in 1:(nx*ny))
  {
    pobj.style.[i] = "line";	# The type/style of plot to draw
    pobj.pstyle[i] = 1;	        # The point-style
    pobj.nbin[i] = 1j;          # The number of bins for a histogram
    pobj.width[i] = 1;		# The pen width for current plot
    pobj.font[i] = 1;		# The current font
    pobj.xlabel[i] = "";
    pobj.ylabel[i] = "";
    pobj.zlabel[i] = "";
    pobj.title[i] = "";
    pobj.orientation[i] = "portrait";
    pobj.desc.[i] = "default";		# The legend description
    pobj.gridx[i] =  grid_x_default;	# Plot axes style, 2D-X
    pobj.gridy[i] =  grid_y_default;	# Plot axes style, 2D-Y
    pobj.grid3x[i] = grid_3x_default;	# Plot axes style, 3D-X
    pobj.grid3y[i] = grid_3y_default;	# Plot axes style, 3D-Y
    pobj.grid3z[i] = grid_3z_default;	# Plot axes style, 3D-Z
    pobj.aspect[i] = 0;		        # Plot aspect style
    pobj.alt[i] = 60;
    pobj.az[i] = 45;
    
    pobj.xmin[i] = 1j;
    pobj.xmax[i] = 1j;
    pobj.ymin[i] = 1j;
    pobj.ymax[i] = 1j;
    pobj.zmin[i] = 1j;
    pobj.zmax[i] = 1j;
    
    pobj.page.xp = 0;
    pobj.page.yp = 0;
    pobj.xleng = 400;
    pobj.yleng = 300;
    pobj.xoff = 200;
    pobj.yoff = 200;

    for (j in 1:14) { pobj.color[i;j] = j; }       
    for (j in 1:8)  { pobj.lstyle[i;j] = j; }
  }
  
  #
  # Save the newly generated plot-object
  # in a list of plot-objects.
  
  WIN.[N] = pobj;
};

##############################################################################
#
# Check to make sure a plot-object exists. If one
# does not exist, create it.
#

check_plot_object = function ()
{
  if (length (WIN) == 0)
  {
    pstart();
    return 0;
  }
  return 1;
};

##############################################################################
#
# Set the current plot window
# Default value = 0
#

pwin = function ( N )
{
  check_plot_object ();
  if (!exist (N)) { N = 0; }
  
  # Check to make sure N is valid
  
  for (i in members (WIN))
  {
    if (N == strtod (i))
    {
      _plsstrm (N);
      return P = N;
    }
  }
  printf ("pwin: invalid argument, N = %i\n", N);
  printf ("      valid values are:\n");
  WIN?
};

##############################################################################
#
# Show the current plot-window, and the possibilities
#

showpwin = function ( all )
{
  if (length (WIN) == 0)
  {
    printf ("No plot objects\n");
    return 0;
  }
  
  printf ("Current plot-window is:\t\t%i\n", P);
  printf ("Available plot windows are:\t");
  for (i in members (WIN))
  {
    printf ("%s   ", i);
  }
  printf ("\n");

  if (exist (all))
  {
    for (i in members (WIN.[P]))
    {
      WIN.[P].[i]
    }
  }
};

getplot = function ( win_no )
{
  local (win_no)

  if (length (WIN) != 0)
  {
    if (!exist (win_no)) { win_no = P; }

    if (exist (WIN.[win_no]))
    {
      return (WIN.[win_no]);
    else
      return 0;
    }
  }
  return <<>>;
};


##############################################################################
#
# Set/start/select the plot device
#

pstart = function ( nx, ny, dev )
{
  if (!exist (nx)) { nx = 1; }
  if (!exist (ny)) { ny = 1; }
  if (!exist (dev)) { dev = "?"; }
  
  # Create the plot-object
  # First, figure out the index
  if (!exist (P))
  {
    P = 0;
  else
    P = P + 1;
  }
  
  create_plot_object (P, nx, ny);
  _plsstrm (P);
  
  #_plscolbg (255,255,255); # white
  # Default window size for X
  _plspage (0, 0, 400, 300, 200, 200);
  
  # Start up the plot-window
  _plstart (dev, nx, ny);
  
  _plwid (8);
  
  # Turn between plot pause off
  _plspause (0);
  _pltext ();
  
  return P;
};

##############################################################################
#
# Close a plot device. We must destroy the current plot-object
# And switch the output stream back to the default.
#

pclose = function ()
{
  if (size (WIN) > 1)
  {   
    #
    # Clear WIN.[P] and reset P to 1st plot-window
    #

    clear (WIN.[P]);
    _plend1 ();
    _plsstrm (strtod (members (WIN)[1]));
    P = strtod (members (WIN)[1]);
    return P;

  else if (size (WIN) == 1) {

    if (exist (WIN.[P])) 
    { 
      clear (WIN.[P]); 
      clear (P);
    }
    _plend1 ();
    return 1;

  else if (size (WIN) == 0) {

    return 0;

  } } }
};

##############################################################################
#
# Close ALL the plot-windows
#

pend = function ()
{
  _plend ();
  if (exist (WIN)) { clear (WIN); }
  if (exist (P)) { clear (P); }
  WIN = <<>>;
};

##############################################################################
#
# Change plot aspect ratio
#

plaspect = function ( aspect )
{
  check_plot_object ();
  i = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;
  if (!exist (aspect))
  {
    WIN.[P].aspect[i] = 0;
  else
    WIN.[P].aspect[i] = aspect;
  }
};

##############################################################################
#
# Change plot line style
#

plstyle = function ( style )
{
  check_plot_object ();
  i = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;

  if (exist (style)) 
  {
    if (class (style) == "string") 
    {
      WIN.[P].style.[i] = style;
    }
    return 1;
  }
  WIN.[P].style.[i] = "line";  
};

##############################################################################
#
# Get the right value of line-style
#

get_style = function ( STY, K )
{
  local (sty);
  sty = mod(K, STY.n);
  if(sty == 0) 
  { 
    sty = STY.n; 
  }
  return STY[sty];
};

##############################################################################
#
# Change fonts
#

plfont = function ( font )
{
  check_plot_object ();
  i = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;
  
  if (!exist (font)) { font = 1; }
  
  if (WIN.[P].fontld == 0)
  {
    _plfontld (1);
    WIN.[P].fontld = 1;
  }
  
  WIN.[P].font[i] = font;
  return P;
};

##############################################################################
#
# Change pen width
#

plwid = function ( width )
{
  check_plot_object ();
  i = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;
  
  if (!exist (width)) { width = 1; }
  WIN.[P].width[i] = width;
  return P;
};

##############################################################################
#
# Place some text on the plot
#

plptex = function ( text, x , y , dx , dy , just )
{
  if (!check_plot_object ()) 
  {
    printf ("Must use plot() before plptex()\n");
    return 0;
  }
  
  if (!exist (x)) { x = 0; }
  if (!exist (y)) { y = 0; }
  if (!exist (dx)) { dx = abs(x)+1; }
  if (!exist (dy)) { dy = 0; }
  if (!exist (just)) { just = 0; }
  
  _plptex (x, y, dx, dy, just, text);
};

##############################################################################
#
# Set up the viewing altitude for 3-D plots
#

plalt = function ( ALT )
{
  check_plot_object ();
  i = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;

  if (exist (ALT)) 
  { 
    WIN.[P].alt[i] = ALT; 
  else
    WIN.[P].alt[i] = 60;
  }
  return P;
};

##############################################################################
#
# Set the viewing azimuth for 3-D plots
#

plaz = function ( AZ )
{
  check_plot_object ();
  i = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;

  if (exist (AZ)) 
  { 
    WIN.[P].az[i] = AZ; 
  else
    WIN.[P].az[i] = 45;
  }
  return P;
};

##############################################################################
#
# Find a character in a string
#

find_char = function ( str , char )
{  
  tmp = strsplt (str);
  for (i in 1:tmp.n)
  {
    if (tmp[i] == char) 
    {
      return i;
    }
  }
  return 0;
};

##############################################################################
#
# Sort list element names/labels by numeric order, then string order.
#

list_sort = function ( L )
{
  tl = <<>>;
  j = k = 1;

  for (i in members (L))
  {
    if (!isnan (strtod (i)))
    {
      num[j] = i;
      j++;
      else
      char[k] = i;
      k++;
    }
  }
  
  # Sort the numeric labels
  
  if (exist (num))
  {
    num = sort (strtod (num)).val;
    tl.num = num;
  }

  if (exist (char))
  {
    tl.char = char;
  }

  return tl;
};

##############################################################################
#
# Set the subplot, this overides the action in plot().
#

subplot = function ( sub )
{
  check_plot_object ();
  
  if (!exist (sub))
  {
    subplot_f = 0;
    _pladv (0);
  else
    if (sub > WIN.[P].nplot)
    {
      error ("Current window does not have this many subplots");
    }
    if (sub > 0)
    {
      WIN.[P].subplot = sub - 1;
      subplot_f = 1;
      _pladv (sub);
    else
      if (sub == 0)
      {
        # Do not advance, stay at current subplot
        WIN.[P].subplot = WIN.[P].subplot - 1;
        subplot_f = 1;
      }
    }
  }
};

##############################################################################
#
# Plot the columns of a matrix (X-Y plot).
#
##############################################################################

plot = function ( data, key, textf )
{
  check_plot_object ();
  
  p = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;	# The current index
  if (!exist (key)) { key = 1; }
  if (!exist (textf)) { textf = 1; }

  #
  # Draw the graph
  # Step through the matrix plotting
  # each column versus the 1st
  #
  
  if (class (data) == "num")
  {
    #
    # Set up the plot basics
    #

    if (abs (key) > data.nc)
    {
      error_1 ("plot: KEY argument > M.nc");
    }
    
    _plgra ();
    _plcol (1);
    _pllsty (1);
    _plfont (WIN.[P].font[p]);
    _plwid (WIN.[P].width[p]);

    if (!subplot_f) 
    {
      _pladv (0);        # Advance 1 subplot
    else
      subplot_f = 0;     # The user has set the subplot
    }

    if (WIN.[P].aspect[p] != 0)
    {
      _plvasp (WIN.[P].aspect[p]);
    else
      _plvsta ();
    }
    
    #
    # Compute scale limits
    #

    k = find ((1:data.nc) != abs (key));
    if (key > 0)
    {
      if (data.nc != 1)
      {
	x_scales ( real(data)[;key], p, xmin, xmax );
	y_scales ( real(data)[;k],   p, ymin, ymax );
      else
	x_scales ( (1:data.nr)', p, xmin, xmax );
	y_scales ( real(data),   p, ymin, ymax );
      }
    else if (key < 0) {
      x_scales ( real(data)[;k],   p, xmin, xmax );
      y_scales ( real(data)[;abs(key)], p, ymin, ymax );
    else
      x_scales ( (1:data.nr)', p, xmin, xmax );
      y_scales ( real(data),   p, ymin, ymax );
    } }

    _plwind (xmin, xmax, ymin, ymax);
    _plbox (WIN.[P].gridx[p], 0, 0, WIN.[P].gridy[p], 0, 0);
    if (plot_matrix ( data, key, p, 0, xmin, xmax, ymin, ymax, ymax-ymin ) < 0) 
    {
      return -1;
    }
    
    else if (class (data) == "list") {
      
      _plgra ();
      _plcol (1);
      _pllsty (1);
      _plfont (WIN.[P].font[p]);
      _plwid (WIN.[P].width[p]);

      list_scales ( data, key, p, xmin, xmax, ymin, ymax );

      if (!subplot_f) 
      {
	_pladv (0);        # Advance 1 subplot
      else
	subplot_f = 0;     # The user has set the subplot
      }

      if (WIN.[P].aspect[p] != 0)
      {
	_plvasp (WIN.[P].aspect[p]);
      else
       _plvsta ();
      }
      
      _plwind (xmin, xmax, ymin, ymax);
      _plbox (WIN.[P].gridx[p], 0, 0, WIN.[P].gridy[p], 0, 0);
      if (plot_list ( data, key, p, xmin, xmax, ymin, ymax ) < 0) 
      { 
	return -1;
      }

    else
      error ("plot: un-acceptable argument");
  } }

  _pllsty (1);
  _plcol (1);
  _pllab (WIN.[P].xlabel[p], WIN.[P].ylabel[p], WIN.[P].title[p]);  
  _plflush ();
  if (textf)
  {
    _pltext ();
  }
  
  #
  # Increment the plot no. so that next time
  # we use the correct settings.
  #
  
  WIN.[P].subplot = WIN.[P].subplot + 1;
  return P;
};

#
# plhold:
# Plot some data, and "hold" on for more.
# Plot the data, setting up the plot as usual the first time.
# On subsequent plots do not do any setup, just plot some
# more. plhold_off must be called to finish up.
#

plhold_first = 1;	# True (1) if plhold() has NOT been used.
                        # Or if plhold_off() has been used.
			# False (0) if plhold is in use

static (hxmin, hxmax, hymin, hymax)

plhold = function ( data, key )
{
  check_plot_object ();
  
  p = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;	# The current index
  if (!exist (key)) { key = 1; }
  if (abs (key) > data.nc)
  {
    error_1 ("plot: KEY argument > M.nc");
  }
  
  if (plhold_first)
  {
    if (class (data) == "num")
    {
      #
      # Do the setup ONCE
      #
      hxmin = hxmax = hymin = hymax = 0;
      _plgra ();
      _plcol (1);
      _pllsty (1);
      _plfont (WIN.[P].font[p]);
      _plwid (WIN.[P].width[p]);

      if (!subplot_f) 
      {
	_pladv (0);        # Advance 1 subplot
      else
	subplot_f = 0;     # The user has set the subplot
      }

      if (WIN.[P].aspect[p] != 0)
      {
	_plvasp (WIN.[P].aspect[p]);
      else
	_plvsta ();
      }

      xy_scales ( real(data), p, hxmin, hxmax, hymin, hymax );
      
      k = find ((1:data.nc) != abs (key));
      if (key > 0)
      {
	x_scales ( real(data)[;key], p, xmin, xmax );
	if (data.nc != 1)
	{
	  y_scales ( real(data)[;k],   p, ymin, ymax );
        else
	  y_scales ( (1:data.nr)',   p, ymin, ymax );
	}
      else if (key < 0) {
	x_scales ( real(data)[;k],   p, xmin, xmax );
	y_scales ( real(data)[;abs(key)], p, ymin, ymax );
      else
	x_scales ( (1:data.nr)', p, xmin, xmax );
	y_scales ( real(data),   p, ymin, ymax );
      } }

      _plwind (hxmin, hxmax, hymin, hymax);
      _plbox (WIN.[P].gridx[p], 0, 0, WIN.[P].gridy[p], 0, 0);
      _pllab (WIN.[P].xlabel[p], WIN.[P].ylabel[p], WIN.[P].title[p]);
    else
      error ("plot: un-acceptable argument");
    }
    plhold_first = 0;
  }
  
  if (plot_matrix ( data, key, p, 0, hxmin, hxmax, hymin, hymax, hymax-hymin ) < 0) 
  { 
    return -1; 
  }

  _plcol (1);
  _plflush ();
  _pltext ();

  return P;
};

##############################################################################
#
# Clean up the plotting environment and get ready
# for normal interactive usage.
#

plhold_off = function ( )
{
  p = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;	# The current index
  plhold_first = 1;  
  _plcol (1);
  _plflush ();
  _pltext ();
  WIN.[P].subplot = WIN.[P].subplot + 1;
  return P;
};

##############################################################################
#
# Plot a 3-Dimensional graph. The data is composed in a list, with
# elements `x', `y', and `z'. x and y are single-dimension arrays
# (row or column matrices), and z is a two-dimensional array. The
# array z, is a function of x and y: z = f(x,y). Thus, the values in
# the array x can be thought of a "row-labels", and the values of y
# can be thought of as "column-lables" for the 2-dimensioal array z.
#
# At present plot3 can plot 3 distinct lists. Each list may have
# different X, Y, and Z scales.
#
##############################################################################

plot3 = function ( L31, L32, L33 )
{
  check_plot_object ();

  #
  # 1st check list contents
  #
  
  if (exist (L31)) { check_3d_list (L31); }
  if (exist (L32)) { check_3d_list (L32); }
  if (exist (L33)) { check_3d_list (L33); }
  
  p = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;	# The current index

  #
  # Figure out the scale limits. 
  # Needs improvement!
  #
  
  xmin = xmax = ymin = ymax = zmin = zmax = 0;
  if (exist (L31)) 
  {
    XYZ_scales (L31.x, L31.y, L31.z, p, Xmin, Xmax, Ymin, Ymax, Zmin, Zmax);
    xmin = Xmin; xmax = Xmax;
    ymin = Ymin; ymax = Ymax;
    zmin = Zmin; zmax = Zmax;
  }
  if (exist (L32)) 
  {
    XYZ_scales (L32.x, L32.y, L32.z, p, Xmin, Xmax, Ymin, Ymax, Zmin, Zmax);
    if (Xmin < xmin) { xmin = Xmin; } if (Xmax > xmax) { xmax = Xmax; }
    if (Ymin < ymin) { ymin = Ymin; } if (Ymax > ymax) { ymax = Ymax; }
    if (Zmin < zmin) { zmin = Zmin; } if (Zmax > zmax) { zmax = Zmax; }
  }   
  if (exist (L33)) 
  {
    XYZ_scales (L33.x, L33.y, L33.z, p, Xmin, Xmax, Ymin, Ymax, Zmin, Zmax);
    if (Xmin < xmin) { xmin = Xmin; } if (Xmax > xmax) { xmax = Xmax; }
    if (Ymin < ymin) { ymin = Ymin; } if (Ymax > ymax) { ymax = Ymax; }
    if (Zmin < zmin) { zmin = Zmin; } if (Zmax > zmax) { zmax = Zmax; }
  }
  
  _plgra ();
  _plcol (1);
  _pllsty (1);
  _plfont (WIN.[P].font[p]);
  _plwid (WIN.[P].width[p]);
  
  # basex = 2; basey = 2; height = 4;
  xmin2d = -2.0; xmax2d = 2.0;
  ymin2d = -3.0; ymax2d = 5.0;
  
  _plenv (xmin2d, xmax2d, ymin2d, ymax2d, 0, -2);
  _plw3d (basex, basey, height, xmin, xmax, ymin, ymax, ...
          zmin, zmax, WIN.[P].alt[p], WIN.[P].az[p]);
  _plbox3 (WIN.[P].grid3x[p], WIN.[P].xlabel[p], 0, 0, ...
           WIN.[P].grid3y[p], WIN.[P].ylabel[p], 0, 0, ...
           WIN.[P].grid3z[p], WIN.[P].zlabel[p], 0, 0);
  _plmtex ("t", 1.0, 0.5, 0.5, WIN.[P].title[p]);

  if (exist (L31))
  {
    if (find_char (WIN.[P].grid3x[p], "l"))
    { x = log10 (real (L31.x)); else x = real (L31.x); }
    if (find_char (WIN.[P].grid3y[p], "l"))
    { y = log10 (real (L31.y)); else y = real (L31.y); }
    if (find_char (WIN.[P].grid3z[p], "l"))
    { z = log10 (real (L31.z)); else z = real (L31.z); }
    
    _plcol (2);
    _plot3d (x, y, z, L31.x.n, L31.y.n, 3, 0);
  }
  if (exist (L32))
  {
    if (find_char (WIN.[P].grid3x[p], "l"))
    { x = log10 (real (L32.x)); else x = real (L32.x); }
    if (find_char (WIN.[P].grid3y[p], "l"))
    { y = log10 (real (L32.y)); else y = real (L32.y); }
    if (find_char (WIN.[P].grid3z[p], "l"))
    { z = log10 (real (L32.z)); else z = real (L32.z); }
    
    _plcol (3);
    _pllsty (2);
    _plot3d (x, y, z, L32.x.n, L32.y.n, 3, 0);
  }
  if (exist (L33)) 
  {
    if (find_char (WIN.[P].grid3x[p], "l"))
    { x = log10 (real (L33.x)); else x = real (L33.x); }
    if (find_char (WIN.[P].grid3y[p], "l"))
    { y = log10 (real (L33.y)); else y = real (L33.y); }
    if (find_char (WIN.[P].grid3z[p], "l"))
    { z = log10 (real (L33.z)); else z = real (L33.z); }
    
    _plcol (4);
    _pllsty (3);
    _plot3d (x, y, z, L33.x.n, L33.y.n, 3, 0);
  }
  
  _plflush ();
  _pltext ();
  
  #
  # Increment the plot no. so that next time
  # we use the correct settings.
  #
  
  WIN.[P].subplot = WIN.[P].subplot + 1;
  
  return P;
};

##############################################################################
#
# Plot a 3-Dimensional graph. The data is composed in a list, with
# elements `x', `y', and `z'. x and y are single-dimension arrays
# (row or column matrices), and z is a two-dimensional array. The
# array z, is a function of x and y: z = f(x,y). Thus, the values in
# the array x can be thought of a "row-labels", and the values of y
# can be thought of as "column-lables" for the 2-dimensioal array z.
#
# At present plmesh can plot 3 distinct lists. Each list may have
# different X, Y, and Z scales.
#
##############################################################################

plmesh = function ( L31, L32, L33 )
{
  check_plot_object ();
  
  #
  # 1st check list contents
  #
  
  if (exist (L31)) { check_3d_list (L31); }
  if (exist (L32)) { check_3d_list (L32); }
  if (exist (L33)) { check_3d_list (L33); }
  
  p = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;	# The current index

  #
  # Figure out the scale limits. 
  # Needs improvement!
  #
  
  xmin = xmax = ymin = ymax = zmin = zmax = 0;
  if (exist (L31)) 
  {
    XYZ_scales (L31.x, L31.y, L31.z, p, Xmin, Xmax, Ymin, Ymax, Zmin, Zmax);
    xmin = Xmin; xmax = Xmax;
    ymin = Ymin; ymax = Ymax;
    zmin = Zmin; zmax = Zmax;
  }
  if (exist (L32)) 
  {
    XYZ_scales (L32.x, L32.y, L32.z, p, Xmin, Xmax, Ymin, Ymax, Zmin, Zmax);
    if (Xmin < xmin) { xmin = Xmin; } if (Xmax > xmax) { xmax = Xmax; }
    if (Ymin < ymin) { ymin = Ymin; } if (Ymax > ymax) { ymax = Ymax; }
    if (Zmin < zmin) { zmin = Zmin; } if (Zmax > zmax) { zmax = Zmax; }
  }   
  if (exist (L33)) 
  {
    XYZ_scales (L33.x, L33.y, L33.z, p, Xmin, Xmax, Ymin, Ymax, Zmin, Zmax);
    if (Xmin < xmin) { xmin = Xmin; } if (Xmax > xmax) { xmax = Xmax; }
    if (Ymin < ymin) { ymin = Ymin; } if (Ymax > ymax) { ymax = Ymax; }
    if (Zmin < zmin) { zmin = Zmin; } if (Zmax > zmax) { zmax = Zmax; }
  }

  _plgra ();
  _plcol (1);
  _pllsty (1);
  _plfont (WIN.[P].font[p]);
  _plwid (WIN.[P].width[p]);
  
  # basex = 2; basey = 2; height = 4;
  xmin2d = -2.0; xmax2d = 2.0;
  ymin2d = -3.0; ymax2d = 5.0;
  
  _plenv (xmin2d, xmax2d, ymin2d, ymax2d, 0, -2);
  _plw3d (basex, basey, height, xmin, xmax, ymin, ymax, ...
          zmin, zmax, WIN.[P].alt[p], WIN.[P].az[p]);
  _plbox3 (WIN.[P].grid3x[p], WIN.[P].xlabel[p], 0, 0, ...
           WIN.[P].grid3y[p], WIN.[P].ylabel[p], 0, 0, ...
           WIN.[P].grid3z[p], WIN.[P].zlabel[p], 0, 0);
  _plmtex ("t", 1.0, 0.5, 0.5, WIN.[P].title[p]);

  if (exist (L31))
  {
    if (find_char (WIN.[P].grid3x[p], "l"))
    { x = log10 (real (L31.x)); else x = real (L31.x); }
    if (find_char (WIN.[P].grid3y[p], "l"))
    { y = log10 (real (L31.y)); else y = real (L31.y); }
    if (find_char (WIN.[P].grid3z[p], "l"))
    { z = log10 (real (L31.z)); else z = real (L31.z); }
    
    _plcol (2);
    _plmesh (x, y, z, L31.x.n, L31.y.n, 3);
  }
  if (exist (L32))
  {
    if (find_char (WIN.[P].grid3x[p], "l"))
    { x = log10 (real (L32.x)); else x = real (L32.x); }
    if (find_char (WIN.[P].grid3y[p], "l"))
    { y = log10 (real (L32.y)); else y = real (L32.y); }
    if (find_char (WIN.[P].grid3z[p], "l"))
    { z = log10 (real (L32.z)); else z = real (L32.z); }
    
    _plcol (3);
    _pllsty (2);
    _plmesh (x, y, z, L32.x.n, L32.y.n, 3);
  }
  if (exist (L33)) 
  {
    if (find_char (WIN.[P].grid3x[p], "l"))
    { x = log10 (real (L33.x)); else x = real (L33.x); }
    if (find_char (WIN.[P].grid3y[p], "l"))
    { y = log10 (real (L33.y)); else y = real (L33.y); }
    if (find_char (WIN.[P].grid3z[p], "l"))
    { z = log10 (real (L33.z)); else z = real (L33.z); }
    
    _plcol (4);
    _pllsty (3);
    _plmesh (x, y, z, L33.x.n, L33.y.n, 3);
  }

  _plflush ();
  _pltext ();
  
  #
  # Increment the plot no. so that next time
  # we use the correct settings.
  #
  
  WIN.[P].subplot = WIN.[P].subplot + 1;
  
  return P;
};

##############################################################################
#
# Plot contours. The data is composed in a list, with
# elements `x', `y', and `z'. x and y are single-dimension arrays
# (row or column matrices), and z is a two-dimensional array. The
# array z, is a function of x and y: z = f(x,y). Thus, the values in
# the array x can be thought of a "row-labels", and the values of y
# can be thought of as "column-lables" for the 2-dimensioal array z.
#
##############################################################################

plcont = function ( CL )
{
  check_plot_object ();
  
  #
  # 1st check list contents
  #
  
  if (exist (CL)) { check_3d_list (CL); }

  p = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;	# The current index
  
  #
  # Figure out the scale limits. 
  # Needs improvement!
  #

  xmin = xmax = ymin = ymax = zmin = zmax = 0;
  if (exist (CL)) 
  {
    XYZ_scales (CL.x, CL.y, CL.z, p, Xmin, Xmax, Ymin, Ymax, Zmin, Zmax);
    if (Xmin < xmin) { xmin = Xmin; } if (Xmax > xmax) { xmax = Xmax; }
    if (Ymin < ymin) { ymin = Ymin; } if (Ymax > ymax) { ymax = Ymax; }
    if (Zmin < zmin) { zmin = Zmin; } if (Zmax > zmax) { zmax = Zmax; }
  }

  _plgra ();
  _plcol (1);
  _pllsty (1);
  _plfont (WIN.[P].font[p]);
  _plwid (WIN.[P].width[p]);

  #
  # Set up the 1st viewport for drawing the plot.
  #

  if (!subplot_f) 
  {
    _pladv (0);        # Advance 1 subplot
  else
    subplot_f = 0;     # The user has set the subplot
  }

  _plvpas (0.15, 0.75, 0.15, 0.85, WIN.[P].aspect[p]);
  # _plvpor (0.15, 0.75, 0.15, 0.85);
  _plwind (xmin, xmax, ymin, ymax);
  _plbox (WIN.[P].gridx[p], 0, 0, WIN.[P].gridy[p], 0, 0);

  # Convert the data to log data if necessary.
  if (find_char (WIN.[P].gridx[p], "l"))
  { x = log10 (real (CL.x)); else x = real (CL.x); }
  if (find_char (WIN.[P].gridy[p], "l"))
  { y = log10 (real (CL.y)); else y = real (CL.y); }
  z = real (CL.z);
  
  if (exist (CL.clevel))
  {
    clevel = CL.clevel;
  else
    clevel = linspace(zmin, zmax, 10);
  }

  #
  # Draw the contours
  #

  l = 1;
  for (i in 1:clevel.n)
  {
    k = mod (i-1, 14) + 1;
    j = mod (i-1, 8) + 1;
    _pllsty(j);
    _plcol (1+k);
    if (_plcont (x, y, z, 1, CL.x.n, 1, CL.y.n, clevel[i]))
    {
      llevel[l] = clevel[i];
      l = l + 1;
    }
  }

  #
  # Reset color and draw the labels.
  #

  _plcol (1);
  _pllab (WIN.[P].xlabel[p], WIN.[P].ylabel[p], WIN.[P].title[p]);  

  #
  # Draw the contour legend. Use a new viewport to the right
  # of the contour plot.
  #

  #_plvpas (0.75, 1.0, 0.15, 0.85, WIN.[P].aspect[p]);
  _plvpor (0.75, 1.0, 0.15, 0.85);
  _plwind (0, 1, 0, 1);

  v = 1 - 1/(2*llevel.n);

  for (i in 1:llevel.n)
  {
    xl = [0.1, 0.2, 0.3]';
    yl = [v, v, v]';
    v = v - 1/llevel.n;

    k = mod (i-1, 14) + 1;
    j = mod (i-1, 8) + 1;

    _plcol (1+k);
    _pllsty (j);

    _plline (3, xl, yl);
    sprintf (stmp, "%.2g", llevel[i]);
    plptex (stmp, xl[3]+.1, yl[3], , , 0);
  }

  # Flush  and go back to text mode.
  _plflush ();
  _pltext ();
  
  #
  # Increment the plot no. so that next time
  # we use the correct settings.
  #
  
  WIN.[P].subplot = WIN.[P].subplot + 1;
  
  return P;
};

##############################################################################
#
# Plot 3-D lines, etc...
#

pl3d = function ( X, Y, Z, BR )
{
  local (X, Y, Z, BR)
  check_plot_object ();
  
  p = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;	# The current index

  #
  # Some basic checks
  #

  if ((N = X.n) != Y.n) { error ("pl3d: X and Y must have same length"); }
  if (N != Z.n) { error ("pl3d: X and Z must have same length"); }

  if (!exist (BR)) { BR = N; }
  if (mod (N, BR) != 0) { error ("pl3d: X.n must be divisible by BR"); }
  iBR = int (N / BR);
  if (iBR == 1) { k = N; else k = BR; }

  #
  # Figure out the scale limits. 
  # Needs improvement!
  #
  
  xmin = xmax = ymin = ymax = zmin = zmax = 0;
  XYZ_scales (X, Y, Z, p, xmin, xmax, ymin, ymax, zmin, zmax);

  _plgra ();
  _plcol (1);
  _pllsty (1);
  _plfont (WIN.[P].font[p]);
  _plwid (WIN.[P].width[p]);
  
  if (!subplot_f) 
  {
    _pladv (0);        # Advance 1 subplot
    else
    subplot_f = 0;     # The user has set the subplot
  }

  if (find_char (WIN.[P].grid3x[p], "l"))
  { X = log10 (real (X)); else X = real (X); }
  if (find_char (WIN.[P].grid3y[p], "l"))
  { Y = log10 (real (Y)); else Y = real (Y); }
  if (find_char (WIN.[P].grid3z[p], "l"))
  { Z = log10 (real (Z)); else Z = real (Z); }
    
  # basex = 2; basey = 2; height = 4;
  xmin2d = -2.0; xmax2d = 2.0;
  ymin2d = -3.0; ymax2d = 5.0;
  
  _plvasp (WIN.[P].aspect[p]);
  _plwind (xmin2d, xmax2d, ymin2d, ymax2d);
  _plw3d (basex, basey, height, xmin, xmax, ymin, ymax, ...
          zmin, zmax, WIN.[P].alt[p], WIN.[P].az[p]);
  _plbox3 (WIN.[P].grid3x[p], WIN.[P].xlabel[p], 0, 0, ...
           WIN.[P].grid3y[p], WIN.[P].ylabel[p], 0, 0, ...
           WIN.[P].grid3z[p], WIN.[P].zlabel[p], 0, 0);
  _plmtex ("t", 1.0, 0.5, 0.5, WIN.[P].title[p]);

  _plcol (2);
  for (i in 1:iBR)
  {
    j = [(i-1)*k+1:i*k];
    _plline3 (k, X[j], Y[j], Z[j]);
  }
  _plflush ();
  _pltext ();
  
  #
  # Increment the plot no. so that next time
  # we use the correct settings.
  #
  
  WIN.[P].subplot = WIN.[P].subplot + 1;
  
  return P;
};

##############################################################################
#
# error bar plot
#

plerry = function (x, y, y_low, y_high)
{
  check_plot_object ();
  
  p = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;	# The current index
  WIN.[P].desc.[p] = 1j;    // use own legend
  
  if (x.nr != y.nr || x.nr != y_low.nr || x.nr != y_high.nr) 
  {
    error(" Size inconsistent in plerry.");
  }
  _plgra ();
  _plcol (1);
  _pllsty (1);
  _plfont (WIN.[P].font[p]);
  _plwid (WIN.[P].width[p]);
  xy_scales ( real([x,y,y_low,y_high]), p, xmin, xmax, ymin, ymax );
  
  if (!subplot_f) 
  {
    _pladv (0);        # Advance 1 subplot
  else
    subplot_f = 0;     # The user has set the subplot
  }

  if (WIN.[P].aspect[p] != 0)
  {
    _plvasp (WIN.[P].aspect[p]);
  else
    _plvsta ();
  }

  _plwind (xmin, xmax, ymin, ymax);
  _plbox (WIN.[P].gridx[p], 0, 0, WIN.[P].gridy[p], 0, 0);

  if (plot_matrix ( [x,y], p, 0, xmin, xmax, ymin, ymax, ymax-ymin ) < 0) 
  { 
    return -1; 
  }

  _plcol (3);
  _plerry(x.nr, x, y_low, y_high);
  _plcol (1);
  _pllab (WIN.[P].xlabel[p], WIN.[P].ylabel[p], WIN.[P].title[p]);  
  _plflush ();
  _pltext ();
  
  #
  # Increment the plot no. so that next time
  # we use the correct settings.
  #
  
  WIN.[P].subplot = WIN.[P].subplot + 1;
  return P;      
};

##############################################################################
#
# Plot a Histogram(s), from the columns of a matrix.
#
##############################################################################

plhist = function ( M , nbin )
{
  check_plot_object ();
  
  if (!exist (nbin)) { nbin = 10; }
  
  p = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;	# The current index
  np = M.nr;
  
  # Compute max/min values of data
  
  ymin = min (min (real (M)));
  ymax = max (max (real (M)));
  
  #
  # Check computed scale limits against user's
  #
  
  if (WIN.[P].ymin[p] != 1j) { ymin = WIN.[P].ymin[p]; }
  if (WIN.[P].ymax[p] != 1j) { ymax = WIN.[P].ymax[p]; }

  _plgra ();
  _plcol (1);
  _plfont (WIN.[P].font[p]);
  _plwid (WIN.[P].width[p]);
  
  for (i in 1:M.nc) 
  { 
    hscale[i] = hist_scales (M[;i], nbin);
  }  

  if (!subplot_f) {
    _pladv (0);        # Advance 1 subplot
  else
    subplot_f = 0;     # The user has set the subplot
  }

  if (WIN.[P].aspect[p] != 0)
  {
    _plvasp (WIN.[P].aspect[p]);
  else
    _plvsta ();
  }

  _plwind (ymin, ymax, 0, max (hscale));
  _plbox (WIN.[P].gridx[p], 0, 0, WIN.[P].gridy[p], 0, 0);

  v = max (hscale);
  xmax = ymax;
  for (i in 1:M.nc)
  {
    k = mod (i, 14) + 1;
    _plcol (WIN.[P].color[p;k]);
    _plhist (np, real(M[;i]), ymin, ymax, nbin, 1);
    
    if (!any (any (WIN.[P].desc.[p] == 1j)))
    {
      # Use the default if necessary
      if (WIN.[P].desc.[p][1] == "default") 
      {
	desc = "c"+num2str(i);
      else if (WIN.[P].desc.[p].n >= i) {
	desc = WIN.[P].desc.[p][i];
      else
	# Not sure what to do, user has messed up.
	desc = "";
      } }

      v = v - max(hscale)/11;
      xl = (ymax-ymin)*[10.5/12, 11/12, 11.5/12]' + ymin;
      yl = [v, v, v]';

      _plline (3, xl, yl);
      plptex(desc, xl[1]-(ymax-ymin)/25, yl[3], , , 1);
    }
  }

  _plcol (1);
  _pllab (WIN.[P].xlabel[p], WIN.[P].ylabel[p], WIN.[P].title[p]);  
  _plflush ();
  _pltext ();

  #
  # Increment the plot no. so that next time
  # we use the correct settings.
  #
  
  WIN.[P].subplot = WIN.[P].subplot + 1;

  return 1;
};

##############################################################################
#
# Various support functions for the WIN list
#
##############################################################################

#
# Replot
#

replot = function ( )
{
  check_plot_object ();
  _replot ();
};

##############################################################################
#
# Set the X-axis label
#

xlabel = function ( xstr )
{
  check_plot_object ();
  if (!exist (xstr)) { xstr = ""; }
  i = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;
  WIN.[P].xlabel[i] = xstr;
};

##############################################################################
#
# Set the Y-axis label
#

ylabel = function ( xstr )
{
  check_plot_object ();
  if (!exist (xstr)) { xstr = ""; }
  i = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;
  WIN.[P].ylabel[i] = xstr;
};

##############################################################################
#
# Set the Z-axis label
#

zlabel = function ( xstr )
{
  check_plot_object ();
  if (!exist (xstr)) { xstr = ""; }
  i = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;
  WIN.[P].zlabel[i] = xstr;
};

##############################################################################
#
# Set the plot-title
#

ptitle = function ( xstr )
{
  check_plot_object ();
  if (!exist (xstr)) { xstr = ""; }
  p = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;	# The current index
  WIN.[P].title[p] = xstr;
};

##############################################################################
#
# Set the scale limits.
#

plimits = function ( xmin, xmax, ymin, ymax, zmin, zmax )
{
  check_plot_object ();
  i = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;

  if (exist (xmin)) 
  {
    WIN.[P].xmin[i] = xmin;
  else
    WIN.[P].xmin[i] = 1j;
  }
  if (exist (xmax)) 
  { 
    WIN.[P].xmax[i] = xmax;
  else
    WIN.[P].xmax[i] = 1j;
  }

  if (exist (ymin)) 
  {
    WIN.[P].ymin[i] = ymin;
  else
    WIN.[P].ymin[i] = 1j;
  }
  if (exist (ymax)) 
  {
    WIN.[P].ymax[i] = ymax;
  else
    WIN.[P].ymax[i] = 1j;
  }

  if (exist (zmin)) 
  {
    WIN.[P].zmin[i] = zmin;
  else
    WIN.[P].zmin[i] = 1j;
  }
  if (exist (zmax)) 
  {
    WIN.[P].zmax[i] = zmax;
  else
    WIN.[P].zmax[i] = 1j;
  }
};

##############################################################################
#
# Set 2-D grid styles. A not-so-friendly interface.
#

plgrid = function ( sty_x, sty_y )
{
  check_plot_object ();
  i = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;

  if (exist (sty_x)) 
  { 
    if (class (sty_x) == "string")
    {
      WIN.[P].gridx[i] = sty_x;
    else
      error ("plgrid: requires string argument GRID_STY_X");
    }
  else
    WIN.[P].gridx[i] = grid_x_default;
  }
  if (exist (sty_y)) 
  { 
    if (class (sty_y) == "string")
    {
      WIN.[P].gridy[i] = sty_y;
    else
      error ("plgrid: requires string argument GRID_STY_Y");
    }
  else
    WIN.[P].gridy[i] = grid_y_default;
  }
};

##############################################################################
#
# Set 3-D grid (axis) styles
#

plgrid3 = function ( sty_x, sty_y, sty_z )
{  
  check_plot_object ();
  i = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;
  if (exist (sty_x)) 
  { 
    if (class (sty_x) == "string")
    {
      WIN.[P].grid3x[i] = sty_x;
    else
      error ("plgrid3: requires string argument GRID_STY_X");
    }
  else
    WIN.[P].grid3x[i] = grid_3x_default;
  }
  if (exist (sty_y)) 
  { 
    if (class (sty_y) == "string")
    {
      WIN.[P].grid3y[i] = sty_y;
    else
      error ("plgrid3: requires string argument GRID_STY_Y");
    }
  else
    WIN.[P].grid3y[i] = grid_3y_default;
  }
  if (exist (sty_z)) 
  { 
    if (class (sty_z) == "string")
    {
      WIN.[P].grid3z[i] = sty_z;
    else
      error ("plgrid3: requires string argument GRID_STY_Z");
    }
  else
    WIN.[P].grid3z[i] = grid_3z_default;
  }
};

##############################################################################
#
# A friendlier interface to changing 2-D grid/axis
# styles.
#

plaxis = function ( X_STR, Y_STR )
{
  check_plot_object ();
  i = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;
  
  if (exist (X_STR))
  {
    if (X_STR == "log") { WIN.[P].gridx[i] = "bcngstl"; }
  else
    WIN.[P].gridx[i] = grid_x_default;
  }
  
  if (exist (Y_STR))
  {
    if (Y_STR == "log") { WIN.[P].gridy[i] = "bcngstlv"; }
  else
    WIN.[P].gridy[i] = grid_y_default;
  }
  return P;
};

##############################################################################
#
# Various internal support functions. Eventually these will be static.
#
##############################################################################

#
# Find the X or Y scale limits .
# M can be a multi-column matrix, all columns
# will be used.
#

x_scales = function ( M, p, xmin, xmax )
{  
  #
  # 1st check for un-plottable data
  #
  
  if (any (any (isinf (M))))
  { error ("plot: cannot plot Infs"); }
  if (any (any (isnan (M))))
  { error ("plot: cannot plot NaNs"); }
  
  xmin = min (min (M));
  xmax = max (max (M));
  
  #
  # Check computed scale limits against user's
  #
  
  if (WIN.[P].xmin[p] != 1j) { xmin = WIN.[P].xmin[p]; }
  if (WIN.[P].xmax[p] != 1j) { xmax = WIN.[P].xmax[p]; }

  #
  # Check for potential errors
  #
  
  if (xmin == xmax) 
  { 
    # As good a guess as any
    xmin = xmin - 1;
    xmax = xmax + 1;
  }
  
  #
  # Finally, adjust if log-scales
  #
  
  if (find_char (WIN.[P].gridx[p], "l"))
  {
    if (xmin <= 0 || xmax <= 0) { error ("cannot plot log(x<=0)"); }
    xmin = log10 (xmin);
    xmax = log10 (xmax);
  }
  
  return 1;
};

y_scales = function ( M, p, xmin, xmax )
{
  
  #
  # 1st check for un-plottable data
  #
  
  if (any (any (isinf (M))))
  { error ("plot: cannot plot Infs"); }
  if (any (any (isnan (M))))
  { error ("plot: cannot plot NaNs"); }
  
  xmin = min (min (M));
  xmax = max (max (M));
  
  #
  # Check computed scale limits against user's
  #
  
  if (WIN.[P].ymin[p] != 1j) { xmin = WIN.[P].ymin[p]; }
  if (WIN.[P].ymax[p] != 1j) { xmax = WIN.[P].ymax[p]; }

  #
  # Check for potential errors
  #
  
  if (xmin == xmax) 
  { 
    # As good a guess as any
    xmin = xmin - 1;
    xmax = xmax + 1;
  }
  
  #
  # Finally, adjust if log-scales
  #
  
  if (find_char (WIN.[P].gridy[p], "l"))
  {
    if (xmin <= 0 || xmax <= 0) { error ("cannot plot log(y<=0)"); }
    xmin = log10 (xmin);
    xmax = log10 (xmax);
  }
  
  return 1;
};

z_scales = function ( M, p, xmin, xmax )
{
  
  #
  # 1st check for un-plottable data
  #
  
  if (any (any (isinf (M))))
  { error ("plot: cannot plot Infs"); }
  if (any (any (isnan (M))))
  { error ("plot: cannot plot NaNs"); }
  
  xmin = min (min (M));
  xmax = max (max (M));
  
  #
  # Check computed scale limits against user's
  #
  
  if (WIN.[P].zmin[p] != 1j) { xmin = WIN.[P].zmin[p]; }
  if (WIN.[P].zmax[p] != 1j) { xmax = WIN.[P].zmax[p]; }

  #
  # Check for potential errors
  #
  
  if (xmin == xmax) 
  { 
    # As good a guess as any
    xmin = xmin - 1;
    xmax = xmax + 1;
  }
  
  #
  # Finally, adjust if log-scales
  #
  
  if (find_char (WIN.[P].gridz[p], "l"))
  {
    if (xmin <= 0 || xmax <= 0) { error ("plot: cannot plot log(z<=0)"); }
    xmin = log10 (xmin);
    xmax = log10 (xmax);
  }
  
  return 1;
};

##############################################################################
#
# Find the X and Y scales for a single matrix. (OLD)
#

xy_scales = function ( M, p, xmin, xmax, ymin, ymax )
{  
  #
  # 1st check for un-plottable data
  #
  
  if (any (any (isinf (M))))
  { error ("plot: cannot plot infs"); }
  if (any (any (isnan (M))))
  { error ("plot: cannot plot NaNs"); }
  
  if (M.nc == 1)
  {
    xmin = 1;
    xmax = M.nr;
    ymin = min (M);
    ymax = max (M);
  else
    xmin = min (M[;1]);
    xmax = max (M[;1]);
    ymin = min (min (M[;2:M.nc]));
    ymax = max (max (M[;2:M.nc]));
  }
  
  #
  # Check computed scale limits against user's
  #
  
  if (WIN.[P].xmin[p] != 1j) { xmin = WIN.[P].xmin[p]; }
  if (WIN.[P].xmax[p] != 1j) { xmax = WIN.[P].xmax[p]; }
  if (WIN.[P].ymin[p] != 1j) { ymin = WIN.[P].ymin[p]; }
  if (WIN.[P].ymax[p] != 1j) { ymax = WIN.[P].ymax[p]; }
  
  #
  # Check for potential errors
  #
  
  if (xmin == xmax) 
  { 
    xmin = xmin - 1;
    xmax = xmax + 1;
  }
  
  if (ymin == ymax)
  {
    ymin = ymin - 1;
    ymax = ymax + 1;
  }
  
  #
  # Finally, adjust if log-scales
  #
  
  if (find_char (WIN.[P].gridx[p], "l"))
  {
    if (xmin <= 0 || xmax <= 0) { error ("plot: cannot plot log <= 0"); }
    xmin = log10 (xmin);
    xmax = log10 (xmax);
  }
  if (find_char (WIN.[P].gridy[p], "l"))
  {
    if (ymin <= 0 || ymax <= 0) { error ("plot: cannot plot log <= 0"); }
    ymin = log10 (ymin);
    ymax = log10 (ymax);
  }
  
  return 1;
};

##############################################################################
#
# Find the X, Y and Z scales for a single matrix.
#

XYZ_scales = function ( X, Y, Z, p, xmin, xmax, ymin, ymax, zmin, zmax )
{
  # X - scale
  if (any (any (isinf (X))))
  { error ("cannot plot infs"); }
  if (any (any (isnan (X))))
  { error ("cannot plot NaNs"); }
  
  xmin = min (real (X));
  xmax = max (real (X));
  
  # Y - scale
  if (any (any (isinf (Y))))
  { error ("cannot plot infs"); }
  if (any (any (isnan (Y))))
  { error ("cannot plot NaNs"); }
  
  ymin = min (real (Y));
  ymax = max (real (Y));
  
  # Z - scale
  if (any (any (isinf (Y))))
  { error ("cannot plot infs"); }
  if (any (any (isnan (Y))))
  { error ("cannot plot NaNs"); }
  
  zmin = min (min (real (Z)));
  zmax = max (max (real (Z)));
  
  #
  # Check computed scale limits against user's
  #
  
  if (WIN.[P].xmin[p] != 1j) { xmin = WIN.[P].xmin[p]; }
  if (WIN.[P].xmax[p] != 1j) { xmax = WIN.[P].xmax[p]; }
  if (WIN.[P].ymin[p] != 1j) { ymin = WIN.[P].ymin[p]; }
  if (WIN.[P].ymax[p] != 1j) { ymax = WIN.[P].ymax[p]; }
  if (WIN.[P].zmin[p] != 1j) { zmin = WIN.[P].zmin[p]; }
  if (WIN.[P].zmax[p] != 1j) { zmax = WIN.[P].zmax[p]; }
  
  #
  # Check for potential errors
  #
  
  if (xmin == xmax) 
  { 
    # As good a guess as any
    xmin = xmin - 1;
    xmax = xmax + 1;
  }
  
  if (ymin == ymax) 
  { 
    # As good a guess as any
    ymin = ymin - 1;
    ymax = ymax + 1;
  }
  
  if (zmin == zmax) 
  { 
    # As good a guess as any
    zmin = zmin - 1;
    zmax = zmax + 1;
  }
  
  #
  # Finally, adjust if log-scales
  #
  
  if (find_char (WIN.[P].grid3x[p], "l"))
  {
    if (xmin <= 0 || xmax <= 0) { error ("plot: cannot plot log(x<=0)"); }
    xmin = log10 (xmin);
    xmax = log10 (xmax);
  }
  
  if (find_char (WIN.[P].grid3y[p], "l"))
  {
    if (ymin <= 0 || ymax <= 0) { error ("plot: cannot plot log(y<=0)"); }
    ymin = log10 (ymin);
    ymax = log10 (ymax);
  }
  
  if (find_char (WIN.[P].grid3z[p], "l"))
  {
    if (zmin <= 0 || zmax <= 0) { error ("plot: cannot plot log(z<=0)"); }
    zmin = log10 (zmin);
    zmax = log10 (zmax);
  }
  
  return 1;
};

##############################################################################
#
# Find the X and Y scales for a list of matrices
#

list_scales = function ( data, key, p, Xmin, Xmax, Ymin, Ymax )
{
  once = 1;
  
  for (i in members (data))
  {
    M = real (data.[i]);
    if (class (M) != "num") { continue; }
    
    if (abs (key) > M.nc)
    {
      error_1 ("plot: KEY argument > M.nc");
    }
    
    #
    # 1st check for un-plottable data
    #
    
    if (any (any (isinf (M))))
    { error ("plot: cannot plot infs"); }
    if (any (any (isnan (M))))
    { error ("plot: cannot plot NaNs"); }
    
    k = find ((1:M.nc) != abs (key));
    if (key > 0)
    {
      if (M.nc != 1)
      {
	x_scales ( real(M)[;key], p, xmin, xmax );
	y_scales ( real(M)[;k],   p, ymin, ymax );
      else
	x_scales ( (1:M.nr)', p, xmin, xmax );
	y_scales ( real(M),   p, ymin, ymax );
      }
    else if (key < 0) {
      x_scales ( real(M)[;k],        p, xmin, xmax );
      y_scales ( real(M)[;abs(key)], p, ymin, ymax );
    else
      x_scales ( (1:M.nr)', p, xmin, xmax );
      y_scales ( real(M),   p, ymin, ymax );
    } }

    if (once) 
    { 
      Xmin = xmin; Xmax = xmax; Ymin = ymin; Ymax = ymax; 
      once = 0; 
    }
    if (xmin < Xmin) { Xmin = xmin; }
    if (xmax > Xmax) { Xmax = xmax; }
    if (ymin < Ymin) { Ymin = ymin; }
    if (ymax > Ymax) { Ymax = ymax; }
  }
  
  return 1;
};

##############################################################################
#
# Find the maximum number of elements in a bin for a single 
# column matrix.
#

hist_scales = function ( data, nbin )
{
  dmin = min (real (data));
  dmax = max (real (data));
  dbin = linspace (dmin, dmax, nbin+1);
  binval = zeros (nbin, 1);
  
  for (i in 1:nbin)
  {
    binval[i] = length (find (data >= dbin[i] && data < dbin[i+1]));
  }
  
  return max (binval);
};

##############################################################################
#
# Plot the columns of a matrix (core function)
#
# Notes: This is the core function for plotting a matrix. If the
# matrix is a single column, then the matrix elements are plotted
# versus the row numbers. If it is a multi-column matrix, then
# columns 2:N are plotted versus column 1.
#
# p, K, k and l are indices for plot features.
# p: the current plot index (the plot #)
# K: usually 0. This index is used to start of the line style and
# color index (k = color index, l = line-style index). This is mostly
# used by plot_list, which may call plot_matrix repeatedly.
# k: the line color index. This value determines the line color used
# for each column of data. If K = 0, then k goes like 2:14, then
# flops back to 1:14.
# l: the line style inex. This value determines the line style used
# for each column of data - not the line-type (points, or lines). If
# K = 0, then l goes like 2:8, then flops back to 1:8.
#
##############################################################################

plot_matrix = function ( M, key, p, K, xmin, xmax, ymin, ymax, v )
{
  np = M.nr;
  
  if (M.nc == 1)
  {
    x = 1:M.nr;
    y = real (M);
    k = mod (1+K, 14) + 1;
    l = mod (1+K, 8);
    
    if (find_char (WIN.[P].gridx[p], "l"))
    { x = log10 (x); }
    if (find_char (WIN.[P].gridy[p], "l"))
    { y = log10 (y); }
    
    _plcol (WIN.[P].color[p;k]);
    _pllsty (WIN.[P].lstyle[p;l]);
    
    if (get_style (WIN.[P].style.[p], k-1) == "line") 
    {
      _plline (M.nr, x, y);
    else if (get_style (WIN.[P].style.[p], k-1) == "point") {
      _plpoin (M.nr, x, y, WIN.[P].pstyle[p]+k);
    else if (get_style (WIN.[P].style.[p], k-1) == "line-point") {
      _plline (M.nr, x, y);
      _plpoin (M.nr, x, y, WIN.[P].pstyle[p]+k);
    else {
      _plline (M.nr, x, y);
    }}}}

    #
    # Now do the legend 
    #
    
    if (!any (any (WIN.[P].desc.[p] == 1j)))
    {
      # Use the default if necessary
      if (WIN.[P].desc.[p][1] == "default") 
      {
	desc = "c1";
      else if (WIN.[P].desc.[p].n >= k-1) {
	desc = WIN.[P].desc.[p][k-1];
      else
	# Not sure what to do, user has messed up.
	desc = "";
      } }
             
      v = v - (ymax-ymin)/11;
      xl = (xmax-xmin)*[10.5/12, 11/12, 11.5/12]' + xmin;
      yl = [v, v, v]' + ymin;
      
      if (get_style (WIN.[P].style.[p], k-1) == "line") 
      {
	_plline (3, xl, yl);
      else if (get_style (WIN.[P].style.[p], k-1) == "point") {
	_plpoin (3, xl, yl, WIN.[P].pstyle[p]+k);
      else if (get_style (WIN.[P].style.[p], k-1) == "line-point") {
	_plline (3, xl, yl);
	_plpoin (3, xl, yl, WIN.[P].pstyle[p]+k);
      } } }

      plptex(desc, xl[1]-(xmax-xmin)/25, yl[3], , , 1);
      
    }

  else

    #
    # Check for large column dimension
    #
    
    if (M.nc > 3*M.nr)
    {
      printf (" Plot %i columns and %i rows, are you sure (y/n) ? "...
               , M.nc, M.nr);
      ans = getline ("stdin");
      if (ans.[1] != "y") { return -1; }
    }
    
    ki = find ((1:M.nc) != abs (key));
    for (i in ki)
    {
      if (key > 0)
      {
	x = real (M[;key]);
	y = real (M[;i]);
      else if (key < 0) {
	x = real (M[;i]);
	y = real (M[;abs(key)]);
      else
	x = (1:M.nr)';
	y = real (M[;i]);
      } }

      # Check for log scales, adjust if necessary
      if (find_char (WIN.[P].gridx[p], "l"))
      { x = log10 (x); }
      if (find_char (WIN.[P].gridy[p], "l"))
      { y = log10 (y); }
      
      k = mod (i-1 + K, 14) + 1;
      l = mod (8 + i-2 + K, 8) + 1;
      
      _plcol (WIN.[P].color[p;k]);
      _pllsty (WIN.[P].lstyle[p;l]);
      
      if (get_style (WIN.[P].style.[p], k-1) == "line") 
      {
	_plline (np, x, y);
      else if (get_style (WIN.[P].style.[p], k-1) == "point") {
	_plpoin (np, x, y, WIN.[P].pstyle[p]+k);
      else if (get_style (WIN.[P].style.[p], k-1) == "line-point") {
	_plline (np, x, y);
	_plpoin (np, x, y, WIN.[P].pstyle[p]+k);
      else {
	_plline (np, x, y);
      }}}}

      #
      # Now do the legend 
      #
      
      if (!any (any (WIN.[P].desc.[p] == 1j)))
      {
	# Use the default if necessary
	if (WIN.[P].desc.[p][1] == "default") 
	{
	  desc = "c" + num2str (i);
        else if (WIN.[P].desc.[p].n >= k-1) {
	  desc = WIN.[P].desc.[p][k-1];
        else
	  # Not sure what to do, user has messed up.
	  desc = "";
        }}
             
	v = v - (ymax-ymin)/11;
	xl = (xmax-xmin)*[10.5/12, 11/12, 11.5/12]' + xmin;
	yl = [v, v, v]' + ymin;
	
	if (get_style (WIN.[P].style.[p], k-1) == "line") 
	{
	  _plline (3, xl, yl);
        else if (get_style (WIN.[P].style.[p], k-1) == "point") {
	  _plpoin (3, xl, yl, WIN.[P].pstyle[p]+k);
        else if (get_style (WIN.[P].style.[p], k-1) == "line-point") {
	  _plline (3, xl, yl);
	  _plpoin (3, xl, yl, WIN.[P].pstyle[p]+k);
        }}}
	
	plptex(desc, xl[1]-(xmax-xmin)/25, yl[3], , , 1);
	
      }
    }
  }
  
  return k-1;
};

##############################################################################
#
# Plot all of the matrices in a list on the same plot
#

plot_list = function ( L, key, p, xmin, xmax, ymin, ymax )
{
  k = 0;
  v = ymax - ymin;
  
  #
  # Sort out the list members
  #

  sl = list_sort (L);

  # Plot the list members with numeric labels 1st.
  if (exist (sl.num))
  {
    for (i in sl.num)
    {
      M = L.[i];
      if (class (M) != "num") { continue; }
      if ((k = plot_matrix (M, key, p, k, xmin, xmax, ymin, ymax, v)) < 0) 
      { 
	return k; 
      }
    }
  }

  # Now plot the list members with string labels.
  if (exist (sl.char))
  {
    for (i in sl.char)
    {
      M = L.[i];
      if (class (M) != "num") { continue; }
      if ((k = plot_matrix (M, key, p, k, xmin, xmax, ymin, ymax, v)) < 0) 
      { 
	return k; 
      }
    }
  }
  return 1;
};

##############################################################################
#
# Check the elements of LIST.
# LIST must contain elements `x', `y',
# and `z'
#

check_3d_list = function ( LIST )
{
  #
  # Check existence and types
  #
  
  if (class (LIST) != "list") {
    error ("plot3: argument must be a list");
  }
  if (!exist (LIST.x)) {
    error ("plot3: arg must contain `x' member");
  else if (class (LIST.x) != "num") {
    error ("plot3: x must be numeric");
  } }
  if (!exist (LIST.y)) {
    error ("plot3: arg must contain `y' member");
  else if (class (LIST.y) != "num") {
    error ("plot3: y must be numeric"); 
  } }
  if (!exist (LIST.z)) {
    error ("plot3: arg must contain `z' member");
  else if (class (LIST.z) != "num") {
    error ("plot3: z must be numeric");
  } }

  #
  # Check sizes
  #
  
  if (LIST.x.n != LIST.z.nr) 
  {
    error ("plot3: x.n != z.nr");
  }
  
  if (LIST.y.n != LIST.z.nc) 
  {
    error ("plot3: y.n != z.nc");
  }
  
};

##############################################################################
#
# A special type of histogram plot.
#

plhistx = function ( M , nbin )
{
  check_plot_object ();
  
  if (!exist (nbin)) { nbin = 10; }
  
  p = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;	# The current index
  np = M.nr;
  
  # Compute max/min values of data
  
  ymin = min (min(real (M)));
  ymax = max (max(real (M)));
  
  #
  # Check computed scale limits against user's
  #
  
  if (WIN.[P].ymin[p] != 1j) { ymin = WIN.[P].ymin[p]; }
  if (WIN.[P].ymax[p] != 1j) { ymax = WIN.[P].ymax[p]; }
  
  _plgra ();
  _plcol (15);
  _plfont (WIN.[P].font[p]);
  _plwid (WIN.[P].width[p]);
  
  dbin = (linspace (ymin, ymax, nbin+1))';
  for (j in 1:M.nc) 
  {
    // counting
    for (i in 1:nbin) 
    {
      binval[i;j] = length (find (M[;j] >= dbin[i] && M[;j] < dbin[i+1]));
    }
  }
  
  if (!subplot_f) {
    _pladv (0);        # Advance 1 subplot
  else
    subplot_f = 0;     # The user has set the subplot
  }

  if (WIN.[P].aspect[p] != 0)
  {
    _plvasp (WIN.[P].aspect[p]);
  else
    _plvsta ();
  }

  xmin = 0;
  xmax =  max(max(binval));
  _plwind (ymin, ymax, xmin, xmax);
  _plbox (WIN.[P].gridx[p], 0, 0, WIN.[P].gridy[p], 0, 0);

  #
  # Now reorganize dbin and binval so we plot points in
  # the middle of the bins.
  #

  delbin = abs(dbin[2] - dbin[1]);
  dbin = (linspace (ymin+delbin/2, ymax-delbin/2, nbin))';
  dbin = [ymin ; dbin ; ymax];
  binval = [zeros(1,binval.nc); binval; zeros(1,binval.nc)];

  v = xmax;
  for (i in 1:M.nc)
  {
    k = mod (i, 14) + 1;
    l = mod (i,  8) + 1;
    _plcol (WIN.[P].color[p;k]);
    _pllsty (WIN.[P].lstyle[p;l]);
    
    if      (get_style (WIN.[P].style.[p], k-1) == "line") 
    {
      _plline (nbin+2, dbin, binval[;i]);
    else if (get_style (WIN.[P].style.[p], k-1) == "point") {
      _plpoin (nbin+2, dbin, binval[;i], WIN.[P].pstyle[p]+k);
    else if (get_style (WIN.[P].style.[p], k-1) == "line-point") {
      _plline (nbin+2, dbin, binval[;i]);
      _plpoin (nbin+2, dbin, binval[;i], WIN.[P].pstyle[p]+k);     
    } } }

    // write legend around upper-right corner.
    // it is better to have user to choose location for legend.
    
    if (!any (any (WIN.[P].desc.[p] == 1j)))
    {
      # Use the default if necessary
      if (WIN.[P].desc.[p][1] == "default") 
      {
	desc = "c"+num2str(i);
      else if (WIN.[P].desc.[p].n >= i) {
	  desc = WIN.[P].desc.[p][i];
      else
	# Not sure what to do, user has messed up.
	desc = "";
      } }

      v = v - (xmax)/11;
      xt = (ymax-ymin)*[10.5/12, 11/12, 11.5/12]' + ymin;
      yt = [v, v, v]';
      
      if      (get_style (WIN.[P].style.[p], k-1) == "line") 
      {
	_plline (3, xt, yt);
      else if (get_style (WIN.[P].style.[p], k-1) == "point") {
	_plpoin (3, xt, yt, WIN.[P].pstyle[p]+k);
      else if (get_style (WIN.[P].style.[p], k-1) == "line-point") {
	_plline (3, xt, yt);
	_plpoin (3, xt, yt, WIN.[P].pstyle[p]+k);
      } } }

      plptex(desc, xt[1]-(ymax-ymin)/25, yt[3], , , 1);
    }
  }
  
  _plcol (15);
  _pllab (WIN.[P].xlabel[p], WIN.[P].ylabel[p], WIN.[P].title[p]);  
  _plflush ();
  _pltext ();
  
  #
  # Increment the plot no. so that next time
  # we use the correct settings.
  #
  
  WIN.[P].subplot = WIN.[P].subplot + 1;
  
  return 1;
};


##############################################################################
#
# Create a legend in the current plot window
#
# if pobj.desc.[p] = inf()		no legend
# if pobj.desc.[p] = "default"		default ("c1", "c2", ...)
# if pobj.desc.[p] = "string"		use "string" as description
#

#
# Set the current plot legend string
#

plegend = function ( LEGEND )
{
  check_plot_object ();
  
  p = mod (WIN.[P].subplot, WIN.[P].nplot) + 1;	# The current index
  
  if (!exist (LEGEND)) 
  {
    WIN.[P].desc.[p] = 1j;
    return P;
  }
  
  if (class (LEGEND) == "string")
  {
    WIN.[P].desc.[p] = LEGEND;
  }
  
  return P;
};

set3d = function (bx, by, h)
{
  if (!exist (bx)) { basex = 2; else basex = bx; }
  if (!exist (by)) { basey = 2; else basey = by; }
  if (!exist (h)) { height = 4; else height = h; }
};
