/* This file is part of the GNU plotutils package.  Copyright (C) 1989,
   1990, 1991, 1995, 1996, 1997, 1998, 1999, 2000, 2005, 2008, Free
   Software Foundation, Inc.

   The GNU plotutils package is free software.  You may 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, or (at your
   option) any later version.

   The GNU plotutils package 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 the GNU plotutils package; see the file COPYING.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin St., Fifth Floor,
   Boston, MA 02110-1301, USA. */

/* This file contains the point plotter half of GNU graph.  The point
   plotter could easily be linked with other software.  It translates a
   sequence of points, regarded as defining a polyline or a sequence of
   polylines, to a sequence of libplot calls.  There is support for
   multigraphing, i.e. producing a plot consisting of more than a single
   graph.  Each graph may be drawn from more than one file, i.e., input
   stream, and each input stream may provide more than a single polyline.

   A `point' is a structure.  Each point structure contains the following
   fields:

      x and y coordinates of the point
      a `have_x_errorbar' flag (true or false)
      a `have_y_errorbar' flag (true or false)
      xmin and xmax (meaningful only if have_x_errorbar is set)
      ymin and ymax (meaningful only if have_y_errorbar is set)
      a `pendown' flag

      a symbol type (a small integer, interpreted as a marker type)
      a symbol size (a fraction of the size of the plotting box)
      a symbol font name (relevant only for symbol types >= 32)
      a linemode (a small integer; line style unless line_color is empty)
      a line_color (a string, overrides linemode color unless empty)
      a linewidth (a fraction of the size of the display device)
      a polyline fill-fraction (in the interval [0,1], <0 means no fill)
      a use_color flag (true or false)

   The point plotter constructs a polyline from each successive run of
   points that have the pendown flag set.  It assumes that the final seven
   fields are assumed to be the same for each point in such a run, i.e., it
   takes their values from the first point of the run.  At the location of
   each point on the polyline, the appropriate marker symbol, if any, will
   be plotted.  Symbol types greater than or equal to 32 are interpreted as
   single characters to be plotted, rather than symbols.

   Points without the pendown flag set cause the polyline to be broken, and
   a new one to begin, before the symbol (if any) is plotted.

   The plotter supports five basic linemodes: 1 through 5.  The
   interpretation of `linemode' depends on the polyline's use_color flag.

	   linemode		If monochrome 		If color

	       1		solid			red
	       2		dotted			green
	       3		dotdashed		blue
	       4		shortdashed		magenta
	       5		longdashed		cyan

   In the monochrome case, the pattern simply repeats: 6,7,8,9,10 are
   equivalent to 1,2,3,4,5, etc.  In the colored case, the sequence of
   colors also repeats.  But linemodes 1,2,3,4,5 are drawn solid, while
   6,7,8,9,10 are drawn dotted, 11,12,13,14,15 are drawn dotdashed, etc.
   So there are 25 distinct colored linemodes, and 5 distinct monochrome
   (black) ones.

   The color of a symbol will be the same as the color of the polyline on
   which it is plotted.

   linemodes -1, -2, etc. have a special interpretation.  They are
   `disconnected' linemodes: no polyline will appear, but if color is
   being used, the color of the plotted symbols (if any) will be
   linemode-dependent.  -1,-2,-3,-4,5 signify red,green,blue,magenta,cyan
   (the same sequence as for 1,2,3,4,5); thereafter the sequence repeats.

   linemode 0 is special (for backward compatibility).  No line is drawn;
   symbol #1 (a point) will be used.  So using linemode 0 is the same as
   using linemode -1, symbol 1.

   The point plotter is invoked by calling the following, in order.

   new_multigrapher() creates a new point plotter.
   begin_graph()
   set_graph_parameters() initializes global structures used by
   	the draw_frame_of_graph() and plot_point() routines.  These include
	the structures that specify the linear transformation from user
	coordinates to the coordinates used by libplot, and structures
	that specify the style of the plot frame.
   draw_frame_of_graph() plots the graph frame.  [Optional.]
   plot_point() uses libplot routines to plot a single point, together
   	with (possibly)	a line extending to it from the last point, and
	a symbol. [Alternatively, plot_point_array() can be used, to plot
	an array of points.]
   end_graph()
   ..
   [The begin_graph()..end_graph() block can be repeated indefinitely
    if desired, to create a multigraph.  set_graph_parameters() allows
    for repositioning of later graphs.]
   ..
   delete_multigrapher() deletes the point plotter.

   There is also a function end_polyline_and_flush(), which is useful for
   real-time display. */

#include "sys-defines.h"
#include "libcommon.h"
#include "plot.h"
#include "extern.h"

/* we use floating point libplot coordinates in the range [0,PLOT_SIZE] */
#define PLOT_SIZE 4096.0

#define FUZZ 0.000001		/* bd. on floating pt. roundoff error */

#define NEAR_EQUALITY(a, b, scale) (fabs((a) - (b)) < (FUZZ * fabs(scale)))

typedef unsigned int outcode;	/* for Cohen-Sutherland clipper */
enum { TOP = 0x1, BOTTOM = 0x2, RIGHT = 0x4, LEFT = 0x8 };
enum { ACCEPTED = 0x1, CLIPPED_FIRST = 0x2, CLIPPED_SECOND = 0x4 };

#define TRIAL_NUMBER_OF_TICK_INTERVALS 5
#define MAX_NUM_SUBTICKS 29	/* max num. of linearly spaced subticks */
#define RELATIVE_SUBTICK_SIZE 0.4 /* subtick_size / tick_size */
/* if a log axis spans >5.0 orders of magnitude, don't plot log subsubticks */
#define MAX_DECADES_WITH_LOG_SUBSUBTICKS 5.0

/* inter-tick spacing types, returned by scale1() and spacing_type() */
#define S_ONE 0
#define S_TWO 1
#define S_FIVE 2
#define S_TWO_FIVE 3	/* we don't use this one, but user may request it */
#define S_UNKNOWN -2

/* valid graph axis layout types; A_LOG2, anyone? */
#define A_LINEAR 0
#define A_LOG10 1

/* The x_trans and y_trans elements of a Multigrapher specify the current
   linear transformation from user coordinates to device coordinates.  They
   are used both in the plotting of a graph frame, and in the plotting of
   data points within a graph. */

typedef struct
{
  /* Input (user) coordinates, all floating point.  These are the
     coordinates used in the original data points (or their base-10 logs,
     for an axis of log type).  We'll map them to the unit interval
     [0.0,1.0]. */
  double input_min, input_max;	/* min, max */
  double input_range;		/* max - min, precomputed for speed */
  /* If we're reversing axes, we'll then map [0.0,1.0] to [1.0,0.0] */
  bool reverse;
  /* We'll map [0.0,1.0] to another (smaller) interval, linearly */
  double squeezed_min, squeezed_max; /* min, max */
  double squeezed_range;	/* max - min */
  /* Output [i.e., libplot] coordinates.  The interval [0.0,1.0] will be
     mapped to this range, and the squeezed interval to a sub-range.  This
     is so that the box within which points are plotted will be smaller
     than the full area of the graphics display. */
  double output_min, output_max; /* min */
  double output_range;		/* max - min */
} Transform;

/* Affine transformation macros */

/* X Scale: convert from user x value to normalized x coordinate (floating
   point, 0.0 to 1.0), and inverse. */
#define XS(x) (((x) - multigrapher->x_trans.input_min)/multigrapher->x_trans.input_range)
#define XSI(x) (((x)*multigrapher->x_trans.input_range) + multigrapher->x_trans.input_min)
/* X Reflect: map [0,1] to [1,0], if that's called for */
#define XR(x) (multigrapher->x_trans.reverse ? 1.0 - (x) : (x))
/* X Squeeze: map [0,1] range for normalized x coordinate into a smaller
   interval, the x range for the plotting area within the graphics display */
#define XSQ(x) (multigrapher->x_trans.squeezed_min + (x) * multigrapher->x_trans.squeezed_range)
#define XSQI(x) (((x) - multigrapher->x_trans.squeezed_min)/multigrapher->x_trans.squeezed_range)
/* X Plot: convert from normalized x coordinate to floating point libplot
   coordinate. */
#define XP(x) (multigrapher->x_trans.output_min + (x) * multigrapher->x_trans.output_range)
#define XPI(x) (((x) - multigrapher->x_trans.output_min)/multigrapher->x_trans.output_range)
/* X Value: convert from user x value (floating point) to floating point
   libplot coordinate. */
#define XV(x) XP(XSQ(XR(XS(x))))
#define XVI(x) XSI(XR(XSQI(XPI(x))))
/* X Normalize: the same, but do not perform reflection if any.  (We use
   this for plotting of axes and their labels.) */
#define XN(y) XP(XSQ(XS(y)))

/* Y Scale: convert from user y value to normalized y coordinate (floating
   point, 0.0 to 1.0). */
#define YS(y) (((y) - multigrapher->y_trans.input_min)/multigrapher->y_trans.input_range)
#define YSI(y) (((y)*multigrapher->y_trans.input_range) + multigrapher->y_trans.input_min)
/* Y Reflect: map [0,1] to [1,0], if that's called for */
#define YR(y) (multigrapher->y_trans.reverse ? 1.0 - (y) : (y))
/* Y Squeeze: map [0,1] range for normalized y coordinate into a smaller
   interval, the y range for the plotting area within the graphics display */
#define YSQ(y) (multigrapher->y_trans.squeezed_min + (y) * multigrapher->y_trans.squeezed_range)
#define YSQI(y) (((y) - multigrapher->y_trans.squeezed_min)/multigrapher->y_trans.squeezed_range)
/* Y Plot: convert from normalized y coordinate to floating point libplot
   coordinate. */
#define YP(y) (multigrapher->y_trans.output_min + (y) * multigrapher->y_trans.output_range)
#define YPI(y) (((y) - multigrapher->y_trans.output_min)/multigrapher->y_trans.output_range)
/* Y Value: convert from user y value (floating point) to floating point
   libplot coordinate.  (We use this for plotting of points.) */
#define YV(y) YP(YSQ(YR(YS(y))))
#define YVI(x) YSI(YR(YSQI(YPI(x))))
/* Y Normalize: the same, but do not perform reflection if any.  (We use
   this for plotting of axes and their labels.) */
#define YN(y) YP(YSQ(YS(y)))

/* Size Scale: convert distances, or sizes, from normalized coors to
   libplot coordinates.  (Used for tick, symbol, and font sizes.)  The min
   should really be precomputed. */
#define SS(x) \
(DMIN(multigrapher->x_trans.output_range * multigrapher->x_trans.squeezed_range, \
multigrapher->y_trans.output_range * multigrapher->y_trans.squeezed_range) * (x))

/* The `x_axis' and `y_axis' elements of a Multigrapher, which are of type
   `Axis', specify the layout of the two axes of a graph.  They are used in
   the drawing of a graph frame.  All elements that are doubles are
   expressed in user coordinates (unless the axis is logarithmic, in which
   case logs are taken before this structure is filled in). */

/* The `x_axis' and `y_axis' elements are filled in by calls to
   prepare_axis() when set_graph_parameters() is called.  The only
   exceptions to this are the elements `max_width' and `non_user_ticks',
   which are filled in by draw_frame_of_graph(), as the frame for a graph
   is being drawn. */

typedef struct
{
  const char *font_name;	/* fontname for axis label and tick labels */
  double font_size;		/* font size for axis label and tick labels */
  const char *label;		/* axis label (a string) */
  int type;			/* axis layout type (A_LINEAR or A_LOG10) */
  double tick_spacing;		/* distance between ticks */
  int min_tick_count, max_tick_count; /* tick location = count * spacing */
  bool have_lin_subticks;	/* does axis have linearly spaced subticks? */
  double lin_subtick_spacing;	/* distance between linearly spaced subticks */
  int min_lin_subtick_count, max_lin_subtick_count;
  bool have_normal_subsubticks; /* does axis have logarithmic subsubticks?*/
  bool user_specified_subsubticks; /* axis has user-spec'd subsubticks? */
  double subsubtick_spacing;	/* spacing for user-specified ones */
  double other_axis_loc;	/* location of intersection w/ other axis */
  double alt_other_axis_loc;	/* alternative loc. (e.g. right end vs. left)*/
  bool switch_axis_end;		/* other axis at right/top, not left/bottom? */
  bool omit_ticks;		/* just plain omit them (and their labels) ? */
  double max_label_width;	/* max width of labels placed on axis, in
				   libplot coors (we update this during
				   plotting, for y axis only) */
  int labelled_ticks;		/* number of labelled ticks, subticks, and
				   subsubticks drawn on the axis
				   (we update this during plotting, so we
				   can advise the user to specify a tick
				   spacing by hand if labelled_ticks <= 2) */
} Axis;


/* The Multigrapher structure.  A pointer to one of these is passed as the
   first argument to each Multigrapher method (e.g., plot_point()). */

struct MultigrapherStruct
{
  /* multigrapher parameters (not updated over a multigrapher's lifetime) */
  plPlotter *plotter;		/* GNU libplot Plotter handle */
  const char *output_format;	/* type of libplot device driver [unused] */
  const char *bg_color;		/* color of background, if non-NULL */
  bool save_screen;		/* erase display when opening plotter? */
  /* graph parameters (constant over any single graph) */
  Transform x_trans, y_trans;   /* user->device coor transformations */
  Axis x_axis, y_axis;		/* information on each axis */
  grid_type grid_spec;		/* frame specification */
  double blankout_fraction;	/* 1.0 means blank whole box before drawing */
  bool no_rotate_y_label;	/* useful for pre-X11R6 X servers */
  double tick_size;		/* fractional tick size */
  double subtick_size;		/* fractional subtick size (for linear axes) */
  double frame_line_width;	/* fractional width of lines in the frame */
  double half_line_width;	/* approx. half of this, in libplot coors */
  const char *frame_color;	/* color for frame (and graph, if no -C) */
  const char *title;		/* graph title */
  const char *title_font_name;	/* font for graph title */
  double title_font_size;	/* fractional height of graph title */
  int clip_mode;		/* 0, 1, or 2 (cf. clipping in gnuplot) */
  /* following elements are updated during plotting of points; they're the
     chief repository for internal state */
  bool first_point_of_polyline;	/* true only at beginning of each polyline */
  double oldpoint_x, oldpoint_y; /* last-plotted point */
  int symbol;			/* symbol being plotted at each point */
  int linemode;			/* linemode used for polyline */
};

/* forward references */
static int clip_line (Multigrapher *multigrapher, double *x0_p, double *y0_p, double *x1_p, double *y1_p);
static int spacing_type (double spacing);
static outcode compute_outcode (Multigrapher *multigrapher, double x, double y, bool tolerant);
static void plot_abscissa_log_subsubtick (Multigrapher *multigrapher, double xval);
static void plot_errorbar (Multigrapher *multigrapher, const Point *p);
static void plot_ordinate_log_subsubtick (Multigrapher *multigrapher, double xval);
static void prepare_axis (Axis *axisp, Transform *trans, double min, double max, double spacing, const char *font_name, double font_size, const char *label, double subsubtick_spacing, bool user_specified_subsubticks, bool round_to_next_tick, bool log_axis, bool reverse_axis, bool switch_axis_end,  bool omit_ticks);
static void print_tick_label (char *labelbuf, const Axis *axis, const Transform *transform, double val);
static void scale1 (double min, double max, double *tick_spacing, int *tick_spacing_type);
static void set_line_style (Multigrapher *multigrapher, int style, const char *line_color, bool use_color);
static void transpose_portmanteau (int *val);


/* print_tick_label() prints a label on an axis tick.  The format depends
 * on whether the axis is a log axis or a linear axis; also, the magnitude
 * of the axis labels.
 */

static void
print_tick_label (char *labelbuf, const Axis *axis, const Transform *transform, double val)
{
  int prec;
  char *eloc, *ptr;
  char labelbuf_tmp[64], incrbuf[64];
  double spacing;
  bool big_exponents;
  double min, max;

  /* two possibilities: large/small exponent magnitudes */

  min = (axis->type == A_LOG10
	 ? pow (10.0, transform->input_min) : transform->input_min);
  max = (axis->type == A_LOG10
	 ? pow (10.0, transform->input_max) : transform->input_max);

  big_exponents = (((min != 0.0 && fabs (log10 (fabs (min))) >= 4.0)
		    || (max != 0.0 && fabs (log10 (fabs (max))) >= 4.0))
		   ? true : false);

  if (big_exponents)
    /* large exponents, rewrite as foo x 10^bar, using escape sequences */
    {
      char *src = labelbuf_tmp, *dst = labelbuf;
      int exponent;
      char floatbuf[64];
      char *fptr = floatbuf;
      double prefactor;

      sprintf (labelbuf_tmp, "%e", val);
      if ((eloc = strchr (labelbuf_tmp, (int)'e')) == NULL)
	return;

      if (axis->type == A_LOG10 && !axis->user_specified_subsubticks)
	/* a hack: this must be a power of 10, so just print "10^bar" */
	{
	  sscanf (++eloc, "%d", &exponent);
	  sprintf (dst, "10\\sp%d\\ep", exponent);
	  return;
	}

      /* special case: zero prints as `0', not 0.0x10^whatever */
      if (val == 0.0)
	{
	  *dst++ = '0';
	  *dst = '\0';
	  return;
	}

      while (src < eloc)
	*fptr++ = *src++;
      *fptr = '\0';
      sscanf (floatbuf, "%lf", &prefactor); /* get foo */
      sscanf (++src, "%d", &exponent); /* get bar */

      spacing = (axis->type == A_LINEAR
		 ? axis->tick_spacing
		 : axis->subsubtick_spacing); /* user-specified, for log axis*/
      sprintf (incrbuf, "%f",
	       spacing / pow (10.0, (double)exponent));
      ptr = strchr (incrbuf, (int)'.');
      prec = 0;
      if (ptr != NULL)
	{
	  int count = 0;

	  while (*(++ptr))
	    {
	      count++;
	      if (*ptr != '0')
		prec = count;
	    }
	}

      /* \sp ... \ep is start_superscript ... end_superscript, and \r6 is
	 right-shift by 1/6 em.  \mu is the `times' character. */
      sprintf (dst, "%.*f\\r6\\mu10\\sp%d\\ep",
	       prec, prefactor, exponent);

      return;
    }

  else	/* small-size exponent magnitudes */
    {
      if (axis->type == A_LOG10 && !axis->user_specified_subsubticks)
	/* a hack: this must be a (small) power of 10, so we'll just use
	   %g format (same as %f, no trailing zeroes) */
	{
	  sprintf (labelbuf, "%.9g", val);
	  return;
	}

      /* always use no. of digits of precision present in increment */
      spacing = (axis->type == A_LINEAR
		 ? axis->tick_spacing
		 : axis->subsubtick_spacing); /* user-specified, for log axis*/
      sprintf (incrbuf, "%.9f", spacing);
      ptr = strchr (incrbuf, (int)'.');
      prec = 0;
      if (ptr != NULL)
	{
	  int count = 0;

	  while (*(++ptr))
	    {
	      count++;
	      if (*ptr != '0')
		prec = count;
	    }
	}
      sprintf (labelbuf, "%.*f", prec, val);
      return;
    }
}

/* Algorithm SCALE1, for selecting an inter-tick spacing that will yield a
 * good-looking linear-format axis.  The spacing is always 1.0, 2.0, or 5.0
 * times a power of ten.
 *
 * Reference: Lewart, C. R., "Algorithms SCALE1, SCALE2, and
 *	SCALE3 for Determination of Scales on Computer Generated
 *	Plots", Communications of the ACM, 10 (1973), 639-640.
 *      Also cited as ACM Algorithm 463.
 *
 * We call this routine even when the axis is logarithmic rather than
 * linear.  In that case the arguments are the logs of the actual
 * arguments, so that it computes an optimal inter-tick factor rather than
 * an optimal inter-tick spacing.
 */

/* ARGS: min,max = data min, max
   	 tick_spacing = inter-tick spacing
	 tick_spacing_type = 0,1,2 i.e. S_ONE,S_TWO,S_FIVE */
static void
scale1 (double min, double max, double *tick_spacing, int *tick_spacing_type)
{
  int k;
  double nal;
  double a, b;

  /* valid interval lengths */
  static const double vint[] =
    {
      1.0, 2.0, 5.0, 10.0
    };

  /* Corresponding breakpoints.  The published algorithm uses geometric
     means, i.e. sqrt(2), sqrt(10), sqrt(50), but using sqrt(10)=3.16...
     will (if nticks=5, as we choose it to be) cause intervals of length
     1.5 to yield an inter-tick distance of 0.2 rather than 0.5.  So we
     could reduce it to 2.95.  Similarly we could reduce sqrt(50) to 6.95
     so that intervals of length 3.5 will yield an inter-tick distance of
     1.0 rather than 0.5. */
  static const double sqr[] =
    {
      M_SQRT2, 3.16228, 7.07107
    };

  /* compute trial inter-tick interval length */
  a = (max - min) / TRIAL_NUMBER_OF_TICK_INTERVALS;
  a *= (max > min) ? 1.0 : -1.0; /* paranoia, max>min always */
  if (a <= 0.0)
    {
      fprintf(stderr, "%s: error: the trial inter-tick spacing '%g' is bad\n",
	      progname, a);
      exit (EXIT_FAILURE);
    }
  nal = floor(log10(a));
  b = a * pow (10.0, -nal);	/* 1.0 <= b < 10.0 */

  /* round to closest permissible inter-tick interval length */
  k = 0;
  do
    {
      if (b < sqr[k])
	break;
      k++;
    }
  while (k < 3);

  *tick_spacing = (max > min ? 1.0 : -1.0) * vint[k] * pow (10.0, nal);
  /* for increment type, 0,1,2 means 1,2,5 times a power of 10 */
  *tick_spacing_type = (k == 3 ? 0 : k);
  return;
}

/* Determine whether an inter-tick spacing (in practice, one specified by
   the user) is 1.0, 2.0, or 5.0 times a power of 10. */
static int
spacing_type (double incr)
{
  int i;
  int i_tenpower = (int)(floor(log10(incr)));
  double tenpower = 1.0;
  bool neg_power = false;

  if (i_tenpower < 0)
    {
      neg_power = true;
      i_tenpower = -i_tenpower;
    }

  for (i = 0; i < i_tenpower; i++)
    tenpower *= 10;
  if (neg_power)
    tenpower = 1.0 / tenpower;

  if (NEAR_EQUALITY(incr, tenpower, tenpower))
    return S_ONE;
  else if (NEAR_EQUALITY(incr, 2 * tenpower, tenpower))
    return S_TWO;
  else if (NEAR_EQUALITY(incr, 2.5 * tenpower, tenpower))
    return S_TWO_FIVE;
  else if (NEAR_EQUALITY(incr, 5 * tenpower, tenpower))
    return S_FIVE;
  else
    return S_UNKNOWN;
}


/* prepare_axis() fills in the Axis structure for an axis, and some of
 * the linear transformation variables in the Transform structure also.
 */

/* ARGS: user_specified_subticks = linear ticks on a log axis?
   	 round_to_next_tick = round limits to next tick mark?
	 log_axis = log axis?
	 reverse_axis = reverse min, max?
	 switch_axis_end = intersection w/ other axis in alt. pos.?
	 omit_ticks = suppress all ticks and tick labels? */
static void
prepare_axis (Axis *axisp, Transform *trans, double min, double max, double spacing, const char *font_name, double font_size, const char *label, double subsubtick_spacing, bool user_specified_subsubticks, bool round_to_next_tick, bool log_axis, bool reverse_axis, bool switch_axis_end, bool omit_ticks)
{
  double range;
  int tick_spacing_type = 0;
  double tick_spacing, lin_subtick_spacing;
  int min_tick_count, max_tick_count;
  int min_lin_subtick_count, max_lin_subtick_count;
  bool have_lin_subticks;

  if (min > max)
    /* paranoia, max < min is swapped at top level */
    {
      fprintf(stderr, "%s: error: min > max for an axis, which is not allowed\n",
	      progname);
      exit (EXIT_FAILURE);
    }

  if (min == max)		/* expand in a clever way */
    {
      max = floor (max + 1.0);
      min = ceil (min - 1.0);
    }

  if (log_axis)		/* log axis, data are stored in logarithmic form */
    /* compute a tick spacing; user can't specify it */
    {
      scale1 (min, max, &tick_spacing, &tick_spacing_type);
      if (tick_spacing <= 1.0)
	{
	  tick_spacing = 1.0;
	  tick_spacing_type = S_ONE;
	}
    }
  else				/* linear axis */
    {
      if (spacing == 0.0)	/* i.e., not specified by user */
	scale1 (min, max, &tick_spacing, &tick_spacing_type);
      else			/* luser is boss, don't use SCALE1 */
	{
	  tick_spacing = spacing;
	  tick_spacing_type = spacing_type (spacing);
	}
    }

  range = max - min;		/* range is not negative */

  if (round_to_next_tick)	/* expand both limits to next tick */
    {
      if (user_specified_subsubticks)
	/* Special Case.  If user specified the `spacing' argument to -x or
	   -y on a logarithmic axis, our usual tick-generating and
	   tick-plotting algorithms are disabled.  So we don't bother with
	   min_tick_count or several other fields of the axis struct;
	   instead we just compute a new (rounded) max, min, and range.
	   Since most data are stored as logs, this is complicated. */
	{
	  double true_min = pow (10.0, min), true_max = pow (10.0, max);
	  double true_range = true_max - true_min;
	  int min_count, max_count;

	  min_count = (int)(floor ((true_min + FUZZ * true_range)
				   / subsubtick_spacing));
	  max_count = (int)(ceil ((true_max - FUZZ * true_range)
				  / subsubtick_spacing));
	  /* avoid core dump, do *not* reduce minimum to zero! */
	  if (min_count > 0)
	    min = log10 (min_count * subsubtick_spacing);
	  max = log10 (max_count * subsubtick_spacing);
	  range = max - min;
	  min_tick_count = max_tick_count = 0; /* keep gcc happy */
	}
      else	/* normal `expand limits to next tick' case */
	{
	  min_tick_count = (int)(floor((min + FUZZ * range)/ tick_spacing));
	  max_tick_count = (int)(ceil((max - FUZZ * range)/ tick_spacing));
	  /* max_tick_count > min_tick_count always */
	  /* tickval = tick_spacing * count,
	     for all count in [min_count,max_count]; must have >=2 ticks */
	  min = tick_spacing * min_tick_count;
	  max = tick_spacing * max_tick_count;
	  range = max - min;
	}
    }
  else		/* don't expand limits to next tick */
    {
      min_tick_count = (int)(ceil((min - FUZZ * range)/ tick_spacing));
      max_tick_count = (int)(floor((max + FUZZ * range)/ tick_spacing));
      /* max_tick_count <= min_tick_count is possible */
      /* tickval = incr * count,
	 for all count in [min_count,max_count]; can have 0,1,2,3... ticks */
    }

  /* Allow 5 subticks per tick if S_FIVE or S_TWO_FIVE, 2 if S_TWO.  Case
     S_ONE is special; we try 10, 5, and 2 in succession */
  switch (tick_spacing_type)
    {
    case S_FIVE:
    case S_TWO_FIVE:
      lin_subtick_spacing = tick_spacing / 5;
      break;
    case S_TWO:
      lin_subtick_spacing = tick_spacing / 2;
      break;
    case S_ONE:
      lin_subtick_spacing = tick_spacing / 10;
      min_lin_subtick_count = (int)(ceil((min - FUZZ * range)/ lin_subtick_spacing));
      max_lin_subtick_count = (int)(floor((max + FUZZ * range)/ lin_subtick_spacing));
      if (max_lin_subtick_count - min_lin_subtick_count > MAX_NUM_SUBTICKS)
	{
	  lin_subtick_spacing = tick_spacing / 5;
	  min_lin_subtick_count = (int)(ceil((min - FUZZ * range)/ lin_subtick_spacing));
	  max_lin_subtick_count = (int)(floor((max + FUZZ * range)/ lin_subtick_spacing));
	  if (max_lin_subtick_count - min_lin_subtick_count > MAX_NUM_SUBTICKS)
	    lin_subtick_spacing = tick_spacing / 2;
	}
      break;
    default:
      /* in default case, i.e. S_UNKNOWN, we won't plot linear subticks */
      lin_subtick_spacing = tick_spacing; /* not actually needed, since not plotted */
      break;
    }

  /* smallest possible inter-subtick factor for a log axis is 10.0 */
  if (log_axis && lin_subtick_spacing <= 1.0)
    lin_subtick_spacing = 1.0;

  min_lin_subtick_count = (int)(ceil((min - FUZZ * range)/ lin_subtick_spacing));
  max_lin_subtick_count = (int)(floor((max + FUZZ * range)/ lin_subtick_spacing));
  have_lin_subticks
    = ((tick_spacing_type != S_UNKNOWN /* S_UNKNOWN -> no subticks */
	&& (max_lin_subtick_count - min_lin_subtick_count) <= MAX_NUM_SUBTICKS)
       ? true : false);

  /* fill in parameters for axis-specific affine transformation */
  trans->input_min = min;
  trans->input_max = max;
  trans->input_range = range;	/* precomputed for speed */
  trans->reverse = reverse_axis;

  /* fill in axis-specific plot frame variables */
  axisp->switch_axis_end = switch_axis_end;
  axisp->omit_ticks = omit_ticks;
  axisp->label = label;
  axisp->font_name = font_name;
  axisp->font_size = font_size;
  axisp->max_label_width = 0.0;
  axisp->type = log_axis ? A_LOG10 : A_LINEAR;
  axisp->tick_spacing = tick_spacing;
  axisp->min_tick_count = min_tick_count;
  axisp->max_tick_count = max_tick_count;
  axisp->have_lin_subticks = have_lin_subticks;
  axisp->lin_subtick_spacing = lin_subtick_spacing;
  axisp->min_lin_subtick_count = min_lin_subtick_count;
  axisp->max_lin_subtick_count = max_lin_subtick_count;
  axisp->user_specified_subsubticks = user_specified_subsubticks;
  axisp->subsubtick_spacing = subsubtick_spacing;
  axisp->labelled_ticks = 0;	/* updated during drawing of frame */

  if (log_axis)		/* logarithmic axis */
    /* do we have special logarithmic subsubticks, and should we label them? */
    {
      if (max - min <=
	  MAX_DECADES_WITH_LOG_SUBSUBTICKS + FUZZ)
	/* not too many orders of magnitude, so plot normal log subsubticks */
	axisp->have_normal_subsubticks = true;
      else
	/* too many orders of magnitude, don't plot log subsubticks */
	axisp->have_normal_subsubticks = false;
    }
  else				/* linear axes don't have log subsubticks */
    axisp->have_normal_subsubticks = false;
}

/* The following routines [new_multigrapher(), begin_graph(),
 * set_graph_parameters(), draw_frame_of_graph(), plot_point(),
 * end_graph(), delete_multigrapher()] are the basic routines of the
 * point-plotter .  See descriptions at the head of this file.  */

/* Create a new Multigrapher.  The arguments, after the first, are the
   libplot Plotter parameters that the `graph' user can set on the command
   line. */

Multigrapher *
new_multigrapher (const char *output_format, const char *bg_color, const char *bitmap_size, const char *emulate_color, const char *max_line_length, const char *meta_portable, const char *page_size, const char *rotation_angle, bool save_screen)
{
  plPlotterParams *plotter_params;
  plPlotter *plotter;
  Multigrapher *multigrapher;

  multigrapher = (Multigrapher *)xmalloc (sizeof (Multigrapher));

  /* set Plotter parameters */
  plotter_params = pl_newplparams ();
  pl_setplparam (plotter_params, "BG_COLOR", (void *)bg_color);
  pl_setplparam (plotter_params, "BITMAPSIZE", (void *)bitmap_size);
  pl_setplparam (plotter_params, "EMULATE_COLOR", (void *)emulate_color);
  pl_setplparam (plotter_params, "MAX_LINE_LENGTH", (void *)max_line_length);
  pl_setplparam (plotter_params, "META_PORTABLE", (void *)meta_portable);
  pl_setplparam (plotter_params, "PAGESIZE", (void *)page_size);
  pl_setplparam (plotter_params, "ROTATION", (void *)rotation_angle);

  /* create Plotter and open it */
  plotter = pl_newpl_r (output_format, NULL, stdout, stderr, plotter_params);
  if (plotter == (plPlotter *)NULL)
    return (Multigrapher *)NULL;
  pl_deleteplparams (plotter_params);
  multigrapher->plotter = plotter;
  if (pl_openpl_r (plotter) < 0)
    return (Multigrapher *)NULL;
  multigrapher->bg_color = bg_color;

  /* if called for, erase it; set up the user->device coor map */
  if (!save_screen || bg_color)
    pl_erase_r (plotter);
  pl_fspace_r (plotter, 0.0, 0.0, (double)PLOT_SIZE, (double)PLOT_SIZE);

  return multigrapher;
}

int
delete_multigrapher (Multigrapher *multigrapher)
{
  int retval;

  retval = pl_closepl_r (multigrapher->plotter);
  if (retval >= 0)
    retval = pl_deletepl_r (multigrapher->plotter);

  free (multigrapher);
  return retval;
}


void
begin_graph (Multigrapher *multigrapher, double scale, double trans_x, double trans_y)
{
  pl_savestate_r (multigrapher->plotter);
  pl_fconcat_r (multigrapher->plotter,
		scale, 0.0, 0.0, scale,
		trans_x * PLOT_SIZE, trans_y * PLOT_SIZE);
}

void
end_graph (Multigrapher *multigrapher)
{
  pl_restorestate_r (multigrapher->plotter);
}


/* ARGS:
     Multigrapher *multigrapher
     double frame_line_width 	= fractional width of lines in the frame
     const char *frame_color 	= color for frame (and plot if no -C option)
     const char *title 		= graph title
     const char *title_font_name = font for graph title (string)
     double title_font_size 	= font size for graph title
     double tick_size 		= fractional size of ticks
     grid_type grid_spec 	= gridstyle (and tickstyle) spec
     double x_min, x_max, x_spacing
     double y_min, y_max, y_spacing
     bool spec_x_spacing
     bool spec_y_spacing
     double width, height, up, right
     const char *x_font_name
     double x_font_size
     const char *x_label
     const char *y_font_name
     double y_font_size
     const char *y_label
     bool no_rotate_y_label

     -- portmanteaux args --
     int log_axis		= whether axes should be logarithmic
     int round_to_next_tick	= round limits to the next tick mark
     int switch_axis_end	= put axes at right/top, not left/bottom?
     int omit_ticks		= omit all ticks, tick labels from an axis?
     -- other args --
     int clip_mode		= 0, 1, or 2
     double blankout_fraction	= 1.0 means blank out whole box before plot
     bool transpose_axes */
void
set_graph_parameters (Multigrapher *multigrapher, double frame_line_width, const char *frame_color, const char *title, const char *title_font_name, double title_font_size, double tick_size, grid_type grid_spec, double x_min, double x_max, double x_spacing, double y_min, double y_max, double y_spacing, bool spec_x_spacing, bool spec_y_spacing, double width, double height, double up, double right, const char *x_font_name, double x_font_size, const char *x_label, const char *y_font_name, double y_font_size, const char *y_label, bool no_rotate_y_label, int log_axis, int round_to_next_tick, int switch_axis_end, int omit_ticks, int clip_mode, double blankout_fraction, bool transpose_axes)
{
  double x_subsubtick_spacing = 0.0, y_subsubtick_spacing = 0.0;
  /* local portmanteau variables */
  int reverse_axis = 0;		/* min > max on an axis? */
  int user_specified_subsubticks = 0; /* i.e. linear ticks on a log axis? */

  if (log_axis & X_AXIS)
    {
      if (spec_x_spacing)
	/* spacing is handled specially for log axes */
	{
	  spec_x_spacing = false;
	  user_specified_subsubticks |= X_AXIS;
	  x_subsubtick_spacing = x_spacing;
	}
    }

  if (log_axis & Y_AXIS)
    {
      if (spec_y_spacing)
	{
	  /* spacing is handled specially for log axes */
	  spec_y_spacing = false;
	  user_specified_subsubticks |= Y_AXIS;
	  y_subsubtick_spacing = y_spacing;
	}
    }

  /* check for reversed axes (min > max) */
  if (x_max < x_min)
    {
      reverse_axis |= X_AXIS;
      {
	double temp;

	temp = x_min;
	x_min = x_max;
	x_max = temp;
      }
    }
  if (x_max == x_min)
    {
      fprintf (stderr,
	       "%s: identical upper and lower x limits (%f, %f) are separated\n",
	       progname, x_max, x_min);
      /* separate them */
      x_max += 1.0;
      x_min -= 1.0;
    }
  /* check for reversed axes (min > max) */
  if (y_max < y_min)
    {
      reverse_axis |= Y_AXIS;
      {
	double temp;

	temp = y_min;
	y_min = y_max;
	y_max = temp;
      }
    }
  if (y_max == y_min)
    {
      fprintf (stderr,
	       "%s: identical upper and lower x limits (%f, %f) are separated\n",
	       progname, y_max, y_min);
      /* separate them */
      y_max += 1.0;
      y_min -= 1.0;
    }

  /* At this point, min < max for each axis, if the user specified the two
     limits on an axis; reverse_axis portmanteau variable keeps track of
     whether either axis was discovered to be reversed. */

  /* silently accept negative spacing as equivalent as positive */
  if (spec_x_spacing)
    {
      if (x_spacing == 0.0)
	{
	  fprintf (stderr,
		   "%s: error: the spacing between ticks on an axis is zero\n",
		   progname);
	  exit (EXIT_FAILURE);
	}
      x_spacing = fabs (x_spacing);
    }
  if (spec_y_spacing)
    {
      if (y_spacing == 0.0)
	{
	  fprintf (stderr,
		   "%s: error: the spacing between ticks on an axis is zero\n",
		   progname);
	  exit (EXIT_FAILURE);
	}
      y_spacing = fabs (y_spacing);
    }

  /* now transpose the two axes (i.e. their portmanteau variables, labels,
     limits etc.) if transpose_axes was set */
  if (transpose_axes)
    {
      const char *temp_string;
      double temp_double;

      transpose_portmanteau (&log_axis);
      transpose_portmanteau (&round_to_next_tick);
      transpose_portmanteau (&switch_axis_end);
      transpose_portmanteau (&omit_ticks);

      transpose_portmanteau (&reverse_axis);
      transpose_portmanteau (&user_specified_subsubticks);

      temp_string = x_label;
      x_label = y_label;
      y_label = temp_string;

      temp_double = x_min;
      x_min = y_min;
      y_min = temp_double;

      temp_double = x_max;
      x_max = y_max;
      y_max = temp_double;

      temp_double = x_spacing;
      x_spacing = y_spacing;
      y_spacing = temp_double;

      temp_double = x_subsubtick_spacing;
      x_subsubtick_spacing = y_subsubtick_spacing;
      y_subsubtick_spacing = temp_double;
    }

  /* fill in the Multigrapher struct */

  multigrapher->frame_line_width = frame_line_width;
  multigrapher->frame_color = frame_color;
  multigrapher->no_rotate_y_label = no_rotate_y_label;
  multigrapher->blankout_fraction = blankout_fraction;

  if (title != NULL)
    multigrapher->title = xstrdup (title);
  else
    multigrapher->title = NULL;
  if (title_font_name != NULL)
      multigrapher->title_font_name = xstrdup (title_font_name);
  else
    multigrapher->title_font_name = NULL;
  multigrapher->title_font_size = title_font_size;
  multigrapher->tick_size = tick_size;
  multigrapher->subtick_size = RELATIVE_SUBTICK_SIZE * tick_size;
  multigrapher->grid_spec = grid_spec;
  multigrapher->clip_mode = clip_mode;

  /* fill in the Transform and Axis elements for each coordinate */
  prepare_axis (&multigrapher->x_axis, &multigrapher->x_trans,
		x_min, x_max, x_spacing,
		x_font_name, x_font_size, x_label,
		x_subsubtick_spacing,
		(bool)(user_specified_subsubticks & X_AXIS),
		(bool)(round_to_next_tick & X_AXIS),
		(bool)(log_axis & X_AXIS),
		(bool)(reverse_axis & X_AXIS),
		(bool)(switch_axis_end & X_AXIS),
		(bool)(omit_ticks & X_AXIS));
  prepare_axis (&multigrapher->y_axis, &multigrapher->y_trans,
		y_min, y_max, y_spacing,
		y_font_name, y_font_size, y_label,
		y_subsubtick_spacing,
		(bool)(user_specified_subsubticks & Y_AXIS),
		(bool)(round_to_next_tick & Y_AXIS),
		(bool)(log_axis & Y_AXIS),
		(bool)(reverse_axis & Y_AXIS),
		(bool)(switch_axis_end & Y_AXIS),
		(bool)(omit_ticks & Y_AXIS));

  /* fill in additional parameters in the two Transform structures */
  multigrapher->x_trans.squeezed_min = right;
  multigrapher->x_trans.squeezed_max = right + width;
  multigrapher->x_trans.squeezed_range = width;
  multigrapher->y_trans.squeezed_min = up;
  multigrapher->y_trans.squeezed_max = up + height;
  multigrapher->y_trans.squeezed_range = height;

  /* specify interval range for each coordinate, in libplot units */
  multigrapher->x_trans.output_min = 0.0;
  multigrapher->x_trans.output_max = (double)PLOT_SIZE;
  multigrapher->x_trans.output_range = multigrapher->x_trans.output_max - multigrapher->x_trans.output_min;
  multigrapher->x_trans.output_min = 0.0;
  multigrapher->y_trans.output_max = (double)PLOT_SIZE;
  multigrapher->y_trans.output_range = multigrapher->y_trans.output_max - multigrapher->y_trans.output_min;

  /* fill in fields in Axis structs dealing with location of other axis */
  if (multigrapher->grid_spec != AXES_AT_ORIGIN)
    /* Normal case */
    {
      /* axes are at left/bottom */
      multigrapher->x_axis.other_axis_loc = multigrapher->x_trans.input_min;
      multigrapher->y_axis.other_axis_loc = multigrapher->y_trans.input_min;
      /* secondary axes (used only if --switch-axis-end is specified) */
      multigrapher->x_axis.alt_other_axis_loc = multigrapher->x_trans.input_max;
      multigrapher->y_axis.alt_other_axis_loc = multigrapher->y_trans.input_max;
    }
  else
    /* Special case: grid type #4, AXES_AT_ORIGIN */
    {
      /* In this case (grid type #4), we don't allow the user to move the
         axis position by using the --switch-axis-end option.  Each axis is
         at the value 0 (the origin) if the value 0 is between the limits
         of the opposing axis.  Otherwise, the position is at the end
         closer to the value of 0. */
      multigrapher->x_axis.other_axis_loc
	= (multigrapher->x_trans.input_min * multigrapher->x_trans.input_max <= 0.0) ? 0.0 :
	  (multigrapher->x_trans.input_min > 0.0 ? multigrapher->x_trans.input_min : multigrapher->x_trans.input_max);
      multigrapher->y_axis.other_axis_loc
	= (multigrapher->y_trans.input_min * multigrapher->y_trans.input_max <= 0.0) ? 0.0 :
	  (multigrapher->y_trans.input_min > 0.0 ? multigrapher->y_trans.input_min : multigrapher->y_trans.input_max);
      /* secondary axes are the same */
      multigrapher->x_axis.alt_other_axis_loc = multigrapher->x_axis.other_axis_loc;
      multigrapher->y_axis.alt_other_axis_loc = multigrapher->y_axis.other_axis_loc;
      multigrapher->x_axis.switch_axis_end = (((multigrapher->x_trans.input_max - multigrapher->x_axis.other_axis_loc)
				 < (multigrapher->x_axis.other_axis_loc - multigrapher->x_trans.input_min))
				? true : false);
      multigrapher->y_axis.switch_axis_end = (((multigrapher->y_trans.input_max - multigrapher->y_axis.other_axis_loc)
				 < (multigrapher->y_axis.other_axis_loc - multigrapher->y_trans.input_min))
				? true : false);
    }

  /* The following is a version of (multigrapher->frame_line_width)/2
     (expressed in terms of libplot coordinates) which the plotter uses as
     an offset, to get highly accurate positioning of ticks and labels. */
  if (frame_line_width < 0.0
      || pl_havecap_r (multigrapher->plotter, "WIDE_LINES") == 0)
    multigrapher->half_line_width = 0.0;/* N.B. <0.0 -> default width, pres. small */
  else
    multigrapher->half_line_width = 0.5 * frame_line_width * multigrapher->x_trans.output_range;

  /* initialize the plotter state variables */
  multigrapher->first_point_of_polyline = true;
  multigrapher->oldpoint_x = 0.0;
  multigrapher->oldpoint_y = 0.0;
}

   	
/* draw_frame_of_graph() plots the graph frame (grid plus axis labels and
   title), using the multigrapher's variables (the multigrapher->x_axis,
   multigrapher->y_axis, multigrapher->x_trans, multigrapher->y_trans
   structures).  It comprises several almost-independent tasks:

   0. draw opaque white rectangle (``canvas''), if requested
   1. draw title, if any, above drawing box
   2. draw axes, and a full drawing box if requested
   3. draw ticks, grid lines, and tick labels on abscissa;
      also subticks, if abscissa is a linear axis
   4. draw ticks, grid lines, and tick labels on ordinate,
      also subticks, if ordinate is a linear axis
   5. draw sub-tick marks, sub-grid lines, and sub-labels,
      if abscissa is a logarithmic axis
   6. draw sub-tick marks, sub-grid lines, and sub-labels,
      if ordinate is a logarithmic axis
   7. draw axis label on abscissa
   8. draw axis label on ordinate (normally rotated 90 degrees)

   A savestate()--restorestate() pair is wrapped around these nine tasks.
   They are not quite independent because in (4) and (6) we keep track of
   the maximum width of the tick labels on the ordinate, to position the
   rotated ordinate label properly in (8).

   At the conclusion of the eight tasks, we warn the user if (i) he/she
   didn't specify a tick spacing for a logarithmic axis by hand, and (ii)
   our default algorithm for drawing ticks on a logarithmic axis has drawn
   too few ticks (i.e. <= 2 ticks) on the axis.

   Task #0 (drawing a white canvas) isn't always performed; only if the
   argument draw_canvas is set.  It isn't set when we call this function
   for the first time; see graph.c.  */

void
draw_frame_of_graph (Multigrapher *multigrapher, bool draw_canvas)
{
  static bool tick_warning_printed = false; /* when too few labelled ticks */

  /* wrap savestate()--restorestate() around all 9 tasks */
  pl_savestate_r (multigrapher->plotter);

  /* set color for graph frame */
  if (multigrapher->frame_color)
    pl_pencolorname_r (multigrapher->plotter, multigrapher->frame_color);

  /* set line width as a fraction of size of display, <0.0 means default */
  pl_flinewidth_r (multigrapher->plotter,
		   multigrapher->frame_line_width * (double)PLOT_SIZE);

  /* axes (or box) will be drawn in solid line style */
  pl_linemod_r (multigrapher->plotter, "solid");

  /* turn off filling */
  pl_filltype_r (multigrapher->plotter, 0);

  /* 0.  DRAW AN OPAQUE WHITE BOX */

  if (draw_canvas)
    {
      pl_savestate_r (multigrapher->plotter);
      /* use user-specified background color (if any) instead of white */
      if (pl_havecap_r (multigrapher->plotter, "SETTABLE_BACKGROUND") != 0
	  && multigrapher->bg_color)
	pl_colorname_r (multigrapher->plotter, multigrapher->bg_color);
      else
	pl_colorname_r (multigrapher->plotter, "white");

      pl_filltype_r (multigrapher->plotter, 1);	/* turn on filling */
      pl_fbox_r (multigrapher->plotter,
		 XP(XSQ(0.5 - 0.5 * multigrapher->blankout_fraction)),
		 YP(YSQ(0.5 - 0.5 * multigrapher->blankout_fraction)),
		 XP(XSQ(0.5 + 0.5 * multigrapher->blankout_fraction)),
		 YP(YSQ(0.5 + 0.5 * multigrapher->blankout_fraction)));
      pl_restorestate_r (multigrapher->plotter);
    }

  /* 1.  DRAW THE TITLE, I.E. THE TOP LABEL */

  if (multigrapher->grid_spec != NO_AXES
      && !multigrapher->y_axis.switch_axis_end /* no title if x axis is at top of plot */
      && multigrapher->title != NULL && *multigrapher->title != '\0')
    {
      double title_font_size;

      /* switch to our font for drawing title */
      pl_fontname_r (multigrapher->plotter, multigrapher->title_font_name);
      title_font_size = pl_ffontsize_r (multigrapher->plotter,
					SS(multigrapher->title_font_size));

      pl_fmove_r (multigrapher->plotter,
		  XP(XSQ(0.5)),
		  YP(YSQ(1.0
			 + (((multigrapher->grid_spec == AXES_AND_BOX
			      || multigrapher->grid_spec == AXES
                              || multigrapher->grid_spec == BOX)
			     && (multigrapher->tick_size <= 0.0) ? 1.0 : 0.5)
			    * fabs(multigrapher->tick_size))))
		  + 0.65 * title_font_size
		  + multigrapher->half_line_width);
      /* title centered, bottom spec'd */
      pl_alabel_r (multigrapher->plotter, 'c', 'b', multigrapher->title);
    }

  /* 2.  DRAW AXES FOR THE PLOT */

  switch (multigrapher->grid_spec)
    {
    case AXES_AND_BOX_AND_GRID:
    case AXES_AND_BOX:
    case BOX:
      /* draw a box, not just a pair of axes */
      pl_fbox_r (multigrapher->plotter,
		 XP(XSQ(0.0)), YP(YSQ(0.0)), XP(XSQ(1.0)), YP(YSQ(1.0)));
      break;
    case AXES:
      {
	double xstart, ystart, xmid, ymid, xend, yend;

	xstart = (multigrapher->x_axis.switch_axis_end
		  ? XN(multigrapher->x_axis.other_axis_loc) - multigrapher->half_line_width
		  : XN(multigrapher->x_axis.alt_other_axis_loc) + multigrapher->half_line_width);
	ystart = (multigrapher->y_axis.switch_axis_end
		  ? YN(multigrapher->y_axis.alt_other_axis_loc)
		  : YN(multigrapher->y_axis.other_axis_loc));
	xmid = (multigrapher->x_axis.switch_axis_end
		? XN(multigrapher->x_axis.alt_other_axis_loc)
		: XN(multigrapher->x_axis.other_axis_loc));
	ymid = ystart;
	xend = xmid;
	yend = (multigrapher->y_axis.switch_axis_end
		? YN(multigrapher->y_axis.other_axis_loc) - multigrapher->half_line_width
		: YN(multigrapher->y_axis.alt_other_axis_loc) + multigrapher->half_line_width);

	pl_fmove_r (multigrapher->plotter, xstart, ystart);
	pl_fcont_r (multigrapher->plotter, xmid, ymid);
	pl_fcont_r (multigrapher->plotter, xend, yend);
      }
      break;
    case AXES_AT_ORIGIN:
      {
	double xpos, ypos;

	xpos = (multigrapher->x_axis.switch_axis_end
		? XN(multigrapher->x_axis.other_axis_loc)
		: XN(multigrapher->x_axis.alt_other_axis_loc));
	ypos = (multigrapher->y_axis.switch_axis_end
		? YN(multigrapher->y_axis.alt_other_axis_loc)
		: YN(multigrapher->y_axis.other_axis_loc));

	pl_fline_r (multigrapher->plotter,
		    xpos, YP(YSQ(0.0)) - multigrapher->half_line_width,
		    xpos, YP(YSQ(1.0)) + multigrapher->half_line_width);
	pl_fline_r (multigrapher->plotter,
		    XP(XSQ(0.0)) - multigrapher->half_line_width, ypos,
		    XP(XSQ(1.0)) + multigrapher->half_line_width, ypos);
      }
      break;
    case NO_AXES:
    default:
      break;
    }

  /* 3.  PLOT TICK MARKS, GRID LINES, AND TICK LABELS ON ABSCISSA */

  if (multigrapher->grid_spec != NO_AXES && !multigrapher->x_axis.omit_ticks
      && !multigrapher->x_axis.user_specified_subsubticks)
    {
      int i;
      double xval, xrange = multigrapher->x_trans.input_max - multigrapher->x_trans.input_min;
      /* there is no way you could use longer labels on tick marks! */
      char labelbuf[2048];

      /* switch to our font for drawing x axis label and tick labels */
      pl_fontname_r (multigrapher->plotter, multigrapher->x_axis.font_name);
      pl_ffontsize_r (multigrapher->plotter, SS(multigrapher->x_axis.font_size));

      for (i = multigrapher->x_axis.min_tick_count; i <= multigrapher->x_axis.max_tick_count; i++)
	/* tick range can be empty */
	{
	  xval = i * multigrapher->x_axis.tick_spacing;

	  /* discard tick locations outside plotting area */
	  if (xval < multigrapher->x_trans.input_min - FUZZ * xrange
	      || xval > multigrapher->x_trans.input_max + FUZZ * xrange)
	    continue;

	  /* Plot the abscissa tick labels. */
	  if (!multigrapher->y_axis.switch_axis_end
	      && !(multigrapher->grid_spec == AXES_AT_ORIGIN
		   /* don't plot label if it could run into an axis */
		   && NEAR_EQUALITY (xval, multigrapher->x_axis.other_axis_loc,
				     multigrapher->x_trans.input_range)
		   && (multigrapher->y_axis.other_axis_loc != multigrapher->y_trans.input_min)
		   && (multigrapher->y_axis.other_axis_loc != multigrapher->y_trans.input_max)))
	    /* print labels below bottom boundary */
	    {
	      pl_fmove_r (multigrapher->plotter,
			  XV (xval),
			  YN (multigrapher->y_axis.other_axis_loc)
			  - (SS ((multigrapher->tick_size >= 0.0 ? 0.75 : 1.75) * fabs(multigrapher->tick_size))
			     + multigrapher->half_line_width));
	      print_tick_label (labelbuf,
				&multigrapher->x_axis, &multigrapher->x_trans,
				(multigrapher->x_axis.type == A_LOG10) ? pow (10.0, xval) : xval);
	      pl_alabel_r (multigrapher->plotter, 'c', 't', labelbuf);
	      multigrapher->x_axis.labelled_ticks++;
	    }
	  else
	    /* print labels above top boundary */
	    if (multigrapher->y_axis.switch_axis_end
	      && !(multigrapher->grid_spec == AXES_AT_ORIGIN
		   /* don't plot label if it could run into an axis */
		   && NEAR_EQUALITY (xval, multigrapher->x_axis.other_axis_loc,
				     multigrapher->x_trans.input_range)
		   && (multigrapher->y_axis.other_axis_loc != multigrapher->y_trans.input_min)
		   && (multigrapher->y_axis.other_axis_loc != multigrapher->y_trans.input_max)))
	      {
		pl_fmove_r (multigrapher->plotter,
			    XV (xval),
			    YN (multigrapher->y_axis.alt_other_axis_loc)
			    + (SS ((multigrapher->tick_size >= 0.0 ? 0.75 : 1.75) * fabs(multigrapher->tick_size))
			     + multigrapher->half_line_width));
		print_tick_label (labelbuf,
				  &multigrapher->x_axis, &multigrapher->x_trans,
				  (multigrapher->x_axis.type == A_LOG10) ? pow (10.0, xval) : xval);
		pl_alabel_r (multigrapher->plotter, 'c', 'b', labelbuf);
		multigrapher->x_axis.labelled_ticks++;
	      }

	  /* Plot the abscissa tick marks, and vertical grid lines. */
	  switch (multigrapher->grid_spec)
	    {
	    case AXES_AND_BOX_AND_GRID:
	      pl_linemod_r (multigrapher->plotter, "dotted");
	      pl_fmove_r (multigrapher->plotter, XV(xval), YP(YSQ(0.0)));
	      pl_fcont_r (multigrapher->plotter, XV(xval), YP(YSQ(1.0)));
	      pl_linemod_r (multigrapher->plotter, "solid");
	      /* fall through */
	    case AXES_AND_BOX:
            case BOX:
	      if (!multigrapher->y_axis.switch_axis_end)
		{
		  pl_fmove_r (multigrapher->plotter,
			      XV (xval),
			      YN (multigrapher->y_axis.alt_other_axis_loc));
		  pl_fcont_r (multigrapher->plotter,
			      XV (xval),
			      YN (multigrapher->y_axis.alt_other_axis_loc)
			      - (SS (multigrapher->tick_size)
				 + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width
				    : -multigrapher->half_line_width)));
		}
	      else
		{
		  pl_fmove_r (multigrapher->plotter,
			      XV (xval),
			      YN (multigrapher->y_axis.other_axis_loc));
		  pl_fcont_r (multigrapher->plotter,
			      XV (xval),
			      YN (multigrapher->y_axis.other_axis_loc)
			      + (SS (multigrapher->tick_size)
				 + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width
				    : -multigrapher->half_line_width)));
		}
	      /* fall through */
	    case AXES:
	    case AXES_AT_ORIGIN:
	      if (!multigrapher->y_axis.switch_axis_end)
		{
		  pl_fmove_r (multigrapher->plotter,
			      XV (xval),
			      YN (multigrapher->y_axis.other_axis_loc));
		  pl_fcont_r (multigrapher->plotter,
			      XV (xval),
			      YN (multigrapher->y_axis.other_axis_loc)
			      + (SS (multigrapher->tick_size)
				 + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width
				    : -multigrapher->half_line_width)));
		}
	      else
		{
		  pl_fmove_r (multigrapher->plotter,
			      XV (xval),
			      YN (multigrapher->y_axis.alt_other_axis_loc));
		  pl_fcont_r (multigrapher->plotter,
			      XV (xval),
			      YN (multigrapher->y_axis.alt_other_axis_loc)
			      - (SS (multigrapher->tick_size)
				 + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width
				    : -multigrapher->half_line_width)));
		}
	      break;
	    default:		/* shouldn't happen */
	      break;
	    }
	}

      if (multigrapher->x_axis.have_lin_subticks)
	{
	  double subtick_size;	/* libplot coordinates */

	  /* linearly spaced subticks on log axes are as long as reg. ticks */
	  subtick_size = (multigrapher->x_axis.type == A_LOG10
			  ? SS(multigrapher->tick_size) : SS(multigrapher->subtick_size));

	  /* Plot the linearly spaced subtick marks on the abscissa */
	  for (i = multigrapher->x_axis.min_lin_subtick_count; i <= multigrapher->x_axis.max_lin_subtick_count; i++)
	    /* tick range can be empty */
	    {
	      xval = i * multigrapher->x_axis.lin_subtick_spacing;

	      /* discard subtick locations outside plotting area */
	      if (xval < multigrapher->x_trans.input_min - FUZZ * xrange
		  || xval > multigrapher->x_trans.input_max + FUZZ * xrange)
		continue;

	      switch (multigrapher->grid_spec)
		{
		case AXES_AND_BOX_AND_GRID:
		case AXES_AND_BOX:
                case BOX:
		  /* draw on both sides */
		  if (!multigrapher->y_axis.switch_axis_end)
		    {
		      pl_fmove_r (multigrapher->plotter,
				  XV (xval),
				  YN (multigrapher->y_axis.alt_other_axis_loc));
		      pl_fcont_r (multigrapher->plotter,
				  XV (xval),
				  YN (multigrapher->y_axis.alt_other_axis_loc)
				  - (subtick_size
				     + (subtick_size > 0.0 ? multigrapher->half_line_width
					: -multigrapher->half_line_width)));
		    }
		  else
		    {
		      pl_fmove_r (multigrapher->plotter,
				  XV (xval),
				  YN (multigrapher->y_axis.other_axis_loc));
		      pl_fcont_r (multigrapher->plotter,
				  XV (xval),
				  YN (multigrapher->y_axis.other_axis_loc)
				  + (subtick_size
				     + (subtick_size > 0.0 ? multigrapher->half_line_width
					: -multigrapher->half_line_width)));
		    }
		  /* fall through */
		case AXES:
		case AXES_AT_ORIGIN:
		  if (!multigrapher->y_axis.switch_axis_end)
		    /* draw on only one side */
		    {
		      pl_fmove_r (multigrapher->plotter,
				  XV (xval),
				  YN (multigrapher->y_axis.other_axis_loc));
		      pl_fcont_r (multigrapher->plotter,
				  XV (xval),
				  YN (multigrapher->y_axis.other_axis_loc)
				  + (subtick_size
				     + (subtick_size > 0.0 ? multigrapher->half_line_width
					: -multigrapher->half_line_width)));
		    }
		  else
		    {
		      pl_fmove_r (multigrapher->plotter,
				  XV (xval),
				  YN (multigrapher->y_axis.alt_other_axis_loc));
		      pl_fcont_r (multigrapher->plotter,
				  XV (xval),
				  YN (multigrapher->y_axis.alt_other_axis_loc)
				  - (subtick_size
				     + (subtick_size > 0.0 ? multigrapher->half_line_width
					: -multigrapher->half_line_width)));
		    }
		  break;
		default:		/* shouldn't happen */
		  break;
		}
	    }
	}

      /* plot a vertical dotted line at x = 0 */
      if (multigrapher->grid_spec != AXES_AT_ORIGIN
          && multigrapher->grid_spec != BOX
	  && multigrapher->x_axis.type == A_LINEAR
	  && multigrapher->x_trans.input_min * multigrapher->x_trans.input_max < 0.0)
	{
	  pl_linemod_r (multigrapher->plotter, "dotted");
	  pl_fline_r (multigrapher->plotter,
		      XV(0.0), YP(YSQ(0.0)), XV(0.0), YP(YSQ(1.0)));
	  pl_linemod_r (multigrapher->plotter, "solid");
	}
    }

  /* 4.  PLOT TICK MARKS, GRID LINES, AND TICK LABELS ON ORDINATE */

  if (multigrapher->grid_spec != NO_AXES && !multigrapher->y_axis.omit_ticks
      && !multigrapher->y_axis.user_specified_subsubticks)
    {
      int i;
      double yval, yrange = multigrapher->y_trans.input_max - multigrapher->y_trans.input_min;
      /* there is no way you could use longer labels on tick marks! */
      char labelbuf[2048];

      /* switch to our font for drawing y axis label and tick labels */
      pl_fontname_r (multigrapher->plotter, multigrapher->y_axis.font_name);
      pl_ffontsize_r (multigrapher->plotter, SS(multigrapher->y_axis.font_size));

      for (i = multigrapher->y_axis.min_tick_count; i <= multigrapher->y_axis.max_tick_count; i++)
	/* range can be empty */
	{
	  yval = i * multigrapher->y_axis.tick_spacing;

	  /* discard tick locations outside plotting area */
	  if (yval < multigrapher->y_trans.input_min - FUZZ * yrange
	      || yval > multigrapher->y_trans.input_max + FUZZ * yrange)
	    continue;

	  /* Plot the ordinate tick labels. */
	  if (!multigrapher->x_axis.switch_axis_end
	      && !(multigrapher->grid_spec == AXES_AT_ORIGIN
		   /* don't plot label if it could run into an axis */
		   && NEAR_EQUALITY (yval, multigrapher->y_axis.other_axis_loc,
				     multigrapher->y_trans.input_range)
		   && (multigrapher->x_axis.other_axis_loc != multigrapher->x_trans.input_min)
		   && (multigrapher->x_axis.other_axis_loc != multigrapher->x_trans.input_max)))
	    /* print labels to left of left boundary */
	    {
	      double new_width;

	      pl_fmove_r (multigrapher->plotter,
			  XN (multigrapher->x_axis.other_axis_loc)
			  - (SS((multigrapher->tick_size >= 0.0 ? 0.75 : 1.75)
				* fabs(multigrapher->tick_size))
			     + multigrapher->half_line_width),
			  YV (yval));
	      print_tick_label (labelbuf,
				&multigrapher->y_axis, &multigrapher->y_trans,
				(multigrapher->y_axis.type == A_LOG10) ? pow (10.0, yval) : yval);
	      new_width = pl_flabelwidth_r (multigrapher->plotter, labelbuf);
	      pl_alabel_r (multigrapher->plotter, 'r', 'c', labelbuf);
	      multigrapher->y_axis.max_label_width = DMAX(multigrapher->y_axis.max_label_width, new_width);
	      multigrapher->y_axis.labelled_ticks++;
	    }
	  else
	    /* print labels to right of right boundary */
	    if (multigrapher->x_axis.switch_axis_end
		&& !(multigrapher->grid_spec == AXES_AT_ORIGIN
		     /* don't plot label if it could run into an axis */
		     && NEAR_EQUALITY (yval, multigrapher->y_axis.other_axis_loc,
				       multigrapher->y_trans.input_range)
		     && (multigrapher->x_axis.other_axis_loc != multigrapher->x_trans.input_min)
		     && (multigrapher->x_axis.other_axis_loc != multigrapher->x_trans.input_max)))
	    {
	      double new_width;

	      pl_fmove_r (multigrapher->plotter,
			  XN (multigrapher->x_axis.alt_other_axis_loc)
			  + (SS((multigrapher->tick_size >= 0.0 ? 0.75 : 1.75)
				* fabs(multigrapher->tick_size))
			     + multigrapher->half_line_width),
			  YV (yval));
	      print_tick_label (labelbuf,
				&multigrapher->y_axis, &multigrapher->y_trans,
				(multigrapher->y_axis.type == A_LOG10) ? pow (10.0, yval) : yval);
	      new_width = pl_flabelwidth_r (multigrapher->plotter, labelbuf);
	      pl_alabel_r (multigrapher->plotter, 'l', 'c', labelbuf);
	      multigrapher->y_axis.max_label_width = DMAX(multigrapher->y_axis.max_label_width, new_width);
	      multigrapher->y_axis.labelled_ticks++;
	    }

	  /* Plot the tick marks on the y-axis, and horizontal grid lines. */
	  switch (multigrapher->grid_spec)
	    {
	    case AXES_AND_BOX_AND_GRID:
	      pl_linemod_r (multigrapher->plotter, "dotted");
	      pl_fmove_r (multigrapher->plotter, XP(XSQ(0.0)), YV (yval));
	      pl_fcont_r (multigrapher->plotter, XP(XSQ(1.0)), YV (yval));
	      pl_linemod_r (multigrapher->plotter, "solid");
	      /* fall through */
	    case AXES_AND_BOX:
            case BOX:
	      if (!multigrapher->x_axis.switch_axis_end)
		{
		  pl_fmove_r (multigrapher->plotter,
			      XN (multigrapher->x_axis.alt_other_axis_loc),
			      YV (yval));
		  pl_fcont_r (multigrapher->plotter,
			      XN (multigrapher->x_axis.alt_other_axis_loc)
			      - (SS (multigrapher->tick_size)
				 + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width
				    : -multigrapher->half_line_width)),
			      YV (yval));
		}
	      else
		{
		  pl_fmove_r (multigrapher->plotter,
			      XN (multigrapher->x_axis.other_axis_loc),
			      YV (yval));
		  pl_fcont_r (multigrapher->plotter,
			      XN (multigrapher->x_axis.other_axis_loc)
			      + (SS (multigrapher->tick_size)
				 + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width
				    : -multigrapher->half_line_width)),
			      YV (yval));
		}
	      /* fall through */
	    case AXES:
	    case AXES_AT_ORIGIN:
	      if (!multigrapher->x_axis.switch_axis_end)
		{
		  pl_fmove_r (multigrapher->plotter,
			      XN (multigrapher->x_axis.other_axis_loc),
			      YV (yval));
		  pl_fcont_r (multigrapher->plotter,
			      XN (multigrapher->x_axis.other_axis_loc)
			      + (SS (multigrapher->tick_size)
				 + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width
				    : -multigrapher->half_line_width)),
			      YV (yval));
		}
	      else
		{
		  pl_fmove_r (multigrapher->plotter,
			      XN (multigrapher->x_axis.alt_other_axis_loc),
			      YV (yval));
		  pl_fcont_r (multigrapher->plotter,
			      XN (multigrapher->x_axis.alt_other_axis_loc)
			      - (SS (multigrapher->tick_size)
				 + (multigrapher->tick_size > 0.0 ? multigrapher->half_line_width
				    : -multigrapher->half_line_width)),
			      YV (yval));
		}
	      break;
	    default:		/* shouldn't happen */
	      break;
	    }
	}

      if (multigrapher->y_axis.have_lin_subticks)
	{
	  double subtick_size;	/* libplot coordinates */

	  /* linearly spaced subticks on a log axis are as long as regular ticks */
	  subtick_size = (multigrapher->y_axis.type == A_LOG10
			  ? SS(multigrapher->tick_size) : SS(multigrapher->subtick_size));

	  /* Plot the linearly spaced subtick marks on the ordinate */
	  for (i = multigrapher->y_axis.min_lin_subtick_count; i <= multigrapher->y_axis.max_lin_subtick_count; i++)
	    /* range can be empty */
	    {
	      yval = i * multigrapher->y_axis.lin_subtick_spacing;

	      /* discard subtick locations outside plotting area */
	      if (yval < multigrapher->y_trans.input_min - FUZZ * yrange
		  || yval > multigrapher->y_trans.input_max + FUZZ * yrange)
		continue;

	      /* Plot the tick marks on the y-axis, and horizontal grid lines. */
	      switch (multigrapher->grid_spec)
		{
		case AXES_AND_BOX_AND_GRID:
		case AXES_AND_BOX:
                case BOX:
		  if (!multigrapher->x_axis.switch_axis_end)
		    {
		      pl_fmove_r (multigrapher->plotter,
				  XN (multigrapher->x_axis.alt_other_axis_loc),
				  YV (yval));
		      pl_fcont_r (multigrapher->plotter,
				  XN (multigrapher->x_axis.alt_other_axis_loc)
				  - (subtick_size
				     + (subtick_size > 0.0 ? multigrapher->half_line_width
					: -multigrapher->half_line_width)),
				  YV (yval));
		    }
		  else
		    {
		      pl_fmove_r (multigrapher->plotter,
				  XN (multigrapher->x_axis.other_axis_loc),
				  YV (yval));
		      pl_fcont_r (multigrapher->plotter,
				  XN (multigrapher->x_axis.other_axis_loc)
				  + (subtick_size
				     + (subtick_size > 0.0 ? multigrapher->half_line_width
					: -multigrapher->half_line_width)),
				  YV (yval));
		    }
		  /* fall through */
		case AXES:
		case AXES_AT_ORIGIN:
		  if (!multigrapher->x_axis.switch_axis_end)
		    {
		      pl_fmove_r (multigrapher->plotter,
				  XN (multigrapher->x_axis.other_axis_loc),
				  YV (yval));
		      pl_fcont_r (multigrapher->plotter,
				  XN (multigrapher->x_axis.other_axis_loc)
				  + (subtick_size
				     + (subtick_size > 0.0 ? multigrapher->half_line_width
					: -multigrapher->half_line_width)),
				  YV (yval));
		    }
		  else
		    {
		      pl_fmove_r (multigrapher->plotter,
				  XN (multigrapher->x_axis.alt_other_axis_loc),
				  YV (yval));
		      pl_fcont_r (multigrapher->plotter,
				  XN (multigrapher->x_axis.alt_other_axis_loc)
				  - (subtick_size
				     + (subtick_size > 0.0 ? multigrapher->half_line_width
					: -multigrapher->half_line_width)),
				  YV (yval));
		    }
		  break;
		default:		/* shouldn't happen */
		  break;
		}
	    }
	}

      /* plot a horizontal dotted line at y = 0 */
      if (multigrapher->grid_spec != AXES_AT_ORIGIN
          && multigrapher->grid_spec != BOX
	  && multigrapher->y_axis.type == A_LINEAR
	  && multigrapher->y_trans.input_min * multigrapher->y_trans.input_max < 0.0)
	{
	  pl_linemod_r (multigrapher->plotter, "dotted");
	  pl_fline_r (multigrapher->plotter,
		      XP(XSQ(0.0)), YV(0.0), XP(XSQ(1.0)), YV(0.0));
	  pl_linemod_r (multigrapher->plotter, "solid");
	}
    }

  /* 5.  DRAW LOGARITHMIC SUBSUBTICKS AND THEIR LABELS ON ABSCISSA */

  /* first, draw normal logarithmic subsubticks if any */
  if (multigrapher->grid_spec != NO_AXES && multigrapher->x_axis.have_normal_subsubticks
      && !multigrapher->x_axis.user_specified_subsubticks && !multigrapher->x_axis.omit_ticks)
    {
      int i, m, imin, imax;
      double xval, xrange = multigrapher->x_trans.input_max - multigrapher->x_trans.input_min;

      /* compute an integer range (of powers of 10) large enough to include
	 the entire desired axis */
      imin = (int)(floor (multigrapher->x_trans.input_min - FUZZ * xrange));
      imax = (int)(ceil (multigrapher->x_trans.input_max + FUZZ * xrange));

      for (i = imin; i < imax; i++)
	{
	  for (m = 1; m <= 9 ; m++)
	    {
	      xval = i + log10 ((double)m);

	      /* Plot subsubtick and label, if desired. */
	      /* N.B. if tick is outside axis range, nothing will be printed */
	      plot_abscissa_log_subsubtick (multigrapher, xval);
	    }
	}
    }

  /* second, draw user-specified logarithmic subsubticks instead, if any */
  if (multigrapher->grid_spec != NO_AXES && multigrapher->x_axis.user_specified_subsubticks
      && !multigrapher->x_axis.omit_ticks)
    {
      int i, imin, imax;
      double xval, xrange = multigrapher->x_trans.input_max - multigrapher->x_trans.input_min;

      /* compute an integer range large enough to include the entire
	 desired axis */
      imin = (int)(floor (pow (10.0, multigrapher->x_trans.input_min - FUZZ * xrange)
			  / multigrapher->x_axis.subsubtick_spacing));
      imax = (int)(ceil (pow (10.0, multigrapher->x_trans.input_max + FUZZ * xrange)
			 / multigrapher->x_axis.subsubtick_spacing));

      /* draw user-specified subsubticks */
      for (i = imin; i <= imax; i++)
	{
	  xval = log10 (i * multigrapher->x_axis.subsubtick_spacing);

	  /* Plot subsubtick and label, if desired. */
	  /* N.B. if tick is outside axis range, nothing will be printed */
	  plot_abscissa_log_subsubtick (multigrapher, xval);
	}
    }

  /* 6.  DRAW LOGARITHMIC SUBSUBTICKS AND THEIR LABELS ON ORDINATE */

  /* first, draw normal logarithmic subsubticks if any */
  if (multigrapher->grid_spec != NO_AXES && multigrapher->y_axis.have_normal_subsubticks
      && !multigrapher->y_axis.user_specified_subsubticks && !multigrapher->y_axis.omit_ticks)
    {
      int i, m, imin, imax;
      double yval, yrange = multigrapher->y_trans.input_max - multigrapher->y_trans.input_min;

      /* compute an integer range (of powers of 10) large enough to include
	 the entire desired axis */
      imin = (int)(floor (multigrapher->y_trans.input_min - FUZZ * yrange));
      imax = (int)(ceil (multigrapher->y_trans.input_max + FUZZ * yrange));

      /* draw normal subticks */
      for (i = imin; i < imax; i++)
	{
	  for (m = 1; m <= 9; m++)
	    {
	      yval = i + log10 ((double)m);

	      /* Plot subsubtick and label, if desired. */
	      /* N.B. if tick is outside axis range, nothing will be printed */
	      plot_ordinate_log_subsubtick (multigrapher, yval);
	    }
	}
    }

  /* second, draw user-specified logarithmic subsubticks instead, if any */
  if (multigrapher->grid_spec != NO_AXES && multigrapher->y_axis.user_specified_subsubticks
      && !multigrapher->y_axis.omit_ticks)
    {
      int i, imin, imax;
      double yval, yrange = multigrapher->y_trans.input_max - multigrapher->y_trans.input_min;

      /* compute an integer range large enough to include the entire
	 desired axis */
      imin = (int)(floor (pow (10.0, multigrapher->y_trans.input_min - FUZZ * yrange)
			  / multigrapher->y_axis.subsubtick_spacing));
      imax = (int)(ceil (pow (10.0, multigrapher->y_trans.input_max + FUZZ * yrange)
			 / multigrapher->y_axis.subsubtick_spacing));

      /* draw user-specified subsubticks */
      for (i = imin; i <= imax; i++)
	{
	  yval = log10 (i * multigrapher->y_axis.subsubtick_spacing);

	  /* Plot subsubtick and label, if desired. */
	  /* N.B. if tick is outside axis range, nothing will be printed */
	  plot_ordinate_log_subsubtick (multigrapher, yval);
	}
    }

  /* 7.  DRAW THE ABSCISSA LABEL */

      if ((multigrapher->grid_spec != NO_AXES)
      && multigrapher->x_axis.label != NULL && multigrapher->x_axis.label != '\0')
    {
      double x_axis_font_size;
      double xloc;

      /* switch to our font for drawing x axis label and tick labels */
      pl_fontname_r (multigrapher->plotter, multigrapher->x_axis.font_name);
      x_axis_font_size = pl_ffontsize_r (multigrapher->plotter,
					 SS(multigrapher->x_axis.font_size));

      if (multigrapher->grid_spec != AXES_AT_ORIGIN)
	/* center the label on the axis */
	xloc = 0.5 * (multigrapher->x_trans.input_max + multigrapher->x_trans.input_min);
      else
	{
	  if ((multigrapher->y_axis.other_axis_loc == multigrapher->y_trans.input_min)
	      || (multigrapher->y_axis.other_axis_loc == multigrapher->y_trans.input_max))

	    xloc = 0.5 * (multigrapher->x_trans.input_max + multigrapher->x_trans.input_min);
	  else
	    /* center label in the larger of the two halves */
	    xloc =
	      multigrapher->x_trans.input_max-multigrapher->x_axis.other_axis_loc >= multigrapher->x_axis.other_axis_loc-multigrapher->x_trans.input_min ?
		0.5 * (multigrapher->x_trans.input_max + multigrapher->x_axis.other_axis_loc) :
		  0.5 * (multigrapher->x_axis.other_axis_loc + multigrapher->x_trans.input_min);
	}

      if (!multigrapher->y_axis.switch_axis_end) /* axis on bottom, label below it */
	{
	  pl_fmove_r (multigrapher->plotter,
		      XV (xloc),
		      YN (multigrapher->y_axis.other_axis_loc)
		      - (SS ((multigrapher->tick_size >= 0.0 ? 0.875 : 2.125)
			     * fabs(multigrapher->tick_size))
			 + (6 * x_axis_font_size)/5
			 + multigrapher->half_line_width));
	  pl_alabel_r (multigrapher->plotter,
		       'c', 't', multigrapher->x_axis.label);
	}
      else			/* axis on top, label above it */
	{
	  pl_fmove_r (multigrapher->plotter,
		      XV (xloc),
		      YN (multigrapher->y_axis.alt_other_axis_loc)
		      + (SS ((multigrapher->tick_size >= 0.0 ? 0.875 : 2.125)
			     * fabs(multigrapher->tick_size))
			 + (6 * x_axis_font_size)/5
			 + multigrapher->half_line_width));
	  pl_alabel_r (multigrapher->plotter,
		       'c', 'b', multigrapher->x_axis.label);
	}
    }

  /* 8.  DRAW THE ORDINATE LABEL */

  if ((multigrapher->grid_spec != NO_AXES)
      && (multigrapher->y_axis.label != NULL && *(multigrapher->y_axis.label) != '\0'))
    {
      double y_axis_font_size;
      double yloc;

      /* switch to our font for drawing y axis label and tick labels */
      pl_fontname_r (multigrapher->plotter, multigrapher->y_axis.font_name);
      y_axis_font_size = pl_ffontsize_r (multigrapher->plotter,
					 SS(multigrapher->y_axis.font_size));

      if (multigrapher->grid_spec != AXES_AT_ORIGIN)
	/* center the label on the axis */
	yloc = 0.5 * (multigrapher->y_trans.input_min + multigrapher->y_trans.input_max);
      else
	{
	  if ((multigrapher->x_axis.other_axis_loc == multigrapher->x_trans.input_min)
	      || (multigrapher->x_axis.other_axis_loc == multigrapher->x_trans.input_max))
	    yloc = 0.5 * (multigrapher->y_trans.input_min + multigrapher->y_trans.input_max);
	  else
	    /* center label in the larger of the two halves */
	    yloc =
	      multigrapher->y_trans.input_max-multigrapher->y_axis.other_axis_loc >= multigrapher->y_axis.other_axis_loc-multigrapher->y_trans.input_min ?
		0.5 * (multigrapher->y_trans.input_max + multigrapher->y_axis.other_axis_loc) :
		  0.5 * (multigrapher->y_axis.other_axis_loc + multigrapher->y_trans.input_min);
	}

/* a relic of temps perdus */
#define libplot_has_font_metrics 1

      if (!multigrapher->x_axis.switch_axis_end)
	{
	  pl_fmove_r (multigrapher->plotter,
		      XN (multigrapher->x_axis.other_axis_loc)
		      - (libplot_has_font_metrics ?
			 (SS((multigrapher->tick_size >= 0.0 ? 0.75 : 1.75)
			     * fabs(multigrapher->tick_size))
			  + 1.15 * multigrapher->y_axis.max_label_width
			  + 0.5 * y_axis_font_size
			  + multigrapher->half_line_width)
			 : (SS((multigrapher->tick_size >= 0.0 ? 0.75 : 1.75)
			       * fabs(multigrapher->tick_size)) /* backup */
			    + 1.0 * y_axis_font_size
			    + multigrapher->half_line_width)),
		      YV(yloc));

	  if (libplot_has_font_metrics
	      && !multigrapher->no_rotate_y_label) /* can rotate label */
	    {
	      pl_textangle_r (multigrapher->plotter, 90);
	      pl_alabel_r (multigrapher->plotter,
			   'c', 'x', multigrapher->y_axis.label);
	      pl_textangle_r (multigrapher->plotter, 0);
	    }
	  else
	    /* non-rotated axis label, right justified */
	    pl_alabel_r (multigrapher->plotter,
			 'r', 'c', multigrapher->y_axis.label);
	}
      else
	{
	  pl_fmove_r (multigrapher->plotter,
		      XN (multigrapher->x_axis.alt_other_axis_loc)
		      + (libplot_has_font_metrics ?
			 (SS((multigrapher->tick_size >= 0.0 ? 0.75 : 1.75)
			     * fabs(multigrapher->tick_size))
			  + 1.15 * multigrapher->y_axis.max_label_width
			  + 0.5 * y_axis_font_size
			  + multigrapher->half_line_width)
			 : (SS((multigrapher->tick_size >= 0.0 ? 0.75 : 1.75)
			       * fabs(multigrapher->tick_size)) /* backup */
			    + 1.0 * y_axis_font_size
			    + multigrapher->half_line_width)),
		      YV(yloc));

	  if (libplot_has_font_metrics
	      && !multigrapher->no_rotate_y_label) /* can rotate label */
	    {
	      pl_textangle_r (multigrapher->plotter, 90);
	      pl_alabel_r (multigrapher->plotter,
			   'c', 't', multigrapher->y_axis.label);
	      pl_textangle_r (multigrapher->plotter, 0);
	    }
	  else
	    /* non-rotated axis label, left justified */
	    pl_alabel_r (multigrapher->plotter,
			 'l', 'c', multigrapher->y_axis.label);
	}
    }

  /* END OF TASKS */

  /* flush frame to device */
  pl_flushpl_r (multigrapher->plotter);

  pl_restorestate_r (multigrapher->plotter);

  if (multigrapher->grid_spec != NO_AXES)
    {
      if (!tick_warning_printed &&
	  ((!multigrapher->x_axis.omit_ticks && multigrapher->x_axis.labelled_ticks <= 2)
	   || (!multigrapher->y_axis.omit_ticks && multigrapher->y_axis.labelled_ticks <= 2)))
	{
	  fprintf (stderr,
		   "%s: the tick spacing is adjusted, as there were too few labelled axis ticks\n",
		   progname);
	  tick_warning_printed = true;
	}
    }
}



/* plot_abscissa_log_subsubtick() and plot_ordinate_log_subsubtick() are
   called to plot both normal log subticks and special (user-requested)
   ones */

/* ARGS: xval = log of location */
static void
plot_abscissa_log_subsubtick (Multigrapher *multigrapher, double xval)
{
  double xrange = multigrapher->x_trans.input_max - multigrapher->x_trans.input_min;
  /* there is no way you could use longer labels on tick marks! */
  char labelbuf[2048];
  double tick_size = SS(multigrapher->tick_size); /* for positioning labels */
  double subsubtick_size = SS(multigrapher->subtick_size);

  /* switch to our font for drawing x axis label and tick labels */
  pl_fontname_r (multigrapher->plotter, multigrapher->x_axis.font_name);
  pl_ffontsize_r (multigrapher->plotter, SS(multigrapher->x_axis.font_size));

  /* discard subsubtick locations outside plotting area */
  if (xval < multigrapher->x_trans.input_min - FUZZ * xrange
      || xval > multigrapher->x_trans.input_max + FUZZ * xrange)
    return;

  /* label subsubtick if it seems appropriate */
  if (multigrapher->x_axis.user_specified_subsubticks)
    {
      print_tick_label (labelbuf,
			&multigrapher->x_axis, &multigrapher->x_trans,
			pow (10.0, xval));
      if (!multigrapher->y_axis.switch_axis_end)
	{
	  pl_fmove_r (multigrapher->plotter,
		      XV (xval),
		      YN (multigrapher->y_axis.other_axis_loc)
		      - ((tick_size >= 0 ? 0.75 : 1.75)
			 * fabs((double)tick_size)
			 + multigrapher->half_line_width));
	  pl_alabel_r (multigrapher->plotter, 'c', 't', labelbuf);
	  multigrapher->x_axis.labelled_ticks++;
	}
      else
	{
	  pl_fmove_r (multigrapher->plotter,
		      XV (xval),
		      YN (multigrapher->y_axis.alt_other_axis_loc)
		      + ((tick_size >= 0 ? 0.75 : 1.75)
			 * fabs((double)tick_size)
			 + multigrapher->half_line_width));
	  pl_alabel_r (multigrapher->plotter, 'c', 'b', labelbuf);
	  multigrapher->x_axis.labelled_ticks++;
	}
    }

  /* draw subsubtick */
  switch (multigrapher->grid_spec)
    {
    case AXES_AND_BOX_AND_GRID:
      pl_linemod_r (multigrapher->plotter, "dotted");
      pl_fmove_r (multigrapher->plotter, XV (xval), YP(YSQ(0.0)));
      pl_fcont_r (multigrapher->plotter, XV (xval), YP(YSQ(1.0)));
      pl_linemod_r (multigrapher->plotter, "solid");
      /* fall through */
    case AXES_AND_BOX:
    case BOX:
      if (!multigrapher->y_axis.switch_axis_end)
	{
	  pl_fmove_r (multigrapher->plotter,
		      XV (xval),
		      YN (multigrapher->y_axis.alt_other_axis_loc));
	  pl_fcont_r (multigrapher->plotter,
		      XV (xval),
		      YN (multigrapher->y_axis.alt_other_axis_loc)
		      - (subsubtick_size
			 + (subsubtick_size > 0.0
			    ? multigrapher->half_line_width
			    : -multigrapher->half_line_width)));
	}
      else
	{
	  pl_fmove_r (multigrapher->plotter,
		      XV (xval),
		      YN (multigrapher->y_axis.other_axis_loc));
	  pl_fcont_r (multigrapher->plotter,
		      XV (xval),
		      YN (multigrapher->y_axis.other_axis_loc)
		      + (subsubtick_size
			 + (subsubtick_size > 0.0
			    ? multigrapher->half_line_width
			    : -multigrapher->half_line_width)));
	}
      /* fall through */
    case AXES:
    case AXES_AT_ORIGIN:
      if (!multigrapher->y_axis.switch_axis_end)
	{
	  pl_fmove_r (multigrapher->plotter,
		      XV (xval),
		      YN (multigrapher->y_axis.other_axis_loc));
	  pl_fcont_r (multigrapher->plotter,
		      XV (xval),
		      YN (multigrapher->y_axis.other_axis_loc)
		      + (subsubtick_size
			 + (subsubtick_size > 0.0
			    ? multigrapher->half_line_width
			    : -multigrapher->half_line_width)));
	}
      else
	{
	  pl_fmove_r (multigrapher->plotter,
		      XV (xval),
		      YN (multigrapher->y_axis.alt_other_axis_loc));
	  pl_fcont_r (multigrapher->plotter,
		      XV (xval),
		      YN (multigrapher->y_axis.alt_other_axis_loc)
		      - (subsubtick_size
			 + (subsubtick_size > 0.0
			    ? multigrapher->half_line_width
			    : -multigrapher->half_line_width)));
	}
      break;
    default:			/* shouldn't happen */
      break;
    }
}

/* ARGS: yval = log of location */
static void
plot_ordinate_log_subsubtick (Multigrapher *multigrapher, double yval)
{
  double yrange = multigrapher->y_trans.input_max - multigrapher->y_trans.input_min;
  /* there is no way you could use longer labels on tick marks! */
  char labelbuf[2048];
  double tick_size = SS(multigrapher->tick_size); /* for positioning labels */
  double subsubtick_size = SS(multigrapher->subtick_size);

  /* switch to our font for drawing y axis label and tick labels */
  pl_fontname_r (multigrapher->plotter, multigrapher->y_axis.font_name);
  pl_ffontsize_r (multigrapher->plotter, SS(multigrapher->y_axis.font_size));

  /* discard subsubtick locations outside plotting area */
  if (yval < multigrapher->y_trans.input_min - FUZZ * yrange
      || yval > multigrapher->y_trans.input_max + FUZZ * yrange)
    return;

  /* label subsubtick if it seems appropriate */
  if (multigrapher->y_axis.user_specified_subsubticks)
    {
      double new_width;

      print_tick_label (labelbuf,
			&multigrapher->y_axis, &multigrapher->y_trans,
			pow (10.0, yval));
      if (!multigrapher->x_axis.switch_axis_end)
	{
	  pl_fmove_r (multigrapher->plotter,
		      XN(multigrapher->x_axis.other_axis_loc)
		      - ((tick_size >= 0 ? 0.75 : 1.75)
			 * fabs((double)tick_size)
			 + multigrapher->half_line_width),
		      YV (yval));
	  new_width = pl_flabelwidth_r (multigrapher->plotter, labelbuf);
	  pl_alabel_r (multigrapher->plotter, 'r', 'c', labelbuf);
	  multigrapher->y_axis.max_label_width = DMAX(multigrapher->y_axis.max_label_width, new_width);
	  multigrapher->y_axis.labelled_ticks++;
	}
      else
	{
	  pl_fmove_r (multigrapher->plotter,
		      XN(multigrapher->x_axis.alt_other_axis_loc)
		      + ((tick_size >= 0 ? 0.75 : 1.75)
			 * fabs((double)tick_size)
			 + multigrapher->half_line_width),
		      YV (yval));
	  new_width = pl_flabelwidth_r (multigrapher->plotter, labelbuf);
	  pl_alabel_r (multigrapher->plotter, 'l', 'c', labelbuf);
	  multigrapher->y_axis.max_label_width = DMAX(multigrapher->y_axis.max_label_width, new_width);
	  multigrapher->y_axis.labelled_ticks++;
	}
    }

  /* draw subsubtick */
  switch (multigrapher->grid_spec)
    {
    case AXES_AND_BOX_AND_GRID:
      pl_linemod_r (multigrapher->plotter, "dotted");
      pl_fmove_r (multigrapher->plotter, XP(XSQ(0.0)), YV (yval));
      pl_fcont_r (multigrapher->plotter, XP(XSQ(1.0)), YV (yval));
      pl_linemod_r (multigrapher->plotter, "solid");
      /* fall through */
    case AXES_AND_BOX:
    case BOX:
      if (!multigrapher->x_axis.switch_axis_end)
	{
	  pl_fmove_r (multigrapher->plotter,
		      XN (multigrapher->x_axis.alt_other_axis_loc),
		      YV (yval));
	  pl_fcont_r (multigrapher->plotter,
		      XN (multigrapher->x_axis.alt_other_axis_loc)
		      - (subsubtick_size
			 + (subsubtick_size > 0.0
			    ? multigrapher->half_line_width
			    : -multigrapher->half_line_width)),
		      YV (yval));
	}
      else
	{
	  pl_fmove_r (multigrapher->plotter,
		      XN (multigrapher->x_axis.other_axis_loc),
		      YV (yval));
	  pl_fcont_r (multigrapher->plotter,
		      XN (multigrapher->x_axis.other_axis_loc)
		      + (subsubtick_size
			 + (subsubtick_size > 0.0
			    ? multigrapher->half_line_width
			    : -multigrapher->half_line_width)),
		      YV (yval));
	}
      /* fall through */
    case AXES:
    case AXES_AT_ORIGIN:
      if (!multigrapher->x_axis.switch_axis_end)
	{
	  pl_fmove_r (multigrapher->plotter,
		      XN (multigrapher->x_axis.other_axis_loc),
		      YV (yval));
	  pl_fcont_r (multigrapher->plotter,
		      XN (multigrapher->x_axis.other_axis_loc)
		      + (subsubtick_size
			 + (subsubtick_size > 0.0
			    ? multigrapher->half_line_width
			    : -multigrapher->half_line_width)),
		      YV (yval));
	}
      else
	{
	  pl_fmove_r (multigrapher->plotter,
		      XN (multigrapher->x_axis.alt_other_axis_loc),
		      YV (yval));
	  pl_fcont_r (multigrapher->plotter,
		      XN (multigrapher->x_axis.alt_other_axis_loc)
		      - (subsubtick_size
			 + (multigrapher->tick_size > 0.0
			    ? multigrapher->half_line_width
			    : -multigrapher->half_line_width)),
		      YV (yval));
	}
      break;
    default:	/* shouldn't happen */
      break;
    }
}


/* set_line_style() maps from line modes to physical line modes.  See
 * explanation at head of file. */

static void
set_line_style (Multigrapher *multigrapher, int style, const char *line_color,
                bool use_color)
{
  if (!use_color)		/* monochrome */
    {
      if (style > 0)
	/* don't issue pl_linemod_r() if style<=0, since no polyline will
           be drawn */
	{
	  int i;

	  i = (style - 1) % NO_OF_LINEMODES;
	  pl_linemod_r (multigrapher->plotter, linemodes[i]);
	}

      /* use same color as used for plot frame */
      pl_colorname_r (multigrapher->plotter, multigrapher->frame_color);
    }
  else				/* color */
    {
      int i, j;

      if (style > 0)		/* solid lines, various colors */
	{
          if (line_color && line_color[0])
            i = (style - 1) % NO_OF_LINEMODES;
          else
            {
              i = ((style - 1) / NO_OF_LINEMODES) % NO_OF_LINEMODES;
              j = (style - 1) % NO_OF_LINEMODES;
            }
          pl_linemod_r (multigrapher->plotter, linemodes[i]);
	}

      else if (style == 0)	/* use first color, as if -m 1 was spec'd */
				/* (no line will be drawn) */
	j = 0;

      else			/* neg. pl_linemode_r (no line will be drawn)*/
	j = (-style - 1) % (NO_OF_LINEMODES - 1);

      pl_colorname_r (multigrapher->plotter,
                      line_color && line_color[0] ? line_color
                                                  : colorstyle[j]);
    }
}


/* plot_point_array() calls plot_point() on each point in an array of
 * points.
 */

void
plot_point_array (Multigrapher *multigrapher, const Point *p, int length)
{
  int index;

  for (index = 0; index < length; index++)
    plot_point (multigrapher, &(p[index]));
}

void
plot_legend (Multigrapher *multigrapher, Legend *legends, int length, double x, double y, double font_size)
{
  if (length==0)
    return;

  pl_savestate_r (multigrapher->plotter);
  pl_fontname_r (multigrapher->plotter, multigrapher->x_axis.font_name);
  font_size = pl_ffontsize_r (multigrapher->plotter, SS(font_size));
  double legend_width = 0.;
  int i;
  for (i=0; i!=length; ++i)
    legend_width = fmax(legend_width, pl_flabelwidth_r(multigrapher->plotter, legends[i].label));
  double posx1 = XP(XSQ(1.0)) - legend_width - font_size*0.5;
  double posx0 = XP(XSQ(0.0)) + font_size*4.0;
  x = x * posx1 + (1-x) * posx0;
  double posy1 = YP(YSQ(1.0)) - font_size*1.0;
  double posy0 = YP(YSQ(0.0)) + font_size*length;
  y = y * posy1 + (1-y) * posy0;
  double xunscale = 1/(XV (1.) - XV (0.));
  double yunscale = 1/(YV (1.) - YV (0.));
  double dimx = font_size * xunscale;

/* white background behind legend */

  pl_savestate_r (multigrapher->plotter);
  /* use user-specified background color (if any) instead of white */
  if (pl_havecap_r (multigrapher->plotter, "SETTABLE_BACKGROUND") != 0
      && multigrapher->bg_color)
    pl_colorname_r (multigrapher->plotter, multigrapher->bg_color);
  else
    pl_colorname_r (multigrapher->plotter, "white");

  pl_filltype_r (multigrapher->plotter, 1);
  pl_fbox_r (multigrapher->plotter,
             x /* left of text */ - 3.5*dimx - 0.25*font_size
               /* left of marker */ - 2.5*dimx ,
             y - font_size*(length-0.25),
             x + legend_width + font_size*0.25,
             y + font_size*0.5);
  pl_restorestate_r (multigrapher->plotter);

  for (i=0; i!=length; ++i)
    {
/* legend marker */
      Point *p = &(legends[i].point);
      double ystep = -(i * font_size);
      int symbol = p->symbol;

      if (p->linemode > 0)
        {
          p->y = YVI(y) + ystep*yunscale;
          p->pendown = false;
          p->symbol = 0;
          p->x = XVI(x) - 3.5*dimx;
          plot_point (multigrapher, p);
          p->pendown = true;
          p->symbol = symbol;
          p->x = XVI(x) - 2*dimx;
          plot_point (multigrapher, p);
          p->x = XVI(x) - 0.5*dimx;
          p->symbol = 0;
          plot_point (multigrapher, p);
          p->pendown = false;
        }
      else
        {
          p->y = YVI(y) + ystep*yunscale;
          p->pendown = false;
          p->symbol = 0;
// @BUG not entirely clear: I get spurious dots if linemode is left to 0.
// $GRAPH --font-name HersheyCyrillic -C -T ps --reposition 0. 0. 1 -m 0 -S 74 0.05 --legend grey -c "#808080" data1 -W 0.005 --legend blue -c blue data2 --legend red%label -m 16 -c red data3 --place-legend 1 > plot.ps && evince plot.ps
          if (p->linemode == 0)
            p->linemode = -1;
          p->x = XVI(x) - 1.0*dimx;
          plot_point (multigrapher, p);
          p->pendown = true;
          p->symbol = symbol;
          plot_point (multigrapher, p);
          p->pendown = false;
        }
/* legend text */
      pl_fmove_r (multigrapher->plotter, x, y+ystep);
      pl_colorname_r (multigrapher->plotter, "black");
      pl_alabel_r (multigrapher->plotter, 'l', 'c', legends[i].label);
    }
  pl_restorestate_r (multigrapher->plotter);
}

/* plot_point() plots a single point, including the appropriate symbol and
 * errorbar(s) if any.  It may call either pl_fcont_r() or pl_fmove_r(),
 * depending on whether the pendown flag is set or not.  Gnuplot-style
 * clipping (clip mode = 0,1,2) is supported.
 *
 * plot_point() makes heavy use of the multigrapher->x_trans and
 * multigrapher->y_trans structures, which specify the linear
 * transformation from user coordinates to device coordinates.  It also
 * updates the multigrapher's internal state variables.  */

void
plot_point (Multigrapher *multigrapher, const Point *point)
{
  double local_x0, local_y0, local_x1, local_y1;
  int clipval;

  /* If new polyline is beginning, take its line style, color/monochrome
     attribute, and line width and fill fraction attributes from the first
     point of the polyline.  We assume all such attribute fields are the
     same for all points in the polyline (our point reader arranges this
     for us). */
  if (!(point->pendown) || multigrapher->first_point_of_polyline)
    {
      int intfill;

      set_line_style (multigrapher, point->linemode, point->line_color,
                      point->use_color);

      /* N.B. linewidth < 0.0 means use libplot default */
      pl_flinewidth_r (multigrapher->plotter,
		       point->line_width * (double)PLOT_SIZE);

      if (point->fill_fraction < 0.0)
	intfill = 0;		/* transparent */
      else			/* guaranteed to be <= 1.0 */
	intfill = 1 + IROUND((1.0 - point->fill_fraction) * 0xfffe);
      pl_filltype_r (multigrapher->plotter, intfill);
    }

  /* determine endpoints of new line segment (for the first point of a
     polyline, use a zero-length line segment) */
  if (multigrapher->first_point_of_polyline)
    {
      local_x0 = point->x;
      local_y0 = point->y;
    }
  else
    {
      local_x0 = multigrapher->oldpoint_x;
      local_y0 = multigrapher->oldpoint_y;
    }
  local_x1 = point->x;
  local_y1 = point->y;

  /* save current point for use as endpoint of next line segment */
  multigrapher->oldpoint_x = point->x;
  multigrapher->oldpoint_y = point->y;

  /* apply Cohen-Sutherland clipper to new line segment */
  clipval = clip_line (multigrapher,
		       &local_x0, &local_y0, &local_x1, &local_y1);

  if (!(clipval & ACCEPTED))	/* rejected in toto */
    {
      pl_fmove_r (multigrapher->plotter,
		  XV (point->x), YV (point->y)); /* move with pen up */
      multigrapher->first_point_of_polyline = false;
      return;
    }

  /* not rejected, ideally move with pen down */
  if (point->pendown && (point->linemode > 0))
    {
      switch (multigrapher->clip_mode) /* gnuplot style clipping (0,1, or 2) */
	{
	case 0:
	  if ((clipval & CLIPPED_FIRST) || (clipval & CLIPPED_SECOND))
	    /* clipped on at least one end, so move with pen up */
	    pl_fmove_r (multigrapher->plotter, XV (point->x), YV (point->y));
	  else
	    /* line segment within box, so move with pen down */
	    {
	      if (!multigrapher->first_point_of_polyline)
		pl_fcont_r (multigrapher->plotter,
			    XV (point->x), YV (point->y));
	      else
		pl_fmove_r (multigrapher->plotter,
			    XV (point->x), YV (point->y));
	    }
	  break;
	case 1:
	default:
	  if ((clipval & CLIPPED_FIRST) && (clipval & CLIPPED_SECOND))
	    /* both OOB, so move with pen up */
	    pl_fmove_r (multigrapher->plotter, XV (point->x), YV (point->y));
	  else
	    /* at most one point is OOB */
	    {
	      if (clipval & CLIPPED_FIRST) /*current pt. OOB, new pt. not OOB*/
		{
		  if (!multigrapher->first_point_of_polyline)
		    {
		      /* move to clipped current point, draw line segment */
		      pl_fmove_r (multigrapher->plotter,
				  XV (local_x0), YV (local_y0));
		      pl_fcont_r (multigrapher->plotter,
				  XV (point->x), YV (point->y));
		    }
		  else
		    pl_fmove_r (multigrapher->plotter,
				XV (point->x), YV (point->y));
		}
	      else		/* current point not OOB, new point OOB */
		{
		  if (!multigrapher->first_point_of_polyline)
		    {
		      /* draw line segment to clipped new point */
		      pl_fcont_r (multigrapher->plotter,
				  XV (local_x1), YV (local_y1));
		      /* N.B. lib's notion of position now differs from ours */
		    }
		  else
		    pl_fmove_r (multigrapher->plotter,
				XV (point->x), YV (point->y));
		}
	    }
	  break;
	case 2:
	  if ((clipval & CLIPPED_FIRST) || multigrapher->first_point_of_polyline)
	    /* move to clipped current point if necc. */
	    pl_fmove_r (multigrapher->plotter, XV (local_x0), YV (local_y0));

	  /* draw line segment to clipped new point */
	  pl_fcont_r (multigrapher->plotter, XV (local_x1), YV (local_y1));

	  if (clipval & CLIPPED_SECOND)
	    /* new point OOB, so move to new point, breaking polyline */
	    pl_fmove_r (multigrapher->plotter, XV (point->x), YV (point->y));
	  break;
	}
    }
  else				/* linemode=0 or pen up; so move with pen up */
    pl_fmove_r (multigrapher->plotter, XV (point->x), YV (point->y));

  multigrapher->first_point_of_polyline = false;

  /* if target point is OOB, return without plotting symbol or errorbar */
  if (clipval & CLIPPED_SECOND)
    return;

  /* plot symbol and errorbar, doing a pl_savestate_r()--pl_restorestate()
     to keep from breaking the polyline under construction (if any) */
  if (point->symbol >= 32)	/* yow, a character */
    {
      /* will do a font change, so save & restore state */
      pl_savestate_r (multigrapher->plotter);
      plot_errorbar (multigrapher, point);
      pl_fontname_r (multigrapher->plotter, point->symbol_font_name);
      pl_fmarker_r (multigrapher->plotter, XV(point->x), YV(point->y),
		    point->symbol, SS(point->symbol_size));
      pl_restorestate_r (multigrapher->plotter);
    }

  else if (point->symbol > 0)	/* a marker symbol */
    {
      if (point->linemode > 0)
	/* drawing a line, so (to keep from breaking it) save & restore state*/
	{
	  pl_savestate_r (multigrapher->plotter);
	  plot_errorbar (multigrapher, point); /* may or may not have one */
	  pl_fmarker_r (multigrapher->plotter, XV(point->x), YV(point->y),
			point->symbol, SS(point->symbol_size));
	  pl_restorestate_r (multigrapher->plotter);
	}
      else
	/* not drawing a line, so just place the marker */
	{
	  plot_errorbar (multigrapher, point);
	  pl_fmarker_r (multigrapher->plotter, XV(point->x), YV(point->y),
			point->symbol, SS(point->symbol_size));
	}
    }

  else if (point->symbol == 0 && point->linemode == 0)
    /* backward compatibility: -m 0 (even with -S 0) plots a dot */
    {
      plot_errorbar (multigrapher, point);
      pl_fmarker_r (multigrapher->plotter,
		    XV(point->x), YV(point->y), M_DOT, SS(point->symbol_size));
    }

  else				/* no symbol, but may be an errorbar */
    plot_errorbar (multigrapher, point);

  return;
}


/* clip_line() takes two points, the endpoints of a line segment, and
 * destructively passes back two points: the endpoints of the line segment
 * clipped by Cohen-Sutherland to the rectangular plotting area.  Return
 * value contains bitfields ACCEPTED, CLIPPED_FIRST, and CLIPPED_SECOND.
 */

static int
clip_line (Multigrapher *multigrapher, double *x0_p, double *y0_p, double *x1_p, double *y1_p)
{
  double x0 = *x0_p;
  double y0 = *y0_p;
  double x1 = *x1_p;
  double y1 = *y1_p;
  outcode outcode0 = compute_outcode (multigrapher, x0, y0, true);
  outcode outcode1 = compute_outcode (multigrapher, x1, y1, true);
  bool accepted;
  int clipval = 0;

  for ( ; ; )
    {
      if (!(outcode0 | outcode1)) /* accept */
	{
	  accepted = true;
	  break;
	}
      else if (outcode0 & outcode1) /* reject */
	{
	  accepted = false;
	  break;
	}
      else
	{
	  /* at least one endpoint is outside; choose one that is */
	  outcode outcode_out = (outcode0 ? outcode0 : outcode1);
	  double x, y;		/* intersection with clip edge */

	  if (outcode_out & RIGHT)
	    {
	      x = multigrapher->x_trans.input_max;
              y = isinf (x0) ? y1 : y0 + (y1 - y0) * (x - x0) / (x1 - x0);
	    }
	  else if (outcode_out & LEFT)
	    {
	      x = multigrapher->x_trans.input_min;
              y = isinf (x0) ? y1 : y0 + (y1 - y0) * (x - x0) / (x1 - x0);
	    }
	  else if (outcode_out & TOP)
	    {
              y = multigrapher->y_trans.input_max;
              x = isinf (y0) ? x1 : x0 + (x1 - x0) * (y - y0) / (y1 - y0);
	    }
	  else
	    {
              y = multigrapher->y_trans.input_min;
              x = isinf (y0) ? x1 : x0 + (x1 - x0) * (y - y0) / (y1 - y0);
	    }

	  if (outcode_out == outcode0)
	    {
	      x0 = x;
	      y0 = y;
	      outcode0 = compute_outcode (multigrapher, x0, y0, true);
	    }
	  else
	    {
	      x1 = x;
	      y1 = y;
	      outcode1 = compute_outcode (multigrapher, x1, y1, true);
	    }
	}
    }

  if (accepted)
    {
      clipval |= ACCEPTED;
      if ((x0 != *x0_p) || (y0 != *y0_p))
	clipval |= CLIPPED_FIRST;
      if ((x1 != *x1_p) || (y1 != *y1_p))
	clipval |= CLIPPED_SECOND;
      *x0_p = x0;
      *y0_p = y0;
      *x1_p = x1;
      *y1_p = y1;
    }

  return clipval;
}

/* Compute usual Cohen-Sutherland outcode, containing bitfields LEFT,
   RIGHT, BOTTOM, TOP.  Nine possibilities:
   {LEFT, interior, RIGHT} x {BOTTOM, interior, TOP}.
   The `tolerant' flag specifies how we handle points on the boundary. */
static outcode
compute_outcode (Multigrapher *multigrapher, double x, double y, bool tolerant)
{
  outcode code = 0;
  double xfuzz = FUZZ * multigrapher->x_trans.input_range;
  double yfuzz = FUZZ * multigrapher->y_trans.input_range;
  int sign = (tolerant == true ? 1 : -1);

  if (x > multigrapher->x_trans.input_max + sign * xfuzz)
    code |= RIGHT;
  else if (x < multigrapher->x_trans.input_min - sign * xfuzz)
    code |= LEFT;
  if (y > multigrapher->y_trans.input_max + sign * yfuzz)
    code |= TOP;
  else if (y < multigrapher->y_trans.input_min - sign * yfuzz)
    code |= BOTTOM;

  return code;
}

static void
transpose_portmanteau (int *val)
{
  bool xtrue, ytrue;
  int newval;

  xtrue = ((*val & X_AXIS) ? true : false);
  ytrue = ((*val & Y_AXIS) ? true : false);

  newval = (xtrue ? Y_AXIS : 0) | (ytrue ? X_AXIS : 0);
  *val = newval;
}

static void
plot_errorbar (Multigrapher *multigrapher, const Point *p)
{
  if (p->have_x_errorbar || p->have_y_errorbar)
    /* save & restore state, since we invoke pl_linemod_r() */
    {
      pl_savestate_r (multigrapher->plotter);
      pl_linemod_r (multigrapher->plotter, "solid");

      if (p->have_x_errorbar)
	{
	  pl_fline_r (multigrapher->plotter,
		      XV(p->xmin), YV(p->y) - 0.5 * SS(p->symbol_size),
		      XV(p->xmin), YV(p->y) + 0.5 * SS(p->symbol_size));
	  pl_fline_r (multigrapher->plotter,
		      XV(p->xmin), YV(p->y), XV(p->xmax), YV(p->y));
	  pl_fline_r (multigrapher->plotter,
		      XV(p->xmax), YV(p->y) - 0.5 * SS(p->symbol_size),
		      XV(p->xmax), YV(p->y) + 0.5 * SS(p->symbol_size));
	}
      if (p->have_y_errorbar)
	{
	  pl_fline_r (multigrapher->plotter,
		      XV(p->x) - 0.5 * SS(p->symbol_size), YV(p->ymin),
		      XV(p->x) + 0.5 * SS(p->symbol_size), YV(p->ymin));
	  pl_fline_r (multigrapher->plotter,
		      XV(p->x), YV(p->ymin), XV(p->x), YV(p->ymax));
	  pl_fline_r (multigrapher->plotter,
		      XV(p->x) - 0.5 * SS(p->symbol_size), YV(p->ymax),
		      XV(p->x) + 0.5 * SS(p->symbol_size), YV(p->ymax));
	}

      pl_restorestate_r (multigrapher->plotter);
    }
}

/* An alternative means of ending a polyline in progress.  Rather than
   ending it by passing plot_point() a point with the `pendown' flag not
   set, one may call this function.  This yields faster response in
   real-time work; e.g. in reader.c it is called by read_and_plot_file()
   after all dataset(s) have been read from the file and plotted. */

void
end_polyline_and_flush (Multigrapher *multigrapher)
{
  pl_endpath_r (multigrapher->plotter);
  pl_flushpl_r (multigrapher->plotter);
  multigrapher->first_point_of_polyline = true;
}

/*
  Local Variables:
  c-file-style: "gnu"
  tab-width: 8
  End:
*/
