#ifndef lint
static char *RCSid = "$Id: graphics.c,v 1.93 1995/12/20 22:39:34 drd Exp $";
#endif


/* GNUPLOT - graphics.c */
/*
 * Copyright (C) 1986 - 1993   Thomas Williams, Colin Kelley
 *
 * Permission to use, copy, and distribute this software and its
 * documentation for any purpose with or without fee is hereby granted, 
 * provided that the above copyright notice appear in all copies and 
 * that both that copyright notice and this permission notice appear 
 * in supporting documentation.
 *
 * Permission to modify the software is granted, but not the right to
 * distribute the modified code.  Modifications are to be distributed 
 * as patches to released version.
 *  
 * This software is provided "as is" without express or implied warranty.
 * 
 *
 * AUTHORS
 * 
 *   Original Software:
 *     Thomas Williams,  Colin Kelley.
 * 
 *   Gnuplot 2.0 additions:
 *       Russell Lang, Dave Kotz, John Campbell.
 *
 *   Gnuplot 3.0 additions:
 *       Gershon Elber and many others.
 *
 * There is a mailing list for gnuplot users. Note, however, that the
 * newsgroup 
 *	comp.graphics.gnuplot 
 * is identical to the mailing list (they
 * both carry the same set of messages). We prefer that you read the
 * messages through that newsgroup, to subscribing to the mailing list.
 * (If you can read that newsgroup, and are already on the mailing list,
 * please send a message info-gnuplot-request@dartmouth.edu, asking to be
 * removed from the mailing list.)
 *
 * The address for mailing to list members is
 *	   info-gnuplot@dartmouth.edu
 * and for mailing administrative requests is 
 *	   info-gnuplot-request@dartmouth.edu
 * The mailing list for bug reports is 
 *	   bug-gnuplot@dartmouth.edu
 * The list of those interested in beta-test versions is
 *	   info-gnuplot-beta@dartmouth.edu
 */

#include <math.h>
#include <assert.h>
#include <ctype.h>
#include "plot.h"
#include "setshow.h"

/* key placement is calculated in boundary, so we need file-wide variables
 * To simplify adjustments to the key, we set all these once [depends on
 * key_reverse] and use them throughout.
 */

/*{{{  local and global variables*/
static int key_sample_left;   /* offset from x for left of line sample */
static int key_sample_right;  /* offset from x for right of line sample */
static int key_point_offset;  /* offset from x for point sample */
static int key_text_left;     /* offset from x for left-justified text */
static int key_text_right;    /* offset from x for right-justified text */
static int key_size_left;     /* size of left bit of key (text or sample, depends on key_reverse) */
static int key_size_right;    /* size of right part of key (including padding) */

/* I think the following should also be static ?? */

static int key_xl, key_xr, key_yt, key_yb;  /* boundarys for key field */
static int max_ptitl_len = 0;	/* max length of plot-titles (keys) */
static int ktitl_lines = 0;	/* no lines in key_title (key header) */
static int ptitl_cnt;	/* count keys with len > 0  */
static int key_cols; /* no cols of keys */
static int key_rows, key_col_wth, yl_ref;


/* penalty for doing tics by callback in gen_tics is need for
 * global variables to communicate with the tic routines
 * Dont need to be arrays for this
 */
static int tic_start, tic_direction, tic_text, tic_just, tic_mirror;

/* set by tic_callback - how large to draw polar radii */
static double largest_polar_circle;

/* either xformat etc or invented time format
 * index with FIRST_X_AXIS etc
 * global because used in gen_tics, which graph3d also uses
 */
char ticfmt[8][25];
int timelevel[8];
double ticstep[8];

static int xlablin, x2lablin, ylablin, y2lablin, titlelin, xticlin, x2ticlin;
static unsigned int x2ticdelta; /* 0 for tic_in or no x2tics, ticscale*v_tic otherwise */
static int top_margin; /* in lines, excluding x2tics */

static int key_entry_height;  /* bigger of t->v_size, pointsize*t->v_tick */
static int p_width, p_height;  /* pointsize * { t->h_tic | t->v_tic } */

#if defined(DJGPP)||defined(sun386)
#define time_t unsigned long
#endif

/* there are several things on right of plot - key, y2tics and y2label
 * when working out boundary, save posn of y2label for later...
 * Same goes for x2label.
 * key posn is also stored in key_xl, and tics go at xright
 */
static int y2label_x;
/*}}}*/

/*{{{  static fns and local macros*/
static void plot_impulses __P((struct curve_points *plot, int yaxis_x, int xaxis_y));
static void plot_lines __P((struct curve_points *plot));
static void plot_points __P((struct curve_points *plot));
static void plot_dots __P((struct curve_points *plot));
static void plot_bars __P((struct curve_points *plot));
static void plot_boxes __P((struct curve_points *plot, int xaxis_y));
static void plot_vectors __P((struct curve_points *plot));
static void edge_intersect __P((struct coordinate GPHUGE *points, int i, double *ex, double *ey));
static int two_edge_intersect __P((struct coordinate GPHUGE *points, int i, double *lx, double *ly));
static TBOOLEAN two_edge_intersect_steps __P((struct coordinate GPHUGE *points, int i, double *lx, double *ly));

static void plot_steps __P((struct curve_points *plot));	/* JG */
static void plot_fsteps __P((struct curve_points *plot));	/* HOE */
static void edge_intersect_steps __P((struct coordinate GPHUGE *points, int i, double *ex, double *ey));    	/* JG */
static void edge_intersect_fsteps __P((struct coordinate GPHUGE *points, int i, double *ex, double *ey));     	/* HOE */
static TBOOLEAN two_edge_intersect_steps __P((struct coordinate GPHUGE *points, int i, double *lx, double *ly));	/* JG */
static TBOOLEAN two_edge_intersect_fsteps __P((struct coordinate GPHUGE *points, int i, double *lx, double *ly));

static double LogScale __P((double coord, int is_log, double log_base_log, char *what, char *axis));
static double dbl_raise __P((double x, int y));
static void boundary __P((int scaling, struct curve_points *plots, int count));
static double set_tic __P((double l10, int guide));
static double make_tics __P((int axis, int guide));
static int widest_tic;  /* widest2d_callback keeps longest so far in here */
static void widest2d_callback __P((int axis, double place, char *text, int grid));
static void ytick2d_callback __P((int axis, double place, char *text, int grid));
static void xtick2d_callback __P((int axis, double place, char *text, int grid));
static void map_position __P((struct position *pos, unsigned int *x, unsigned int *y, char *what));

#if defined(sun386) || defined(AMIGA_SC_6_1)
static double CheckLog __P((TBOOLEAN is_log, double base_log, double x));
#endif

/* for plotting error bars */
#define ERRORBARTIC (t->h_tic/2) /* half the width of error bar tic mark */

#ifndef max		/* Lattice C has max() in math.h, but shouldn't! */
#define max(a,b) ((a > b) ? a : b)
#endif

#ifndef min
#define min(a,b) ((a < b) ? a : b)
#endif

/*
 * The Amiga SAS/C 6.2 compiler moans about macro envocations causing
 * multiple calls to functions. I converted these macros to inline
 * functions coping with the problem without loosing speed.
 * If your compiler supports __inline, you should add it to the
 * #ifdef directive
 * (MGR, 1993)
 */

#ifdef AMIGA_SC_6_1
__inline static TBOOLEAN i_inrange(int z,int min,int max)
{
  return((min<max) ? ((z>=min)&&(z<=max)) : ((z>=max)&&(z<=min)));
}

__inline static double f_max(double a,double b)
{
  return(max(a,b));
}

__inline static double f_min(double a,double b)
{
  return(min(a,b));
}

#else
#define f_max(a,b) max((a),(b))
#define f_min(a,b) min((a),(b))
#define i_inrange(z,a,b) inrange((z),(a),(b))
#endif

#define inrange(z,min,max) ((min<max) ? ((z>=min)&&(z<=max)) : ((z>=max)&&(z<=min)) )

/* True if a and b have the same sign or zero (positive or negative) */
#define samesign(a,b) ((a) * (b) >= 0)
/*}}}*/

/*{{{  more variables*/
/* Define the boundary of the plot
 * These are computed at each call to do_plot, and are constant over
 * the period of one do_plot. They actually only change when the term
 * type changes and when the 'set size' factors change.
 * - no longer true, for 'set key out' or 'set key under'. also depend
 * on tic marks and multi-line labels.
 * They are shared with graph3d.c since we want to use its draw_clip_line()
 */
int xleft, xright, ybot, ytop;


/* we make a local copy of the 'key' variable so that if something
 * goes wrong, we can switch it off temporarily
 */

static int lkey;

/* First attempt at double axes...
 * x_min etc are now accessed from a global array min_array[], max_array[]
 * put the scale factors into a similar array
 * for convenience in this first attack on double axes, just define x_min etc
 * since code already uses x_min, etc  Eventually it will be done properly
 */

extern TBOOLEAN term_graphics, term_suspended;

extern double min_array[], max_array[];
extern int auto_array[];

extern int log_array[];
extern double base_array[], log_base_array[];

static int x_axis=FIRST_X_AXIS, y_axis=FIRST_Y_AXIS;  /* current axes */

static double scale[AXIS_ARRAY_SIZE];  /* scale factors for mapping for each axis */

/* BODGES BEFORE I FIX IT UP */
#define x_min min_array[x_axis]
#define x_max max_array[x_axis]
#define y_min min_array[y_axis]
#define y_max max_array[y_axis]

/* And the functions to map from user to terminal coordinates */
#define map_x(x) (int)(xleft+(x-min_array[x_axis])*scale[x_axis]+0.5) /* maps floating point x to screen */ 
#define map_y(y) (int)(ybot +(y-min_array[y_axis])*scale[y_axis]+0.5)	/* same for y */

/* (DFK) Watch for cancellation error near zero on axes labels */
#define SIGNIF (0.01)		/* less than one hundredth of a tic mark */
#define CheckZero(x,tic) (fabs(x) < ((tic) * SIGNIF) ? 0.0 : (x))
#define NearlyEqual(x,y,tic) (fabs((x)-(y)) < ((tic) * SIGNIF))
/*}}}*/

/*{{{  CheckLog()*/
/* (DFK) For some reason, the Sun386i compiler screws up with the CheckLog 
 * macro, so I write it as a function on that machine.
 *
 * Amiga SAS/C 6.2 thinks it will do too much work calling functions in
 * macro arguments twice, thus I inline theese functions. (MGR, 1993)
 * If your compiler doesn't handle those macros correctly, you should
 * also subscribe here. Even without inlining you gain speed with log plots
 */
#if defined(sun386) || defined(AMIGA_SC_6_1)
#ifdef AMIGA_SC_6_1
__inline
#endif
static double
CheckLog(is_log, base_log, x)
     TBOOLEAN is_log;
     double base_log;
     double x;
{
  if (is_log)
    return(pow(base_log, x));
  else
    return(x);
}
#else
/* (DFK) Use 10^x if logscale is in effect, else x */
#define CheckLog(is_log, base_log, x) ((is_log) ? pow(base_log, (x)) : (x))
#endif /* sun386 || SAS/C */
/*}}}*/

/*{{{  LogScale()*/
static double
LogScale(coord, is_log, log_base_log, what, axis)
	double coord;			/* the value */
	TBOOLEAN is_log;			/* is this axis in logscale? */
        double log_base_log;		/* if so, the log of its base */
	char *what;			/* what is the coord for? */
	char *axis;			/* which axis is this for ("x" or "y")? */
{
    if (is_log) {
	   if (coord <= 0.0) {
		  char errbuf[100];		/* place to write error message */
		(void) sprintf(errbuf,"%s has %s coord of %g; must be above 0 for log scale!",
				what, axis, coord);
		  graph_error(errbuf);
	   } else
		return(log(coord)/log_base_log);
    }
    return(coord);
}
/*}}}*/

/*{{{  graph_error()*/
/* handle errors during graph-plot in a consistent way */
void graph_error(text)
char *text;
{
	if (term_graphics) {
		if (multiplot && term_suspended && term->resume)
			(*term->resume)(); /* some drivers may rely on this ? */
		(term->text)();
		fflush(outfile);
		term_graphics = FALSE;
		term_suspended= FALSE;
		multiplot = FALSE;
	}
	int_error(text, NO_CARET);
}
/*}}}*/

/*{{{  widest2d_callback()*/
/* we determine widest tick label by getting gen_ticks to call this
 * routine with every label
 */

static void widest2d_callback(axis, place, text, grid)
int axis;
double place;
char *text;
int grid;
{
	int len=label_width(text, NULL);
	if (len>widest_tic)
		widest_tic = len;
}
/*}}}*/


/*{{{  boundary()*/
/* borders of plotting area
 * computed once on every call to do_plot
 *
 * The order in which things is done is getting pretty critical:
 *  ytop depends on title, x2label, ylabels (if no rotated text)
 *  ybot depends on key, if TUNDER
 *  once we have these, we can setup the y1 and y2 tics and the 
 *  only then can we calculate xleft and xright
 *  xright depends also on key TRIGHT
 *  then we can do x and x2 tics
 *
 * For set size square, everything depends on everything else...
 * not really a lot we can do about that, so we lose if the plot has to
 * be reduced vertically. But the chances are the
 * change will not be very big, so the number of tics will not
 * change dramatically.
 */
 
static void boundary(scaling,plots,count)
	TBOOLEAN scaling;	/* TRUE if terminal is doing the scaling */
	struct curve_points *plots;
	int count;
{
    int ytlen;
    register struct termentry *t = term;
    int key_h, key_w;
    int can_rotate=(*t->text_angle)(1);

    int yticlin=0, y2ticlin=0, timelin=0;

    lkey = key; /* but we may have to disable it later */

   xticlin = ylablin = y2lablin = xlablin = x2lablin = titlelin = top_margin = 0;

   /*{{{  count lines in labels and tics*/
   if (*title.text) label_width(title.text, &titlelin);
   if (*xlabel.text) label_width(xlabel.text, &xlablin);
   if (*x2label.text) label_width(x2label.text, &x2lablin);
   if (*ylabel.text) label_width(ylabel.text, &ylablin);
   if (*y2label.text) label_width(y2label.text, &y2lablin);
   if (xtics) label_width(xformat, &xticlin);
   if (x2tics) label_width(x2format, &x2ticlin);
   if (ytics) label_width(yformat, &yticlin);
   if (y2tics) label_width(y2format, &y2ticlin);
   if (*timelabel.text) label_width(timelabel.text, &timelin);
   /*}}}*/

	/*{{{  ytop*/
		
	if (tmargin >= 0)
		top_margin = tmargin;
	else {
		/* allow an extra one if both are present */
		top_margin = titlelin + x2lablin + (titlelin && x2lablin);
		
		if (*ylabel.text && !can_rotate && ylablin>top_margin)
			top_margin = ylablin;
		
		if (*y2label.text && !can_rotate && y2lablin>top_margin)
			top_margin = y2lablin;
	}
	
	if (x2tics&TICS_ON_BORDER) {
		/* ought to consider tics on axes if axis near border */
		top_margin += x2ticlin+0.5;
	}
	
	if (tmargin < 0 && !tic_in && (x2tics&TICS_ON_BORDER || xtics&TICS_MIRROR))
		x2ticdelta = (t->v_tic)*ticscale;
	else
		x2ticdelta = 0;
	
	ytop = (ysize+yoffset) * (t->ymax) - (t->v_char)*(top_margin+1) - x2ticdelta;
	/*}}}*/

	/*{{{  tentative xleft, needed for TUNDER*/
	xleft = xoffset * (t->xmax) + (lmargin > 0 ? (t->h_char)*lmargin : t->h_tic);
	/*}}}*/
	
	/*{{{  tentative xright, needed for TUNDER*/
	if (rmargin >= 0)
		xright = (xsize+xoffset) * (t->xmax) - (t->h_char)*rmargin;
	else
		xright = (xsize+xoffset) * (t->xmax) - (t->h_char)*2 - (t->h_tic);
	/*}}}*/
	
	/*{{{  ybot*/
	ybot = t->ymax * yoffset;
	
	if (bmargin >= 0)
		ybot += bmargin * (t->v_char);
	else {
		if (xtics&TICS_ON_BORDER)
			ybot += (t->v_char)*(xticlin+0.5);
		else
			ybot += (t->v_char)*0.5;
	
		if (*xlabel.text || (!can_rotate && *timelabel.text))
		{
			ybot += (t->v_char)*(((!can_rotate && (timelin > xlablin)) ? timelin : xlablin)+0.5);
		}
		
		if (!tic_in && (xtics&TICS_ON_BORDER || x2tics&TICS_MIRROR))
			ybot += (t->v_tic)*ticscale;
	
	}
	/*}}}*/

#define KEY_PANIC(x) if (x) { lkey=0; goto key_escape; }

	if (lkey) {
		/*{{{  essential key features*/
		p_width  = pointsize * t->h_tic;
		p_height = pointsize * t->v_tic;
		
		key_entry_height = p_height * 1.25;
		if (key_entry_height < (t->v_char) )
			key_entry_height = (t->v_char);
		
		/* count max_len key and number keys with len > 0 */
		max_ptitl_len = find_maxl_keys(plots,count,&ptitl_cnt);
		if ( (ytlen = label_width(key_title,&ktitl_lines)) > max_ptitl_len ) 
			max_ptitl_len = ytlen;
		
		if (key_reverse) {
			key_sample_left= -4*(t->h_char) - p_width;
			key_sample_right= 0;
			key_text_left=t->h_char;
			key_text_right=(t->h_char)*(max_ptitl_len+1);
			key_size_right=key_text_right + t->h_char;
			key_size_left=t->h_char - key_sample_left;  /* sample left is -ve */
			key_size_right=key_text_right + t->h_char;
		} else {
			key_sample_left= 0;
			key_sample_right= 4*(t->h_char) + p_width;
			key_text_left=-(t->h_char)*(max_ptitl_len+1);
			key_text_right=-(t->h_char);
			key_size_left=t->h_char - key_text_left;
			key_size_right=key_sample_right + t->h_char;
		}
		key_point_offset = (key_sample_left + key_sample_right)/2;
		
		/* advance width for cols */
		key_col_wth = key_size_left + key_size_right;
		
		key_rows = ptitl_cnt;
		key_cols = 1;
		
		/* calculate rows and cols for key - if something goes wrong,
		 * the tidiest way out is to  set lkey=0, and a goto
		 */
		 
		if (lkey == -1) {
			if (key_vpos == TUNDER ) {
				/* maximise no cols, limited by label-length */
				key_cols = (int)(xright - xleft)/key_col_wth;
				KEY_PANIC(key_cols == 0);
				key_rows = (int)(ptitl_cnt+key_cols-1)/key_cols;
				KEY_PANIC(key_rows==0);
				/* now calculate actual no cols depending on no rows */
				key_cols = (int)(ptitl_cnt+key_rows-1)/key_rows;
				KEY_PANIC(key_cols==0);
				key_col_wth = (int)(xright - xleft)/key_cols;
		 		/* we divide into columns, then centre in column by considering ratio
		 		 * of key_left_size to key_right_size
				 * key_size_left/(key_size_left+key_size_right) * (xright-xleft)/key_cols
				 * do one integer division to maximise accuracy (hope we dont overflow !)
				 */
				key_xl = xleft - key_size_left + ((xright-xleft)*key_size_left) / (key_cols*(key_size_left+key_size_right));
				key_xr = key_xl + key_col_wth * (key_cols-1) + key_size_left + key_size_right;
				key_yb = t->ymax * yoffset;
				key_yt = key_yb + key_rows * key_entry_height + ktitl_lines * t->v_char;
				ybot += key_entry_height*key_rows + t->v_char*(ktitl_lines+1);
			} else {
				/* maximise no rows, limited by ytop-ybot */
				int i = (int)(ytop-ybot-(ktitl_lines+1)*(t->v_char))/key_entry_height;
				KEY_PANIC(i==0);
				if (ptitl_cnt > i) {
					key_cols = (int)(ptitl_cnt+i-1)/i;
					/* now calculate actual no rows depending on no cols */
					KEY_PANIC(key_cols==0);
					key_rows = (int)(ptitl_cnt+key_cols-1)/key_cols;
				}
			}
			/* come here if we detect a division by zero in key calculations */
			key_escape:
				; /* ansi requires this */
		}
		/*}}}*/
	}


	/*{{{  set up y and y2 tics*/
	{
		int guide = (ytop-ybot)/term->v_char;
		if (ytics) setup_tics(FIRST_Y_AXIS, &yticdef, yformat, (int) (guide/yticlin));
		if (y2tics) setup_tics(SECOND_Y_AXIS, &y2ticdef, y2format, (int) (guide/y2ticlin));
	}
	/*}}}*/
	
	/*{{{  fix up xleft based on widest ytic, ylabels, etc*/
	if ( (lmargin == -1)) {
		if (ytics&TICS_ON_BORDER) {
			widest_tic=0; /* reset the global variable ... */
			/* get gen_tics to call widest2d_callback with all labels
			 * the latter sets widest_tic to the length of the widest one
			 * ought to consider tics on axis if axis near border...
			 */
			gen_tics(FIRST_Y_AXIS, &yticdef, 0, 0, 0.0, widest2d_callback);
	
			xleft += (t->h_char)*(widest_tic+3);
		}
	
		if (*ylabel.text || (can_rotate && *timelabel.text)) {
			xleft += (t->v_char)*(can_rotate && timelin>ylablin ? timelin : ylablin);
		}
	
			if (!tic_in && (ytics&TICS_ON_BORDER || y2tics&TICS_MIRROR))
				xleft += (t->h_tic)*ticscale;
	}
	/*}}}*/
	
	/*{{{  fix up xright based on widest y2tic. y2labels, key TOUT*/
	if ( rmargin<0 && lkey == -1 && key_hpos == TOUT ) {
		xright -= key_col_wth*key_cols; 
		key_xl = xright  + (t->h_tic);
	}
	
	/* fix up xright some more if y2label
	 * also, store y2label posn, since it is not trivial to
	 * work out again
	 */
	
	if ( *y2label.text) {
		if (can_rotate && rmargin<0)
			xright -= (t->v_char)*y2lablin;
		/* else it goes at top, so we dont need to move xright */
		
		y2label_x = xright; /* right or top justified to here */
	}
	
	/* fix up xright yet more if second tics */
	if (rmargin<0) {
		if (y2tics&TICS_ON_BORDER) {
			/* dont use map_x/map_y, so dont need to set x_axis/y_axis */
			widest_tic=0; /* reset the global variable */
			gen_tics(SECOND_Y_AXIS, &y2ticdef, 0, 0, 0.0, widest2d_callback);
			xright -= (widest_tic+1) * (t->h_char);
		}
	
		if (!tic_in && (y2tics&TICS_ON_BORDER || ytics&TICS_MIRROR))
			xright -= (t->h_tic)*ticscale;
	 }
	/*}}}*/
	
   /*{{{  set aspect ratio if required*/
   if (square) {
   	double current = ( (double) (ytop-ybot)) / ( (double) (xright-xleft) );
   	double required= ( (double) t->v_tic) / ( (double) t->h_tic);
   
   	if (current > required) {
   		/* too tall */
   		ytop = ybot + required * (xright-xleft);
   	} else {
    		int y2 = y2label_x - xright;
   		xright = xleft + (ytop-ybot)/required;
   		y2label_x = xright + y2;
   	}
   }
   /*}}}*/

	/*{{{  set up x and x2 tics*/
	/* we should base the guide on the width of the xtics, but we cannot
	 * use widest_tics until tics are set up. Bit of a downer - let us
	 * assume tics are 5 characters wide
	 */
	
	 {
	 	int guide = (xright - xleft) / (5*t->h_char);
	
		if (xtics) setup_tics(FIRST_X_AXIS, &xticdef, xformat, guide);
		if (x2tics) setup_tics(SECOND_X_AXIS, &x2ticdef, x2format, guide);
	 }
	/*}}}*/

	/* restore text to horizontal [we tested rotation above] */
	(void)(*t->text_angle)(0);

	/* needed for map_position() below */
	
	scale[FIRST_Y_AXIS] = (ytop - ybot)/(max_array[FIRST_Y_AXIS] - min_array[FIRST_Y_AXIS]);
	scale[FIRST_X_AXIS] = (xright - xleft)/(max_array[FIRST_X_AXIS] - min_array[FIRST_X_AXIS]);
	scale[SECOND_Y_AXIS] = (ytop - ybot)/(max_array[SECOND_Y_AXIS] - min_array[SECOND_Y_AXIS]);
	scale[SECOND_X_AXIS] = (xright - xleft)/(max_array[SECOND_X_AXIS] - min_array[SECOND_X_AXIS]);

   /*{{{  calculate the window in the grid for the key*/
   if (lkey==1 || (lkey==-1 && key_vpos != TUNDER)) {
   	/* calculate space for keys to prevent grid overwrite the keys */
   	/* do it even if there is no grid, as do_plot will use these to position key */
   	key_w = key_col_wth*key_cols;
   	key_h = (ktitl_lines) * t->v_char + key_rows * key_entry_height;
   	if (lkey == -1) {
   		if ( key_vpos == TTOP ) {
   			key_yt = (int) ytop - (t->v_tic);
   			key_yb = key_yt - key_h;
   		} else {
   			key_yb = ybot + (t->v_tic);
   			key_yt = key_yb + key_h;
   		}
   		if ( key_hpos == TLEFT ) {
   			key_xl = xleft + (t->h_char); /* for Left just */
   			key_xr = key_xl + key_w;
   		} else if (key_hpos == TRIGHT) {
   			key_xr = xright - (t->h_char);  /* for Right just */
   			key_xl = key_xr - key_w;
   		} else /* TOUT */ {
   			/* do this here for do_plot() */
   			/* align right first since rmargin may be manual */
   			key_xr = (xsize+xoffset) * (t->xmax) - (t->h_char);
   			key_xl = key_xr - key_w;
   		}
   	} else {
   		unsigned int x,y;
   		map_position(&key_user_pos, &x, &y, "key");
   		key_xl = x - key_size_left;
   		key_xr = key_xl + key_w;
   		key_yt = y + (ktitl_lines ? t->v_char : key_entry_height)/2;
   		key_yb = key_yt - key_h;
   	}	
   }
   /*}}}*/

}
/*}}}*/

/*{{{  dbl_raise()*/
static double dbl_raise(x,y)
double x;
int y;
{
register int i=abs(y);
double val=1.0;

	while (--i >= 0)
		val *= x;

	if (y < 0 ) return (1.0/val);
	return(val);
}
/*}}}*/

/*{{{  timetic_fmt()*/
void
timetic_format(axis,amin,amax)
int axis;
double amin, amax;
{
	double time_tic_just();
	struct tm tmin, tmax;

	*ticfmt[axis]=0;  /* make sure we strcat to empty string */

	ggmtime(&tmin,(double)time_tic_just(timelevel[axis],amin));
	ggmtime(&tmax,(double)time_tic_just(timelevel[axis],amax));
	/*
	if ( tmax.tm_year == tmin.tm_year && 
		tmax.tm_mon == tmin.tm_mon && 
		tmax.tm_mday == tmin.tm_mday ) {
	*/
	if ( tmax.tm_year == tmin.tm_year && 
		tmax.tm_yday == tmin.tm_yday ) {
		/* same day, skip date */
		if ( tmax.tm_hour != tmin.tm_hour ) {
			strcpy(ticfmt[axis],"%H");
		}
		if ( timelevel[axis] < 3 ) {
			if (strlen(ticfmt[axis]))
				strcat(ticfmt[axis],":");
			strcat(ticfmt[axis],"%M");
		}
		if ( timelevel[axis] < 2 ) {
			strcat(ticfmt[axis],":%S");
		}
	} else {
		if ( tmax.tm_year != tmin.tm_year ) {
			/* different years, include year in ticlabel */
			/* check convention, day/month or month/day */
			if (strchr(timefmt,'m') < strchr(timefmt,'d')) {
				strcpy(ticfmt[axis],"%m/%d/%");
			} else {
				strcpy(ticfmt[axis],"%d/%m/%");
			}
			if ( ((int)(tmax.tm_year/100)) != ((int)(tmin.tm_year/100)) ) {
				strcat(ticfmt[axis],"Y");
			} else {
				strcat(ticfmt[axis],"y");
			}
						 
		} else {
			if (strchr(timefmt,'m') < strchr(timefmt,'d')) {
				strcpy(ticfmt[axis],"%m/%d");
			} else {
				strcpy(ticfmt[axis],"%d/%m");
			}
		}
		if ( timelevel[axis] < 4 ) {
			strcat(ticfmt[axis],"\n%H:%M");
		}
	}
}
/*}}}*/

/*{{{  set_tic()*/
static double
set_tic(l10,guide)
double l10;
int guide;
{
	double xnorm, tics, posns;

	int fl=floor(l10);
	xnorm = pow(10.0,l10-fl); /* approx number of decades */

	posns = guide / xnorm;  /* approx number of tic posns per decade */
	
	if (posns > 10)
		tics = 0.2;  /* eg 0,0.2,0.4,... */
	else if (posns > 4)
		tics = 0.5; /* 0,0.5,1, */
	else if (posns > 1)
		tics = 1; /* 0,1,2,.... */
	else if (posns > 0.5)
		tics = 2; /* 0, 2, 4, 6 */
	else
		/* getting desperate... the ceil is to make sure we
		 * go over rather than under - eg plot [-10:10] x*x
		 * gives a range of about 99.999 - tics=xnorm gives
		 * tics at 0, 99.99 and 109.98  - BAD !
		 * This way, inaccuracy the other way will round
		 * up (eg 0->100.0001 => tics at 0 and 101
		 * I think latter is better than former
		 */
		tics = ceil(xnorm);

	return (tics * dbl_raise(10.0,fl));
}
/*}}}*/

/*{{{  make_tics()*/
static double make_tics(axis, guide)
int axis,guide;
{
	register double xr,tic,l10;

	xr = fabs(min_array[axis]-max_array[axis]);

	l10 = log10(xr);
	tic = set_tic(l10, guide);
	if (log_array[axis] && tic<1.0)
		tic=1.0;
	if (datatype[axis] == TIME) {
		struct tm ftm, etm;
		/* this is not fun */
		ggmtime(&ftm,(double)min_array[axis]);
		ggmtime(&etm,(double)max_array[axis]);
	
		timelevel[axis] = 0; /* seconds */
		if ( tic > 20 ) {
			/* turn tic into units of minutes */
			tic = set_tic(log10(xr/60.0),guide)*60;
			timelevel[axis] = 1; /* minutes */
		}
		if ( tic > 20*60 ) {
			/* turn tic into units of hours */
			tic = set_tic(log10(xr/3600.0),guide)*3600;
			timelevel[axis] = 2; /* hours */
		}
		if ( tic > 2*3600 ) {
			/* need some tickling */
			tic = set_tic(log10(xr/(3*3600.0)),guide)*3*3600;
		}
		if ( tic > 6*3600 ) {
			/* turn tic into units of days */
			tic = set_tic(log10(xr/DAY_SEC),guide)*DAY_SEC;
			timelevel[axis] = 3; /* days */
		}
		if ( tic > 3*DAY_SEC ) {
			/* turn tic into units of weeks */
			tic = set_tic(log10(xr/WEEK_SEC),guide)*WEEK_SEC;
			if ( tic < WEEK_SEC ) { /* force */
				tic = WEEK_SEC;
			}
			timelevel[axis] = 4; /* weeks */
		}
		if ( tic > 3*WEEK_SEC ) {
			/* turn tic into units of month */
			tic = set_tic(log10(xr/MON_SEC),guide)*MON_SEC;
			if ( tic < MON_SEC ) { /* force */
				tic = MON_SEC;
			}
			timelevel[axis] = 5; /* month */
		}
		if ( tic > 2*MON_SEC ) {
			/* turn tic into units of month */
			tic = set_tic(log10(xr/(3*MON_SEC)),guide)*3*MON_SEC;
		}
		if ( tic > 6*MON_SEC ) {
			/* turn tic into units of years */
			tic = set_tic(log10(xr/YEAR_SEC),guide)*YEAR_SEC;
			if ( tic < (YEAR_SEC/2) ) {
				tic = YEAR_SEC / 2;
			}
			timelevel[axis] = 6; /* year */
		}
	}
	return(tic);
}
/*}}}*/


void do_plot(plots, pcount)
struct curve_points *plots;
int pcount;			/* count of plots in linked list */
{

/* BODGES BEFORE I FIX IT UP */
#define ytic ticstep[y_axis]
#define xtic ticstep[x_axis]

register struct termentry *t = term;
register int curve;
int axis_zero[AXIS_ARRAY_SIZE]; /* axes in terminal coords for FIRST_X_AXIS, etc */
register struct curve_points *this_plot = NULL;
register int xl=0, yl=0; /* avoid gcc -Wall warning */
register int key_count=0;
			/* only a Pyramid would have this many registers! */
struct text_label *this_label;
struct arrow_def *this_arrow;
TBOOLEAN scaling;
char ss[MAX_LINE_LEN+1], *s, *e;

    /* so that macros for x_min etc pick up correct values
     * until this is done properly
     */
     
    x_axis=FIRST_X_AXIS; y_axis=FIRST_Y_AXIS;

/*	Apply the desired viewport offsets. */
     if (y_min < y_max) {
	    y_min -= boff;
	    y_max += toff;
	} else {
	    y_max -= boff;
	    y_min += toff;
	}
     if (x_min < x_max) {
	    x_min -= loff;
	    x_max += roff;
	} else {
	    x_max -= loff;
	    x_min += roff;
	}


/*	This used be x_max == x_min, but that caused an infinite loop once. */
	if (fabs(x_max - x_min) < zero)
		int_error("x_min should not equal x_max!",NO_CARET);
	if (fabs(y_max - y_min) < zero)
		int_error("y_min should not equal y_max!",NO_CARET);

		
/* INITIALIZE TERMINAL */
	if (!term_init) {
		(*t->init)();
		term_init = TRUE;
	}

	 /* compute boundary for plot (xleft, xright, ytop, ybot)
	  * also calculates tics, since xtics depend on xleft
	  * but xleft depends on ytics. Boundary calculations
	  * depend on term->v_char etc, so terminal must be
	  * initialised.
     */

	scaling = (*t->scale)(xsize, ysize);
 
	boundary(scaling,plots,pcount);

	screen_ok = FALSE;

	if (!term_graphics) {
		(*t->graphics)();
		term_graphics = TRUE;
	} else if (multiplot && term_suspended) {
		/* second plot of a multiplot, after interactive prompt */
		if (t->resume)
			(*t->resume)();
		term_suspended = FALSE;
	}
		
/* DRAW TICS AND GRID */
	(*t->linetype)(-2); /* border linetype */
	largest_polar_circle=0;

	/* select first mapping */
	x_axis=FIRST_X_AXIS; y_axis=FIRST_Y_AXIS;

	/* label first y axis tics */
	if (ytics) {
		int axis=map_x(ZERO);
		/* set the globals ytick2d_callback() needs */
		tic_just=RIGHT;
		if (ytics&TICS_MIRROR)
			tic_mirror=xright;
		else
			tic_mirror= -1; /* no thank you */

		if ( (ytics&TICS_ON_AXIS) && !log_array[FIRST_X_AXIS] && inrange(axis, xleft, xright)) {
			tic_start = axis;
			tic_direction=-1;
			/* put text at boundary if axis is close to boundary */
			tic_text = ( ( (tic_start-xleft) > (3*t->h_char) ) ? tic_start : xleft) - t->h_char;
		} else {
			tic_start=xleft;
			tic_direction=tic_in ? 1 : -1;
			tic_text=xleft-t->h_char;
			if (!tic_in) tic_text -= ticscale*(t->h_tic);
		}
		/* go for it */
		gen_tics(FIRST_Y_AXIS, &yticdef,
		   grid&(GRID_Y|GRID_MY),
		   mytics, mytfreq, ytick2d_callback);

	}

	/* label first x axis tics */
	if (xtics) {
		int axis=map_y(ZERO);
		/* set the globals xtick2d_callback() needs */
		tic_just=JUST_TOP;

		if (xtics&TICS_MIRROR)
			tic_mirror=ytop;
		else
			tic_mirror= -1; /* no thank you */
		if ( (xtics&TICS_ON_AXIS) && !log_array[FIRST_Y_AXIS] && inrange(axis, ybot, ytop)) {
			tic_start=axis;
			tic_direction=-1;
			/* put text at boundary if axis is close to boundary */
			if (tic_start - ybot > 2*t->v_char)
				tic_text = tic_start - ticscale*t->v_tic - t->v_char;
			else
				tic_text =  ybot - t->v_char;
		} else {
			tic_start=ybot;
			tic_direction=tic_in ? 1 : -1;
			tic_text=ybot-t->v_char;
			if (!tic_in)
				tic_text -= ticscale* t->v_tic;
		}
		/* go for it */
		gen_tics(FIRST_X_AXIS, &xticdef,
		   grid&(GRID_X|GRID_MX),
		   mxtics, mxtfreq, xtick2d_callback);
	}

	/* select second mapping */
	x_axis=SECOND_X_AXIS; y_axis=SECOND_Y_AXIS;

	/* label second y axis tics */
	if (y2tics) {
		/* set the globalss ytick2d_callback() needs */
		int axis=map_x(ZERO);
		tic_just=LEFT;
		if (y2tics&TICS_MIRROR)
			tic_mirror=xleft;
		else
			tic_mirror= -1; /* no thank you */
		if ((y2tics&TICS_ON_AXIS) && !log_array[FIRST_X_AXIS] && inrange(axis,xleft,xright)) {
			tic_start = axis;
			tic_direction=1;
			/* put text at boundary if axis is close to boundary */
			tic_text = ( ( (xright-tic_start) > (3*t->h_char) ) ? tic_start : xright) + t->h_char;
		} else {
			tic_start=xright;
			tic_direction=tic_in ? -1 : 1;
			tic_text=xright+t->h_char;
			if (!tic_in)
				tic_text += ticscale*(t->h_tic);
		}
		/* go for it */
		gen_tics(SECOND_Y_AXIS, &y2ticdef,
		  grid&(GRID_Y2|GRID_MY2),
		  my2tics, my2tfreq, ytick2d_callback);
	}

	/* label second x axis tics */
	if (x2tics) {
		int axis=map_y(ZERO);
		/* set the globals xtick2d_callback() needs */
		tic_just=JUST_BOT;
		if (x2tics&TICS_MIRROR)
			tic_mirror=ybot;
		else
			tic_mirror= -1; /* no thank you */
		if ( (x2tics&TICS_ON_AXIS) && !log_array[SECOND_Y_AXIS] && inrange(axis,ybot,ytop)) {
			tic_start=axis;
			tic_direction=1;
			/* put text at boundary if axis is close to boundary */
			tic_text = ( ( (ytop - tic_start ) > (2*t->v_char) ) ? tic_start : ytop) + t->v_char;
		} else {
			tic_start=ytop;
			tic_direction=tic_in ? -1 : 1;
			tic_text=ytop+t->v_char + x2ticdelta;
		}
		/* go for it */
		gen_tics(SECOND_X_AXIS, &x2ticdef,
		  grid&(GRID_X2|GRID_MX2),
		  mx2tics, mx2tfreq, xtick2d_callback);
	}

	/* select first mapping */
	x_axis=FIRST_X_AXIS; y_axis=FIRST_Y_AXIS;

/* RADIAL LINES FOR POLAR GRID */

	if (polar_grid_angle) {
		double theta=0;
		int ox=map_x(0);
		int oy=map_y(0);
		(*t->linetype)(grid_linetype);
		for (theta=0; theta<6.29; theta += polar_grid_angle)
			draw_clip_line(ox,oy,
			  map_x(largest_polar_circle*cos(theta)),
			  map_y(largest_polar_circle*sin(theta)));
	}


/* DRAW AXES */

   /* after grid so that axes linetypes are on top */

	x_axis=FIRST_X_AXIS; y_axis=FIRST_Y_AXIS; /* chose scaling */
	axis_zero[FIRST_X_AXIS] = map_y(0.0);
	axis_zero[FIRST_Y_AXIS] = map_x(0.0); 

	if (axis_zero[FIRST_X_AXIS] < ybot || is_log_y)
		axis_zero[FIRST_X_AXIS] = ybot;				/* save for impulse plotting */
	else if (axis_zero[FIRST_X_AXIS] >= ytop)
		axis_zero[FIRST_X_AXIS] = ytop ;
	else if (xzeroaxis > -3) {
		(*t->linetype)(xzeroaxis);
		(*t->move)(xleft,axis_zero[FIRST_X_AXIS]);
		(*t->vector)(xright,axis_zero[FIRST_X_AXIS]);
	}

	if ((yzeroaxis>-3) && !is_log_x && axis_zero[FIRST_Y_AXIS] >= xleft && axis_zero[FIRST_Y_AXIS] < xright ) {
		(*t->linetype)(yzeroaxis);
		(*t->move)(axis_zero[FIRST_Y_AXIS],ybot);
		(*t->vector)(axis_zero[FIRST_Y_AXIS],ytop);
	}

	x_axis=SECOND_X_AXIS; y_axis=SECOND_Y_AXIS; /* chose scaling */
	axis_zero[SECOND_X_AXIS] = map_y(0.0);
	axis_zero[SECOND_Y_AXIS] = map_x(0.0); 

	if (axis_zero[SECOND_X_AXIS] < ybot || is_log_y2)
		axis_zero[SECOND_X_AXIS] = ybot;				/* save for impulse plotting */
	else if (axis_zero[SECOND_X_AXIS] >= ytop)
		axis_zero[SECOND_X_AXIS] = ytop ;
	else if (x2zeroaxis>-3) {
		(*t->linetype)(x2zeroaxis);
		(*t->move)(xleft,axis_zero[SECOND_X_AXIS]);
		(*t->vector)(xright,axis_zero[SECOND_X_AXIS]);
	}

	if ((y2zeroaxis>-3) && !is_log_x2 && axis_zero[SECOND_Y_AXIS] >= xleft && axis_zero[SECOND_Y_AXIS] < xright ) {
		(*t->linetype)(y2zeroaxis);
		(*t->move)(axis_zero[SECOND_Y_AXIS],ybot);
		(*t->vector)(axis_zero[SECOND_Y_AXIS],ytop);
	}


/* DRAW PLOT BORDER */
	(*t->linetype)(-2); /* border linetype */
	if (draw_border) {
		(*t->move)(xleft,ybot);
		if(border_south){
			(*t->vector)(xright,ybot);
		} else {
			(*t->move)(xright,ybot);
		}
		if(border_east){
			(*t->vector)(xright,ytop);
		} else {
			(*t->move)(xright,ytop);
		}
		if(border_north){ 
			(*t->vector)(xleft,ytop);
		} else {
			(*t->move)(xleft,ytop);
		}
		if(border_west){ 
			(*t->vector)(xleft,ybot);
		} else {
			(*t->move)(xleft,ybot);
		}
	}

/* YLABEL */
    if (*ylabel.text) {
	strcpy(ss, ylabel.text);
	if ((*t->text_angle)(1)) {
		unsigned int x=(t->v_char)+xoffset*(t->xmax)+(ylabel.xoffset)*(t->v_char);
		unsigned int y=(ytop+ybot)/2 + ylabel.yoffset*(t->h_char);
		write_multiline(x,y,ss,CENTRE, JUST_TOP, 1, ylabel.font);
		(*t->text_angle)(0);
	} else {
		/* really bottom just, but we know number of lines */
		unsigned int x=(1+ylabel.xoffset)*(t->h_char) + xoffset*(t->xmax);
		unsigned int y=ytop+(ylablin+ylabel.yoffset)*(t->v_char);
		write_multiline(x,y,ss,LEFT,JUST_TOP, 0, ylabel.font);
	}
    }

/* Y2LABEL */
    if (*y2label.text) {
	strcpy(ss, y2label.text);
	if ((*t->text_angle)(1)) {
		/* we worked out posn in boundary() */
		unsigned int x=y2label_x+(y2label.xoffset+1)*(t->v_char);
		unsigned int y=(ytop+ybot)/2 + y2label.yoffset*(t->h_char);
		write_multiline(x,y,ss,CENTRE, JUST_TOP, 1, y2label.font);
		(*t->text_angle)(0);
	} else {
		/* really bottom just, but we know number of lines */
		unsigned int x= y2label_x + (y2label.xoffset)*(t->h_char);
		unsigned int y=ytop+(y2lablin+y2label.yoffset)*(t->v_char);
		write_multiline(x,y,ss,RIGHT,JUST_TOP, 0, y2label.font);
	}
    }

/* XLABEL */
    if (*xlabel.text) {
	unsigned int x=(xright+xleft)/2 + xlabel.xoffset*(t->h_char);
	unsigned int y=ybot - (t->v_char) + xlabel.yoffset*(t->v_char);
	if (xtics&TICS_ON_BORDER) {
		y -= xticlin*(t->v_char);
		if (!tic_in)
			y -= ticscale*(t->h_tic);
	}
	strcpy(ss, xlabel.text);
	write_multiline(x,y,ss,CENTRE,JUST_TOP, 0, xlabel.font);
    }
	
/* PLACE TITLE */
    if (*title.text) {
		unsigned int x = (xleft+xright)/2 + title.xoffset * t->h_char;
		unsigned int y = ytop + (t->v_char)*top_margin + x2ticdelta + title.yoffset * t->v_char;
		strcpy(ss, title.text);
		write_multiline(x,y,ss,CENTRE, JUST_TOP, 0, title.font);
    }

/* X2LABEL */
	if (*x2label.text) {
		unsigned int x=(xright+xleft)/2 + x2label.xoffset*(t->h_char);
		unsigned int y=  ytop + (t->v_char)*top_margin + x2ticdelta;
		if (*title.text)
			y -= (t->v_char)*(titlelin+0.5);
		strcpy(ss, x2label.text);
		write_multiline(x,y,ss,CENTRE,JUST_TOP, 0, x2label.font);
	}
		

/* PLACE TIMEDATE */
	if (*timelabel.text) {
		char str[MAX_LINE_LEN+1];
		time_t now;
		unsigned int x = (timelabel.xoffset+1)*t->h_char;
		unsigned int y = yoffset * t->ymax + (timelabel.yoffset+1)*t->v_char;

		time(&now);
		strftime(str, MAX_LINE_LEN, timelabel.text, localtime(&now));

		if ((*t->text_angle)(1)) {
			write_multiline(x,y,str,LEFT, JUST_TOP, 1, timelabel.font);
			(*t->text_angle)(0);
		} else {
			write_multiline(x,y,str,LEFT, JUST_BOT, 0, timelabel.font);
  		}
  	}

/* PLACE LABELS */
    for (this_label = first_label; this_label!=NULL;
			this_label=this_label->next ) {
		unsigned int x,y;
		map_position(&this_label->place, &x, &y, "label");
		strcpy(ss, this_label->text);
		write_multiline(x, y, ss, this_label->pos, JUST_TOP, 0, this_label->font);
 	 }

/* PLACE ARROWS */
    for (this_arrow = first_arrow; this_arrow!=NULL;
	    this_arrow = this_arrow->next ) {
	    	unsigned int sx,sy,ex,ey;
	    	map_position(&this_arrow->start, &sx, &sy, "arrow");
	    	map_position(&this_arrow->end, &ex, &ey, "arrow");

			(*t->linetype)(this_arrow->line-1);	/* arrow line type */
		   (*t->arrow)(sx, sy, ex, ey, this_arrow->head);
    }

/* WORK OUT KEY SETTINGS AND DO KEY TITLE / BOX */


	if (lkey) { /* may have been cancelled if something went wrong */
		/* just use key_xl etc worked out in boundary() */
		xl = key_xl + key_size_left;
		yl = key_yt;

#if 0
	}

	if (lkey != 0 && strlen(key_title)) {
		sprintf(ss,"%s\n",key_title);
		s = ss;
		yl -= t->v_char/2;
		while( (e=(char *)strchr(s,'\n')) != NULL ) {
			*e = '\0';
			if ( key_just == JLEFT) {
				(*t->justify_text)(LEFT);
				(*t->put_text)(xl+key_text_left,yl,s);
			} else {
				if ((*t->justify_text)(RIGHT)) {
					(*t->put_text)(xl+key_text_right,yl,s);
				} else {
					int x=xl+key_text_right-(t->h_char)*strlen(s);
					if (key_hpos == TOUT || inrange(x, xleft, xright))
						(*t->put_text)(x, yl,s);
				}
			}
			s = ++e;
			yl -= t->v_char;
		}
		yl += t->v_char/2;
	}
	yl_ref = yl -= key_entry_height/2;  /* centralise the keys */
	key_count = 0;

	if (lkey && key_box>-3) {
		(*t->linetype)(key_box);
		(*t->move)(key_xl,key_yb);
		(*t->vector)(key_xl,key_yt);
		(*t->vector)(key_xr,key_yt);
		(*t->vector)(key_xr,key_yb);
		(*t->vector)(key_xl,key_yb);
                /* draw a horizontal line between key title and first entry */   /* JFi */
		(*t->move)(key_xl,key_yt - (ktitl_lines) * t->v_char);   /* JFi */
                (*t->vector)(key_xr,key_yt - (ktitl_lines) * t->v_char); /* JFi */
	}
#else
 		if (*key_title) {
 			sprintf(ss,"%s\n",key_title);
 			s = ss;
 			yl -= t->v_char/2;
 			while( (e=(char *)strchr(s,'\n')) != NULL ) {
 				*e = '\0';
 				if ( key_just == JLEFT) {
 					(*t->justify_text)(LEFT);
 					(*t->put_text)(xl+key_text_left,yl,s);
  				} else {
 					if ((*t->justify_text)(RIGHT)) {
 						(*t->put_text)(xl+key_text_right,yl,s);
 					} else {
 						int x=xl+key_text_right-(t->h_char)*strlen(s);
 						if (key_hpos == TOUT || inrange(x, xleft, xright))
 							(*t->put_text)(x, yl,s);
 					}
 				}
 				s = ++e;
 				yl -= t->v_char;
 			}
 			yl += t->v_char/2;
 		}
 		yl_ref = yl -= key_entry_height/2;  /* centralise the keys */
 		key_count = 0;
 	
 		if (key_box>-3) {
 			(*t->linetype)(key_box);
 			(*t->move)(key_xl,key_yb);
 			(*t->vector)(key_xl,key_yt);
 			(*t->vector)(key_xr,key_yt);
 			(*t->vector)(key_xr,key_yb);
 			(*t->vector)(key_xl,key_yb);
                    /* draw a horizontal line between key title
                       and first entry                          */             /* JFi */
		        (*t->move)  (key_xl,key_yt - (ktitl_lines) * t->v_char); /* JFi */
                      (*t->vector)(key_xr,key_yt - (ktitl_lines) * t->v_char); /* JFi */
 		}
 	} /* lkey */
#endif

/* DRAW CURVES */

	this_plot = plots;
 	for (curve = 0; curve < pcount; this_plot = this_plot->next_cp, curve++) {
  		int localkey = lkey; /* a local copy */

		/* set scaling for this plot's axes */
		x_axis=this_plot->x_axis;
		y_axis=this_plot->y_axis;

		(*t->linetype)(this_plot->line_type);

		if (this_plot->title && !*this_plot->title) {
		    localkey = 0;
		} else {
			(*t->linetype)(this_plot->line_type);
			if (localkey != 0 && this_plot->title) {
  
				key_count++;
				if ( key_just == JLEFT) {
					(*t->justify_text)(LEFT);
					(*t->put_text)(xl+key_text_left,
							 yl,this_plot->title);
				} else {
					if ((*t->justify_text)(RIGHT)) {
						(*t->put_text)(xl+key_text_right,
							yl,this_plot->title);
					} else {
						int x=xl+key_text_right-(t->h_char)*strlen(this_plot->title);
						if (key_hpos == TOUT ||
						     i_inrange(x, xleft, xright))
				 		(*t->put_text)(x, yl,this_plot->title);
					}
				}
			}

		}
		switch(this_plot->plot_style) {
		    case IMPULSES: {
			   if (localkey != 0 && this_plot->title) {
				  (*t->move)(xl+key_sample_left,yl);
				  (*t->vector)(xl+key_sample_right,yl);
			   }
			   plot_impulses(this_plot, axis_zero[y_axis], axis_zero[x_axis]);
			   break;
		    }
		    case LINES: {
			   if (localkey != 0 && this_plot->title) {
				  (*t->move)(xl+key_sample_left,yl);
				  (*t->vector)(xl+key_sample_right,yl);
			   }
			   plot_lines(this_plot);
			   break;
		    }
/* JG */	    case STEPS: {
			   if (localkey != 0 && this_plot->title) {
				  (*t->move)(xl+key_sample_left,yl);
				  (*t->vector)(xl+key_sample_right,yl);
			   }
			   plot_steps(this_plot);
			   break;
		    }
/* JG */	    case FSTEPS: {
			   if (localkey != 0 && this_plot->title) {
				  (*t->move)(xl+key_sample_left,yl);
				  (*t->vector)(xl+key_sample_right,yl);
			   }
			   plot_fsteps(this_plot);
			   break;
		    }
		    case POINTSTYLE: {
			   if (localkey != 0 && this_plot->title) {
				  (*t->point)(xl+key_point_offset,yl,
						    this_plot->point_type);
			   }
			   plot_points(this_plot);
			   break;
		    }
  		    case LINESPOINTS: {
  			   /* put lines */
			   if (localkey != 0 && this_plot->title) {
				  (*t->move)(xl+key_sample_left,yl);
				  (*t->vector)(xl+key_sample_right,yl);
			   }
			   plot_lines(this_plot);

			   /* put points */
			   if (key != 0 && this_plot->title) {
				  (*t->point)(xl+key_point_offset,yl,
						    this_plot->point_type);
			   }
			   plot_points(this_plot);
			   break;
		    }
  		    case DOTS: {
			   if (localkey != 0 && this_plot->title) {
  				  (*t->point)(xl+key_point_offset,yl, -1);
  			   }
  			   plot_dots(this_plot);
			   break;
		    }
                    case YERRORBARS: {
                           if (localkey != 0 && this_plot->title) {
                                  (*t->point)(xl+key_point_offset,yl,
                                                    this_plot->point_type);
                           }
                           plot_points(this_plot);

                           /* for functions, just like POINTSTYLE */
                           if (this_plot->plot_type == DATA) {
                                  if (localkey != 0 && this_plot->title) {
                                         (*t->move)(xl+key_sample_left,yl);
                                         (*t->vector)(xl+key_sample_right,yl);
                                         if(bar_size>0.0){
                                            (*t->move)(xl+key_sample_left,yl+ERRORBARTIC);
                                            (*t->vector)(xl+key_sample_left,yl-ERRORBARTIC);
                                            (*t->move)(xl+key_sample_right,yl+ERRORBARTIC);
                                            (*t->vector)(xl+key_sample_right,yl-ERRORBARTIC);
                                         }
                                  }
                                  plot_bars(this_plot);
                           }
                           break;
                    }
                    case XERRORBARS: {
                           if (localkey != 0 && this_plot->title) {
                                  (*t->point)(xl+2*(t->h_char),yl,
                                                    this_plot->point_type);
                           }
                           plot_points(this_plot);

                           /* for functions, just like POINTSTYLE */
                           if (this_plot->plot_type == DATA) {
			   if (localkey != 0 && this_plot->title) {
                                         (*t->move)(xl+key_sample_left,yl);
                                         (*t->vector)(xl+key_sample_right,yl);
                                         if(bar_size > 0.0){
                                            (*t->move)(xl+key_sample_left,yl+ERRORBARTIC);
                                            (*t->vector)(xl+key_sample_left,yl-ERRORBARTIC);
                                            (*t->move)(xl+key_sample_right,yl+ERRORBARTIC);
                                            (*t->vector)(xl+key_sample_right,yl-ERRORBARTIC);
                                         }
                                  }
                                  plot_bars(this_plot);
                           }
                           break;
                    }
                    case XYERRORBARS: {
                           if (localkey != 0 && this_plot->title) {
				  (*t->point)(xl+2*(t->h_char),yl,
						    this_plot->point_type);
			   }
			   plot_points(this_plot);

			   /* for functions, just like POINTSTYLE */
			   if (this_plot->plot_type == DATA) {
				   if (localkey != 0 && this_plot->title) {
                                         (*t->move)(xl+key_sample_left,yl);
                                         (*t->vector)(xl+key_sample_right,yl);
                                         if(bar_size > 0.0){
                                            (*t->move)(xl+key_sample_left,yl+ERRORBARTIC);
                                            (*t->vector)(xl+key_sample_left,yl-ERRORBARTIC);
                                            (*t->move)(xl+key_sample_right,yl+ERRORBARTIC);
                                            (*t->vector)(xl+key_sample_right,yl-ERRORBARTIC);
                                         }
                                  }
				  plot_bars(this_plot);
			   }
			   break;
		    }
                    case BOXXYERROR: {
                           if (localkey != 0 && this_plot->title) {
                                  (*t->point)(xl+key_point_offset,yl,
                                                    this_plot->point_type);
                           }
                           plot_boxes(this_plot,axis_zero[x_axis]);
                           break;
                    }
			case BOXERROR: {
			   if (this_plot->plot_type == DATA) {
				   if (localkey != 0 && this_plot->title) {
                                         (*t->move)(xl+key_sample_left,yl);
                                         (*t->vector)(xl+key_sample_right,yl);
                                         if(bar_size > 0.0){
                                            (*t->move)(xl+key_sample_left,yl+ERRORBARTIC);
                                            (*t->vector)(xl+key_sample_left,yl-ERRORBARTIC);
                                            (*t->move)(xl+key_sample_right,yl+ERRORBARTIC);
                                            (*t->vector)(xl+key_sample_right,yl-ERRORBARTIC);
                                         }
                                  }
				  plot_bars(this_plot);
			   }
			}
			/* no break */
		    case BOXES: {
			   if (localkey != 0 && this_plot->title) {
				  (*t->move)(xl+key_sample_left,yl);
				  (*t->vector)(xl+key_sample_right,yl);
			   }
			   plot_boxes(this_plot,axis_zero[x_axis]);
			   break;
		    }

		    case VECTOR:
			   if (localkey != 0 && this_plot->title) {
				  (*t->move)(xl+key_sample_left,yl);
				  (*t->vector)(xl+key_sample_right,yl);
			   }
			   plot_vectors(this_plot);
			   break;
		}
		if (localkey && this_plot->title) {
		    if (key_count >= key_rows ) {
			yl = yl_ref;
			xl += key_col_wth;
			key_count = 0;
		    } else 
		    	yl = yl - key_entry_height;
		}
	}
	if (!multiplot){
		(*t->text)();
		term_graphics=FALSE;
	}
	(void) fflush(outfile);
}


/* BODGES */
#undef ytic
#undef xtic

/* plot_impulses:
 * Plot the curves in IMPULSES style
 */

static void
plot_impulses(plot, yaxis_x, xaxis_y)
	struct curve_points *plot;
	int yaxis_x, xaxis_y;
{
    int i;
    int x,y;
    struct termentry *t = term;

    for (i = 0; i < plot->p_count; i++) {
	   switch (plot->points[i].type) {
		  case INRANGE: {
			 x = map_x(plot->points[i].x);
			 y = map_y(plot->points[i].y);
			 break;
		  }
		  case OUTRANGE: {
			 if (!inrange(plot->points[i].x, x_min,x_max))
			   continue;
			 x = map_x(plot->points[i].x);
			 if ((y_min < y_max 
				 && plot->points[i].y < y_min)
				|| (y_max < y_min 
				    && plot->points[i].y > y_min))
			   y = map_y(y_min);
			 else
			   y = map_y(y_max);
			
			 break;
		  }
		  default:		/* just a safety */
		  case UNDEFINED: {
			 continue;
		  }
	   }
				    
	   if (polar)
	      (*t->move)(yaxis_x,xaxis_y);
	   else
	      (*t->move)(x,xaxis_y);
	   (*t->vector)(x,y);
    }

}

/* plot_lines:
 * Plot the curves in LINES style
 */
static void
plot_lines(plot)
	struct curve_points *plot;
{
    int i;				/* point index */
    int x,y;				/* point in terminal coordinates */
    struct termentry *t = term;
    enum coord_type prev = UNDEFINED; /* type of previous point */
    double ex, ey;			/* an edge point */
    double lx[2], ly[2];		/* two edge points */

    for (i = 0; i < plot->p_count; i++) {
	   switch (plot->points[i].type) {
		  case INRANGE: {
			 x = map_x(plot->points[i].x);
			 y = map_y(plot->points[i].y);

			 if (prev == INRANGE) {
				(*t->vector)(x,y);
			 } else if (prev == OUTRANGE) {
				/* from outrange to inrange */
				if (!clip_lines1) {
				    (*t->move)(x,y);
				} else {
				    edge_intersect(plot->points, i, &ex, &ey);
				    (*t->move)(map_x(ex), map_y(ey));
				    (*t->vector)(x,y);
				}
			 } else {		/* prev == UNDEFINED */
				(*t->move)(x,y);
				(*t->vector)(x,y);
			 }
				    
			 break;
		  }
		  case OUTRANGE: {
			 if (prev == INRANGE) {
				/* from inrange to outrange */
				if (clip_lines1) {
				    edge_intersect(plot->points, i, &ex, &ey);
				    (*t->vector)(map_x(ex), map_y(ey));
				}
			 } else if (prev == OUTRANGE) {
				/* from outrange to outrange */
				if (clip_lines2) {
				    if (two_edge_intersect(plot->points, i, lx, ly)) {
					   (*t->move)(map_x(lx[0]), map_y(ly[0]));
					   (*t->vector)(map_x(lx[1]), map_y(ly[1]));
				    }
				}
			 }
			 break;
		  }
		  default:		/* just a safety */
		  case UNDEFINED: {
			 break;
		  }
	   }
	   prev = plot->points[i].type;
    }
}

/* XXX - JG  */
/* plot_steps:				
 * Plot the curves in STEPS style
 */
static void
plot_steps(plot)
struct curve_points *plot;
{
    int i;				/* point index */
    int x,y;				/* point in terminal coordinates */
    struct termentry *t = term;
    enum coord_type prev = UNDEFINED;	/* type of previous point */
    double ex, ey;			/* an edge point */
    double lx[2], ly[2];		/* two edge points */
    int yprev=0;                 /* previous point coordinates */

    for (i = 0; i < plot->p_count; i++) {
	   switch (plot->points[i].type) {
		  case INRANGE: {
			 x = map_x(plot->points[i].x);
			 y = map_y(plot->points[i].y);

			 if (prev == INRANGE) {
				(*t->vector)(x,yprev);
				(*t->vector)(x,y);
			 } else if (prev == OUTRANGE) {
				/* from outrange to inrange */
				if (!clip_lines1) {
				    (*t->move)(x,y);
				} else {		/* find edge intersection */
				    edge_intersect_steps(plot->points, i, &ex, &ey);
				    (*t->move)(map_x(ex), map_y(ey));
				    (*t->vector)(x,map_y(ey));
				    (*t->vector)(x,y);
				}
			 } else {		/* prev == UNDEFINED */
				(*t->move)(x,y);
				(*t->vector)(x,y);
			 }
	   	 yprev = y;
			 break;
		  }
		  case OUTRANGE: {
			 if (prev == INRANGE) {
				/* from inrange to outrange */
				if (clip_lines1) {
				    edge_intersect_steps(plot->points, i, &ex, &ey);
				    (*t->vector)(map_x(ex), yprev);
				    (*t->vector)(map_x(ex), map_y(ey));
				}
			 } else if (prev == OUTRANGE) {
				/* from outrange to outrange */
				if (clip_lines2) {
				    if (two_edge_intersect_steps(plot->points, i, lx, ly)) {
					   (*t->move)(map_x(lx[0]), map_y(ly[0]));
					   (*t->vector)(map_x(lx[1]), map_y(ly[0]));
					   (*t->vector)(map_x(lx[1]), map_y(ly[1]));
				    }
				}
			 }
			 break;
		  }
		  default:		/* just a safety */
		  case UNDEFINED: {
			 break;
		  }
	   }
	   prev  = plot->points[i].type;
    }
}
/* XXX - HOE  */
/* plot_fsteps:				
 * Plot the curves in STEPS style by step on forward yvalue
 */
static void
plot_fsteps(plot)
struct curve_points *plot;
{
    int i;				/* point index */
    int x,y;				/* point in terminal coordinates */
    struct termentry *t = term;
    enum coord_type prev = UNDEFINED;	/* type of previous point */
    double ex, ey;			/* an edge point */
    double lx[2], ly[2];		/* two edge points */
    int xprev = 0;		/* previous point coordinates */

    for (i = 0; i < plot->p_count; i++) {
	   switch (plot->points[i].type) {
		  case INRANGE: {
			 x = map_x(plot->points[i].x);
			 y = map_y(plot->points[i].y);

			 if (prev == INRANGE) {
				(*t->vector)(xprev,y);
				(*t->vector)(x,y);
			 } else if (prev == OUTRANGE) {
				/* from outrange to inrange */
				if (!clip_lines1) {
				    (*t->move)(x,y);
				} else {		/* find edge intersection */
				    edge_intersect_fsteps(plot->points, i, &ex, &ey);
				    (*t->move)(map_x(ex), map_y(ey));
				    (*t->vector)(map_x(ex),y);
				    (*t->vector)(x,y);
				}
			 } else {		/* prev == UNDEFINED */
				(*t->move)(x,y);
				(*t->vector)(x,y);
			 }
			 xprev = x;
			 break;
		  }
		  case OUTRANGE: {
			 if (prev == INRANGE) {
				/* from inrange to outrange */
				if (clip_lines1) {
				    edge_intersect_fsteps(plot->points, i, &ex, &ey);
				    (*t->vector)(xprev,map_y(ey));
				    (*t->vector)(map_x(ex), map_y(ey));
				}
			 } else if (prev == OUTRANGE) {
				/* from outrange to outrange */
				if (clip_lines2) {
				    if (two_edge_intersect_fsteps(plot->points, i, lx, ly)) {
					   (*t->move)(map_x(lx[0]), map_y(ly[0]));
					   (*t->vector)(map_x(lx[0]), map_y(ly[1]));
					   (*t->vector)(map_x(lx[1]), map_y(ly[1]));
				    }
				}
			 }
			 break;
		  }
		  default:		/* just a safety */
		  case UNDEFINED: {
			 break;
		  }
	   }
	   prev  = plot->points[i].type;
    }
}


/* plot_bars:
 * Plot the curves in ERRORBARS style
 *  we just plot the bars; the points are plotted in plot_points
 */
static void
plot_bars(plot)
	struct curve_points *plot;
{
    int i;				/* point index */
    struct termentry *t = term;
    double x, y;                        /* position of the bar */
    double ylow, yhigh;		/* the ends of the bars */
    double xlow, xhigh;
    unsigned int xM, ylowM, yhighM; /* the mapped version of above */
    unsigned int yM, xlowM, xhighM;
    TBOOLEAN low_inrange, high_inrange;
    int tic = ERRORBARTIC;
    
/* Limitation: no boxes with x errorbars */

    if ((plot->plot_style == YERRORBARS) || (plot->plot_style == XYERRORBARS) ||
         (plot->plot_style == BOXERROR)){
/* Draw the vertical part of the bar */
    for (i = 0; i < plot->p_count; i++) {
	   /* undefined points don't count */
	   if (plot->points[i].type == UNDEFINED)
		continue;

	   /* check to see if in xrange */
	   x = plot->points[i].x;
	   if (! inrange(x, x_min, x_max))
		continue;
	   xM = map_x(x);

	   /* find low and high points of bar, and check yrange */
	   yhigh = plot->points[i].yhigh;
	   ylow = plot->points[i].ylow;

	   high_inrange = inrange(yhigh, y_min,y_max);
	   low_inrange = inrange(ylow, y_min,y_max);

	   /* compute the plot position of yhigh */
	   if (high_inrange)
		yhighM = map_y(yhigh);
	   else if (samesign(yhigh-y_max, y_max-y_min))
		yhighM = map_y(y_max);
	   else
		yhighM = map_y(y_min);
	   
	   /* compute the plot position of ylow */
	   if (low_inrange)
		ylowM = map_y(ylow);
	   else if (samesign(ylow-y_max, y_max-y_min))
		ylowM = map_y(y_max);
	   else
		ylowM = map_y(y_min);

	   if (!high_inrange && !low_inrange && ylowM == yhighM)
		/* both out of range on the same side */
		  continue;

	   /* by here everything has been mapped */
	   (*t->move)(xM, ylowM);
	   (*t->vector)(xM, yhighM); /* draw the main bar */
           if(bar_size > 0.0){
	   (*t->move)((unsigned int)(xM-bar_size*tic), ylowM); /* draw the bottom tic */
	   (*t->vector)((unsigned int)(xM+bar_size*tic), ylowM);
	   (*t->move)((unsigned int)(xM-bar_size*tic), yhighM); /* draw the top tic */
	   (*t->vector)((unsigned int)(xM+bar_size*tic), yhighM);
    }
    } /* for loop */
    } /* if yerrorbars OR xyerrorbars */

    if ((plot->plot_style == XERRORBARS) || (plot->plot_style == XYERRORBARS)){
/* Draw the horizontal part of the bar */
    for (i = 0; i < plot->p_count; i++) {
           /* undefined points don't count */
           if (plot->points[i].type == UNDEFINED)
                continue;

           /* check to see if in yrange */
           y = plot->points[i].y;
           if (! inrange(y, y_min, y_max))
                continue;
           yM = map_y(y);

           /* find low and high points of bar, and check xrange */
           xhigh = plot->points[i].xhigh;
           xlow = plot->points[i].xlow;

           high_inrange = inrange(xhigh, x_min,x_max);
           low_inrange = inrange(xlow, x_min,x_max);

           /* compute the plot position of xhigh */
           if (high_inrange)
                xhighM = map_x(xhigh);
           else if (samesign(xhigh-x_max, x_max-x_min))
                xhighM = map_x(x_max);
           else
                xhighM = map_x(x_min);

           /* compute the plot position of xlow */
           if (low_inrange)
                xlowM = map_x(xlow);
           else if (samesign(xlow-x_max, x_max-x_min))
                xlowM = map_x(x_max);
           else
                xlowM = map_x(x_min);

           if (!high_inrange && !low_inrange && xlowM == xhighM)
                /* both out of range on the same side */
                  continue;

           /* by here everything has been mapped */
           (*t->move)(xlowM, yM);
           (*t->vector)(xhighM, yM); /* draw the main bar */
           if(bar_size > 0.0){
              (*t->move)(xlowM, (unsigned int)(yM-bar_size*tic)); /* draw the left tic */
              (*t->vector)(xlowM, (unsigned int)(yM+bar_size*tic));
              (*t->move)(xhighM, (unsigned int)(yM-bar_size*tic)); /* draw the right tic */
              (*t->vector)(xhighM, (unsigned int)(yM+bar_size*tic));
           }
    } /* for loop */
    } /* if xerrorbars OR xyerrorbars */
}

/* plot_boxes:
 * Plot the curves in BOXES style
 */
static void
plot_boxes(plot,xaxis_y)
	struct curve_points *plot;
	int xaxis_y;
{
    int i;				/* point index */
    int xl,xr,yt;			/* point in terminal coordinates */
	double dxl,dxr,dyt;
    struct termentry *t = term;
    enum coord_type prev = UNDEFINED; /* type of previous point */
    TBOOLEAN boxxy = (plot->plot_style == BOXXYERROR);

    for (i = 0; i < plot->p_count; i++) {
	   switch (plot->points[i].type) {
		  case OUTRANGE:
		  case INRANGE: {
			if (plot->points[i].z<0.0) {
			   if (boxwidth<0.0) {
					/* calculate width */
					if (prev!=UNDEFINED)
						dxl = (plot->points[i-1].x - plot->points[i].x)/2.0;
					else
						dxl = 0.0;
					if (i < plot->p_count-1) {
						if (plot->points[i+1].type!=UNDEFINED)
							dxr = (plot->points[i+1].x - plot->points[i].x)/2.0;
						else
							dxr = -dxl;
					}
					else {
						dxr = -dxl;
					}
					if (prev==UNDEFINED)
						dxl = -dxr;
				}
				else {
					dxr = boxwidth/2.0;
					dxl = -dxr;
				}
			}
			else {
				dxr = plot->points[i].z/2.0;
				dxl = -dxr;
			}

                        if (boxxy) {
                                dxr = plot->points[i].xhigh;
                                dxl = plot->points[i].xlow;
                                dyt = plot->points[i].yhigh;
                                xaxis_y = map_y(plot->points[i].ylow);
                        }
                        else {
			dxl= plot->points[i].x+dxl;
			dxr= plot->points[i].x+dxr;
			dyt= plot->points[i].y;
                        }

			/* clip to border */
			if ((y_min < y_max  && dyt < y_min)
				|| (y_max < y_min  && dyt > y_min))
			   dyt = y_min;
			if ((y_min < y_max  && dyt > y_max)
				|| (y_max<y_min  && dyt < y_max))
			   dyt = y_max;
			if ((x_min < x_max  && dxr < x_min)
				|| (x_max < x_min  && dxr > x_min))
			   dxr = x_min;
			if ((x_min < x_max  && dxr > x_max)
				|| (x_max<x_min  && dxr < x_max))
			   dxr = x_max;
			if ((x_min < x_max  && dxl < x_min)
				|| (x_max < x_min  && dxl > x_min))
			   dxl = x_min;
			if ((x_min < x_max  && dxl > x_max)
				|| (x_max<x_min  && dxl < x_max))
			   dxl = x_max;

			xl= map_x(dxl);
			xr= map_x(dxr);
			yt = map_y(dyt);

			(*t->move)(xl,xaxis_y);
			(*t->vector)(xl,yt);
			(*t->vector)(xr,yt);
			(*t->vector)(xr,xaxis_y);
			(*t->vector)(xl,xaxis_y);
			break;
		  }
		  default:		/* just a safety */
		  case UNDEFINED: {
			 break;
		  }
	   }
	   prev = plot->points[i].type;
    }
}

/* plot_points:
 * Plot the curves in POINTSTYLE style
 */
static void
plot_points(plot)
	struct curve_points *plot;
{
    int i;
    int x,y;
    struct termentry *t = term;

    for (i = 0; i < plot->p_count; i++) {
	   if (plot->points[i].type == INRANGE) {
		  x = map_x(plot->points[i].x);
		  y = map_y(plot->points[i].y);
		  /* do clipping if necessary */
		  if (!clip_points ||
			 (   x >= xleft + p_width  && y >= ybot + p_height 
			  && x <= xright - p_width && y <= ytop - p_height))
		    (*t->point)(x,y, plot->point_type);
	   }
    }
}

/* plot_dots:
 * Plot the curves in DOTS style
 */
static void
plot_dots(plot)
	struct curve_points *plot;
{
    int i;
    int x,y;
    struct termentry *t = term;

    for (i = 0; i < plot->p_count; i++) {
	   if (plot->points[i].type == INRANGE) {
		  x = map_x(plot->points[i].x);
		  y = map_y(plot->points[i].y);
		  /* point type -1 is a dot */
		  (*t->point)(x,y, -1);
	   }
    }
}

/* plot_vectors:
 * Plot the curves in VECTORS style
 */
static void
plot_vectors(plot)
	struct curve_points *plot;
{
    int i;
    int x1,y1,x2,y2;
    struct termentry *t = term;

    for (i = 0; i < plot->p_count; i++) {
	   if (plot->points[i].type == INRANGE) {
		  x1 = map_x(plot->points[i].xlow);
		  y1 = map_y(plot->points[i].ylow);
		  x2 = map_x(plot->points[i].xhigh);
		  y2 = map_y(plot->points[i].yhigh);
		  (*t->arrow)(x1,y1, x2,y2, TRUE);
	   }
    }
}

/* single edge intersection algorithm */
/* Given two points, one inside and one outside the plot, return
 * the point where an edge of the plot intersects the line segment defined 
 * by the two points.
 */
static void
edge_intersect(points, i, ex, ey)
	struct coordinate GPHUGE *points; /* the points array */
	int i;				/* line segment from point i-1 to point i */
	double *ex, *ey;		/* the point where it crosses an edge */
{
    /* global x_min, x_max, y_min, x_max */
    double ix = points[i-1].x;
    double iy = points[i-1].y;
    double ox = points[i].x;
    double oy = points[i].y;
    double x, y;			/* possible intersection point */

    if(points[i].type == INRANGE)
      {
	/* swap points around so that ix/ix/iz are INRANGE and ox/oy/oz are OUTRANGE */
	x = ix;ix = ox;ox = x;
	y = iy;iy = oy;oy = y;
      }

    /* nasty degenerate cases, effectively drawing to an infinity point (?)
       cope with them here, so don't process them as a "real" OUTRANGE point 

       If more than one coord is -VERYLARGE, then can't ratio the "infinities"
       so drop out by returning the INRANGE point. 

       Obviously, only need to test the OUTRANGE point (coordinates) */
    if(ox == -VERYLARGE || oy == -VERYLARGE)
      {
	*ex = ix;
	*ey = iy;

	if(ox == -VERYLARGE)
	  {
	    /* can't get a direction to draw line, so simply return INRANGE point */
	    if(oy == -VERYLARGE)
	      return;

	    *ex = x_min;
	    return;
	  }

	/* obviously oy is -VERYLARGE and ox != -VERYLARGE */
	*ey = y_min;
	return;
      }

    /*
     * Can't have case (ix == ox && iy == oy) as one point
     * is INRANGE and one point is OUTRANGE.
     */    
    if (iy == oy) {
	   /* horizontal line */
	   /* assume inrange(iy, y_min, y_max) */
	   *ey = iy;		/* == oy */

	   if (inrange(x_max, ix, ox))
		*ex = x_max;
	   else if (inrange(x_min, ix, ox))
		*ex = x_min;
	   else {
		graph_error("error in edge_intersect");
	   }
	   return;
    } else if (ix == ox) {
	   /* vertical line */
	   /* assume inrange(ix, x_min, x_max) */
	   *ex = ix;		/* == ox */

	   if (inrange(y_max, iy, oy))
		*ey = y_max;
	   else if (inrange(y_min, iy, oy))
		*ey = y_min;
	   else {
		graph_error("error in edge_intersect");
	   }
	   return;
    }

    /* slanted line of some kind */

    /* does it intersect y_min edge */
    if (inrange(y_min, iy, oy) && y_min != iy && y_min != oy) {
	   x = ix + (y_min-iy) * ((ox-ix) / (oy-iy));
	   if (inrange(x, x_min, x_max)) {
		  *ex = x;
		  *ey = y_min;
		  return;			/* yes */
	   }
    }
    
    /* does it intersect y_max edge */
    if (inrange(y_max, iy, oy) && y_max != iy && y_max != oy) {
	   x = ix + (y_max-iy) * ((ox-ix) / (oy-iy));
	   if (inrange(x, x_min, x_max)) {
		  *ex = x;
		  *ey = y_max;
		  return;			/* yes */
	   }
    }

    /* does it intersect x_min edge */
    if (inrange(x_min, ix, ox) && x_min != ix && x_min != ox) {
	   y = iy + (x_min-ix) * ((oy-iy) / (ox-ix));
	   if (inrange(y, y_min, y_max)) {
		  *ex = x_min;
		  *ey = y;
		  return;
	   }
    }

    /* does it intersect x_max edge */
    if (inrange(x_max, ix, ox) && x_max != ix && x_max != ox) {
	   y = iy + (x_max-ix) * ((oy-iy) / (ox-ix));
	   if (inrange(y, y_min, y_max)) {
		  *ex = x_max;
		  *ey = y;
		  return;
	   }
    }

    /* If we reach here, the inrange point is on the edge, and
     * the line segment from the outrange point does not cross any 
     * other edges to get there. In this case, we return the inrange 
     * point as the 'edge' intersection point. This will basically draw
     * line.
     */
    *ex = ix; 
    *ey = iy;
    return;
}

/* XXX - JG  */
/* single edge intersection algorithm for "steps" curves */
/* 
 * Given two points, one inside and one outside the plot, return
 * the point where an edge of the plot intersects the line segments
 * forming the step between the two points. 
 *
 * Recall that if P1 = (x1,y1) and P2 = (x2,y2), the step from  
 * P1 to P2 is drawn as two line segments: (x1,y1)->(x2,y1) and 
 * (x2,y1)->(x2,y2). 
 */
static void
edge_intersect_steps(points, i, ex, ey)
	struct coordinate GPHUGE *points; /* the points array */
	int i;				/* line segment from point i-1 to point i */
	double *ex, *ey;		/* the point where it crosses an edge */
{
    /* global x_min, x_max, y_min, x_max */
    double ax = points[i-1].x;
    double ay = points[i-1].y;
    double bx = points[i].x;
    double by = points[i].y;

    if (points[i].type == INRANGE) {	/* from OUTRANGE to INRANG */
	    if (inrange(ay,y_min,y_max)) {
		*ey = ay;
		if (ax > x_max)
			*ex = x_max;
		else			/* x < x_min */
			*ex = x_min;
	    } else {
	    	*ex = bx;
		if (ay > y_max)     
			*ey = y_max;
		else			/* y < y_min */
			*ey = y_min;
	    }
    } else {				/* from INRANGE to OUTRANGE */
	    if (inrange(bx,x_min,x_max)) {
		*ex = bx;
		if (by > y_max)
			*ey = y_max;
		else			/* y < y_min */
			*ey = y_min;
	    } else {
	    	*ey = ay;
		if (bx > x_max)     
			*ex = x_max;
		else			/* x < x_min */
			*ex = x_min;
	    }
    }
    return;
}

/* XXX - HOE  */
/* single edge intersection algorithm for "fsteps" curves */
/* fsteps means step on forward y-value. 
 * Given two points, one inside and one outside the plot, return
 * the point where an edge of the plot intersects the line segments
 * forming the step between the two points. 
 *
 * Recall that if P1 = (x1,y1) and P2 = (x2,y2), the step from  
 * P1 to P2 is drawn as two line segments: (x1,y1)->(x1,y2) and 
 * (x1,y2)->(x2,y2). 
 */
static void
edge_intersect_fsteps(points, i, ex, ey)
	struct coordinate GPHUGE *points; /* the points array */
	int i;				/* line segment from point i-1 to point i */
	double *ex, *ey;		/* the point where it crosses an edge */
{
    /* global x_min, x_max, y_min, x_max */
    double ax = points[i-1].x;
    double ay = points[i-1].y;
    double bx = points[i].x;
    double by = points[i].y;

    if (points[i].type == INRANGE) {	/* from OUTRANGE to INRANG */
	    if (inrange(ax,x_min,x_max)) {
		*ex = ax;
		if (ay > y_max)
			*ey = y_max;
		else			/* y < y_min */
			*ey = y_min;
	    } else {
	    	*ey = by;
		if (bx > x_max)     
			*ex = x_max;
		else			/* x < x_min */
			*ex = x_min;
	    }
    } else {				/* from INRANGE to OUTRANGE */
	    if (inrange(by,y_min,y_max)) {
		*ey = by;
		if (bx > x_max)
			*ex = x_max;
		else			/* x < x_min */
			*ex = x_min;
	    } else {
	    	*ex = ax;
		if (by > y_max)     
			*ey = y_max;
		else			/* y < y_min */
			*ey = y_min;
	    }
    }
    return;
}

/* XXX - JG  */
/* double edge intersection algorithm for "steps" plot */
/* Given two points, both outside the plot, return the points where an 
 * edge of the plot intersects the line segments forming a step 
 * by the two points. There may be zero, one, two, or an infinite number
 * of intersection points. (One means an intersection at a corner, infinite
 * means overlaying the edge itself). We return FALSE when there is nothing
 * to draw (zero intersections), and TRUE when there is something to 
 * draw (the one-point case is a degenerate of the two-point case and we do 
 * not distinguish it - we draw it anyway).
 *
 * Recall that if P1 = (x1,y1) and P2 = (x2,y2), the step from  
 * P1 to P2 is drawn as two line segments: (x1,y1)->(x2,y1) and 
 * (x2,y1)->(x2,y2). 
 */
static TBOOLEAN				/* any intersection? */
two_edge_intersect_steps(points, i, lx, ly)
	struct coordinate GPHUGE *points; /* the points array */
	int i;				/* line segment from point i-1 to point i */
	double *lx, *ly;		/* lx[2], ly[2]: points where it crosses edges */
{
    /* global x_min, x_max, y_min, x_max */
    double ax = points[i-1].x;
    double ay = points[i-1].y;
    double bx = points[i].x;
    double by = points[i].y;

    if ( max(ax,bx) < x_min || min(ax,bx) > x_max || 
         max(ay,by) < y_min || min(ay,by) > y_max ||
         ( (ay  > y_max || ay < y_min)            &&
           (bx  > x_max || bx < x_min)  ) ) {
	return(FALSE);				
    } else if (inrange(ay,y_min,y_max) && inrange(bx,x_min,x_max)) {	/* corner of step inside plotspace */
    	*ly++ = ay;
	if (ax < x_min) 
		*lx++ = x_min;
	else 
		*lx++ = x_max;

	*lx++ = bx;
	if (by < y_min) 
		*ly++ = y_min;
	else 
		*ly++ = y_max;

	return(TRUE);
    } else if (inrange(ay,y_min,y_max)) {	/* cross plotspace in x-direction */
	*lx++ = x_min;
	*ly++ = ay;
	*lx++ = x_max;
	*ly++ = ay;
	return(TRUE);
    } else if (inrange(ax,x_min,x_max)) {	/* cross plotspace in y-direction */
	*lx++ = bx;
	*ly++ = y_min;
	*lx++ = bx;
	*ly++ = y_max;
	return(TRUE);
    } else
	return(FALSE);
}

/* XXX - HOE  */
/* double edge intersection algorithm for "fsteps" plot */
/* Given two points, both outside the plot, return the points where an 
 * edge of the plot intersects the line segments forming a step 
 * by the two points. There may be zero, one, two, or an infinite number
 * of intersection points. (One means an intersection at a corner, infinite
 * means overlaying the edge itself). We return FALSE when there is nothing
 * to draw (zero intersections), and TRUE when there is something to 
 * draw (the one-point case is a degenerate of the two-point case and we do 
 * not distinguish it - we draw it anyway).
 *
 * Recall that if P1 = (x1,y1) and P2 = (x2,y2), the step from  
 * P1 to P2 is drawn as two line segments: (x1,y1)->(x1,y2) and 
 * (x1,y2)->(x2,y2). 
 */
static TBOOLEAN				/* any intersection? */
two_edge_intersect_fsteps(points, i, lx, ly)
	struct coordinate GPHUGE *points; /* the points array */
	int i;		/* line segment from point i-1 to point i */
	double *lx, *ly; /* lx[2], ly[2]: points where it crosses edges */
{
    /* global x_min, x_max, y_min, x_max */
    double ax = points[i-1].x;
    double ay = points[i-1].y;
    double bx = points[i].x;
    double by = points[i].y;

    if ( max(ax,bx) < x_min || min(ax,bx) > x_max || 
         max(ay,by) < y_min || min(ay,by) > y_max ||
         ( (by  > y_max || by < y_min)            &&
           (ax  > x_max || ax < x_min)  ) ) {
	return(FALSE);				
    } else if (inrange(by,y_min,y_max) && inrange(ax,x_min,x_max)) {	/* corner of step inside plotspace */
    	*lx++ = ax;
	if (ay < y_min) 
		*ly++ = y_min;
	else 
		*ly++ = y_max;

	*ly = by;
	if (bx < x_min) 
		*lx = x_min;
	else 
		*lx = x_max;

	return(TRUE);
    } else if (inrange(by,y_min,y_max)) {	/* cross plotspace in x-direction */
	*lx++ = x_min;
	*ly++ = by;
	*lx = x_max;
	*ly = by;
	return(TRUE);
    } else if (inrange(ax,x_min,x_max)) {	/* cross plotspace in y-direction */
	*lx++ = ax;
	*ly++ = y_min;
	*lx = ax;
	*ly = y_max;
	return(TRUE);
    } else
	return(FALSE);
}

/* double edge intersection algorithm */
/* Given two points, both outside the plot, return
 * the points where an edge of the plot intersects the line segment defined 
 * by the two points. There may be zero, one, two, or an infinite number
 * of intersection points. (One means an intersection at a corner, infinite
 * means overlaying the edge itself). We return FALSE when there is nothing
 * to draw (zero intersections), and TRUE when there is something to 
 * draw (the one-point case is a degenerate of the two-point case and we do 
 * not distinguish it - we draw it anyway).
 */
static TBOOLEAN				/* any intersection? */
two_edge_intersect(points, i, lx, ly)
	struct coordinate GPHUGE *points; /* the points array */
	int i;				/* line segment from point i-1 to point i */
	double *lx, *ly;		/* lx[2], ly[2]: points where it crosses edges */
{
    /* global x_min, x_max, y_min, x_max */
    int count;
    double ix = points[i-1].x;
    double iy = points[i-1].y;
    double ox = points[i].x;
    double oy = points[i].y;
    double t[4];
    double swap;
    double t_min, t_max;
#if 0
    fprintf(stderr, "\ntwo_edge_intersect (%g, %g) and (%g, %g) : ",
	    points[i-1].x, points[i-1].y,
	    points[i].x, points[i].y);
#endif
    /* nasty degenerate cases, effectively drawing to an infinity point (?)
       cope with them here, so don't process them as a "real" OUTRANGE point 

       If more than one coord is -VERYLARGE, then can't ratio the "infinities"
       so drop out by returning FALSE */
    
    count = 0;
    if(ix == -VERYLARGE) count++;
    if(ox == -VERYLARGE) count++;
    if(iy == -VERYLARGE) count++;
    if(oy == -VERYLARGE) count++;

    /* either doesn't pass through graph area *or* 
       can't ratio infinities to get a direction to draw line, so simply return(FALSE) */
    if(count > 1){
#if 0
      fprintf(stderr, "\tA\n");
#endif
      return(FALSE);
    }

    if(ox == -VERYLARGE || ix == -VERYLARGE)
      {
	if(ix == -VERYLARGE)
	  {
	    /* swap points so ix/iy don't have a -VERYLARGE component */
	    swap = ix;ix = ox;ox = swap;
	    swap = iy;iy = oy;oy = swap;
	  }

	/* check actually passes through the graph area */
	if(ix > x_max && inrange(iy, y_min, y_max))
	  {
	    lx[0] = x_min;
	    ly[0] = iy;

	    lx[1] = x_max;
	    ly[1] = iy;
#if 0
    fprintf(stderr, "(%g %g) -> (%g %g)", 
	    lx[0], ly[0], lx[1], ly[1]);
#endif
	    return(TRUE);
	  }
	else {
#if 0
	  fprintf(stderr, "\tB\n");
#endif
	  return(FALSE);
	}
      }

    if(oy == -VERYLARGE || iy == -VERYLARGE)
      {
	if(iy == -VERYLARGE)
	  {
	    /* swap points so ix/iy don't have a -VERYLARGE component */
	    swap = ix;ix = ox;ox = swap;
	    swap = iy;iy = oy;oy = swap;
	  }

	/* check actually passes through the graph area */
	if(iy > y_max && inrange(ix, x_min, x_max))
	  {
	    lx[0] = ix;
	    ly[0] = y_min;

	    lx[1] = ix;
	    ly[1] = y_max;
#if 0
    fprintf(stderr, "(%g %g) -> (%g %g)", 
	    lx[0], ly[0], lx[1], ly[1]);
#endif
	    return(TRUE);
	  }
	else {
#if 0
	  fprintf(stderr, "\tC\n");
#endif
	  return(FALSE);
	}
      }

    /*
     * Special horizontal/vertical, etc. cases are checked and remaining
     * slant lines are checked separately.
     *
     * The slant line intersections are solved using the parametric form
     * of the equation for a line, since if we test x/y min/max planes explicitly
     * then e.g. a  line passing through a corner point (x_min,y_min) 
     * actually intersects 2 planes and hence further tests would be required 
     * to anticipate this and similar situations.
     */

    /*
     * Can have case (ix == ox && iy == oy) as both points OUTRANGE
     */
    if(ix == ox && iy == oy)
      {
	/* but as only define single outrange point, can't intersect graph area */
	return(FALSE);
      }

    if(ix == ox) {
	/* line parallel to y axis */
	
	/* x coord must be in range, and line must span both y_min and y_max */
	/* note that spanning y_min implies spanning y_max, as both points OUTRANGE */
	if(!inrange(ix, x_min, x_max))
	  {
	    return(FALSE);
	  }

	if (inrange(y_min, iy, oy)) {
	  lx[0] = ix;
	  ly[0] = y_min;

	  lx[1] = ix;
	  ly[1] = y_max;
#if 0
	  fprintf(stderr, "(%g %g) -> (%g %g)", 
		  lx[0], ly[0], lx[1], ly[1]);
#endif
	  return(TRUE);
	} else
	  return(FALSE);
      }

    if(iy == oy) {
      /* already checked case (ix == ox && iy == oy) */
      
      /* line parallel to x axis */
	/* y coord must be in range, and line must span both x_min and x_max */
	/* note that spanning x_min implies spanning x_max, as both points OUTRANGE */
	if(!inrange(iy, y_min, y_max))
	  {
	    return(FALSE);
	  }

	if (inrange(x_min, ix, ox)) {
	  lx[0] = x_min;
	  ly[0] = iy;

	  lx[1] = x_max;
	  ly[1] = iy;
#if 0
    fprintf(stderr, "(%g %g) -> (%g %g)", 
	    lx[0], ly[0], lx[1], ly[1]);
#endif
	  return(TRUE);
	} else
	  return(FALSE);
      }

    /* nasty 2D slanted line in an xy plane */

    /*
      Solve parametric equation
      
      (ix, iy) + t (diff_x, diff_y)

      where 0.0 <= t <= 1.0 and

      diff_x = (ox - ix);
      diff_y = (oy - iy);
      */

      t[0] = (x_min - ix)/(ox - ix);
      t[1] = (x_max - ix)/(ox - ix);
      
      if(t[0] > t[1]) {
	swap = t[0];t[0] = t[1];t[1] = swap;
      }

      t[2] = (y_min - iy)/(oy - iy);
      t[3] = (y_max - iy)/(oy - iy);
      
      if(t[2] > t[3]) {
	swap = t[2];t[2] = t[3];t[3] = swap;
      }

      t_min = max(max(t[0],t[2]),0.0);
      t_max = min(min(t[1],t[3]),1.0);
      
      if(t_min > t_max)
	return(FALSE);

      lx[0] = ix + t_min * (ox - ix);
      ly[0] = iy + t_min * (oy - iy);
      
      lx[1] = ix + t_max * (ox - ix);
      ly[1] = iy + t_max * (oy - iy);
      
      /*
       * Can only have 0 or 2 intersection points -- only need test one coord
       */
      if(inrange(lx[0], x_min, x_max) && 
	 inrange(ly[0], y_min, y_max))
	{
#if 0
	  fprintf(stderr, "(%g %g) -> (%g %g)", 
		  lx[0], ly[0], lx[1], ly[1]);
#endif
	  return(TRUE);
	}
      
    return(FALSE);
}

/* justify ticplace to a proper date-time value */
double
time_tic_just(level,ticplace)
int level;
double ticplace;
{
	struct tm tm;

	if (level <= 0) {
		return(ticplace);
	}
	ggmtime(&tm,(double)ticplace);
  	if (level > 0) { /* units of minutes */
  		if ( tm.tm_sec > 50 ) 
  			tm.tm_min ++;
  		tm.tm_sec = 0;
  	}
  	if (level > 1) { /* units of hours */
  		if ( tm.tm_min > 50 )
  			tm.tm_hour ++;
  		tm.tm_min = 0;
  	}
  	if (level > 2) { /* units of days */
		if ( tm.tm_hour > 14 ) {
			tm.tm_hour = 0;
			tm.tm_mday = 0;
  			tm.tm_yday ++;
 			ggmtime(&tm,(double)gtimegm(&tm));
  		} else if (tm.tm_hour > 7 ) {
  			tm.tm_hour = 12;
  		} else if (tm.tm_hour > 3 ) {
  			tm.tm_hour = 6;
  		} else {
  			tm.tm_hour = 0;
  		}
  	}
  	/* skip it, I have not bothered with weekday so far */
	if (level > 4) { /* units of month */
		if ( tm.tm_mday > 25 ) {
			tm.tm_mon ++;
			if (tm.tm_mon > 11) {
				tm.tm_year++;
				tm.tm_mon = 0;
			}
		}
		tm.tm_mday = 1;
	}
	if (level > 5) {
		if ( tm.tm_mon >= 7 )
			tm.tm_year ++;
		tm.tm_mon = 0;
	}
	ticplace = (double) gtimegm(&tm);
	ggmtime(&tm,(double)gtimegm(&tm));
	return(ticplace);
}

/* make smalltics for time-axis */
double
make_ltic(tlevel,incr)
int tlevel;
double incr;
{
	double tinc;
	tinc = 0;
	if ( tlevel < 0 ) tlevel = 0;
	switch ( tlevel ) {
		case 0: 
		case 1: {
			if ( incr >= 20 )
				tinc = 10;
			if ( incr >= 60 )
				tinc = 30;
			if ( incr >= 2*60 )
				tinc = 60;
			if ( incr >= 5*60 )
				tinc = 2*60;
			if ( incr >= 10*60 )
				tinc = 5*60;
			if ( incr >= 20*60 )
				tinc = 10*60;
			break;
			}
		case 2: {
			if ( incr >= 20*60 )
				tinc = 10*60;
			if ( incr >= 3600 )
				tinc = 30*60;
			if ( incr >= 2*3600 )
				tinc = 3600;
			if ( incr >= 5*3600 )
				tinc = 2*3600;
			if ( incr >= 10*3600 )
				tinc = 5*3600;
			if ( incr >= 20*3600 )
				tinc = 10*3600;
			break;
			}
		case 3: {
			if ( incr > 2*3600 )
				tinc = 3600;
			if ( incr > 4*3600 )
				tinc = 2*3600;
			if ( incr > 7*3600 )
				tinc = 3*3600;
			if ( incr > 13*3600 )
				tinc = 6*3600;
			if ( incr > DAY_SEC )
				tinc = 12*3600;
			if ( incr > 2*DAY_SEC )
				tinc = DAY_SEC;
			break;
			}
		case 4: { /* week: tic per day */
			if ( incr > 2*DAY_SEC )
				tinc = DAY_SEC;
			if ( incr > 7*DAY_SEC )
				tinc = 7*DAY_SEC;
			break;
			}
		case 5: { /* month */
			if ( incr > 2*DAY_SEC )
				tinc = DAY_SEC;
			if ( incr > 15*DAY_SEC )
				tinc = 10*DAY_SEC;
			if ( incr > 2*MON_SEC )
				tinc = MON_SEC;
			if ( incr > 6*MON_SEC )
				tinc = 3*MON_SEC;
			if ( incr > 2*YEAR_SEC )
				tinc = YEAR_SEC;
			break;
			}
		case 6: { /* year */
			if ( incr > 2*MON_SEC )
				tinc = MON_SEC;
			if ( incr > 6*MON_SEC )
				tinc = 3*MON_SEC;
			if ( incr > 2*YEAR_SEC )
				tinc = YEAR_SEC;
			if ( incr > 10*YEAR_SEC )
				tinc = 5*YEAR_SEC;
			if ( incr > 50*YEAR_SEC ) 
				tinc = 10*YEAR_SEC;
			if ( incr > 100*YEAR_SEC ) 
				tinc = 20*YEAR_SEC;
			if ( incr > 200*YEAR_SEC ) 
				tinc = 50*YEAR_SEC;
			if ( incr > 300*YEAR_SEC ) 
				tinc = 100*YEAR_SEC;
			break;
			}
	}
	return(tinc);
}


void write_multiline(x,y,text,hor,vert,angle, font)
unsigned int x,y;
char *text;
int hor,vert; /* horizontal and vertical just - text in hor direction despite angle */
int angle; /* assume term has already been set for this */
char *font; /* NULL or "" means use default */
{
	/* assumes we are free to mangle the text */
    register struct termentry *t = term;
	char *p;
	if (vert != JUST_TOP) {
		/* count lines and adjust y */
		int lines=0; /* number of linefeeds - one fewer than lines */
		for (p=text; *p; ++p)
			if (*p=='\n') ++lines;
		if (angle)
			x -= (vert*lines*t->v_char)/2;
		else
			y += (vert*lines*t->v_char)/2;
	}

	if (font && *font)
		(*t->set_font)(font);
		

	for(;;) { /* we will explicitly break out */
	
		if ( (p=strchr(text, '\n')) != NULL )
			*p=0; /* terminate the string */

		if ((*t->justify_text)(hor)) {
			(*t->put_text)(x, y, text);
		} else {
			int fix=hor*(t->h_char)*strlen(text)/2;
			if (angle)
				(*t->put_text)(x, y-fix, text);
			else
				(*t->put_text)(x - fix, y, text);
		}
		if (angle)
			x += t->v_char;
		else
			y -= t->v_char;

		if (!p)
			break;

		text = p+1;
	} /* unconditional branch back to the for(;;) - just a goto ! */

	if (font && *font)
		(*t->set_font)(default_font);
}

/* display a x-axis ticmark - called by gen_ticks */
/* also uses global tic_start, tic_direction, tic_text and tic_just */
static void xtick2d_callback(axis, place, text, grid)
int axis;
double place;
char *text;
int grid; /* linetype or -2 for no grid */
{
    register struct termentry *t = term;
    /* minitick if text is NULL - beware - h_tic is unsigned */
    int ticsize = tic_direction*(int)(t->v_tic)*(text ? ticscale : miniticscale);
    unsigned int x=map_x(place);

  if (grid > -2) {
    (*t->linetype)(grid);
    if (polar_grid_angle) {
	double x=place, y=0, s=sin(0.1), c=cos(0.1);
	int i;
	if (place > largest_polar_circle) largest_polar_circle=place;
	else if (-place > largest_polar_circle) largest_polar_circle=-place;
	clip_move(map_x(x),map_y(0));
	for (i=1;i<=63 /* 2pi/0.1*/; ++i) {
		{
			/* cos(t+dt)=cos(t)cos(dt)-sin(t)cos(dt) */
			double tx=x*c-y*s;
			/* sin(t+dt)=sin(t)cos(dt)+cos(t)sin(dt) */
			y=y*c+x*s;
			x=tx;
		}
		clip_vector(map_x(x), map_y(y));
	}
    } else {
	    if (x < key_xr && x > key_xl ) {
		if ( key_yb > ybot ) {
		     (*t->move)(x, ybot);
		     (*t->vector)(x, key_yb);
		}
		if ( key_yt < ytop ) {
		     (*t->move)(x,key_yt);
		     (*t->vector)(x, ytop);
		}
	    } else {
	        (*t->move)(x, ybot);
	        (*t->vector)(x, ytop);
	    }
    }
    (*t->linetype)(-2); /* border linetype */
  }

    /* we precomputed tic posn and text posn in global vars */

    (*t->move)(x, tic_start);
    (*t->vector)(x,tic_start+ticsize);

    if (tic_mirror >= 0) {
	(*t->move)(x, tic_mirror);
	(*t->vector)(x,tic_mirror-ticsize);
    }
 	
    if (text) {
    	write_multiline(x, tic_text, text, CENTRE, tic_just, 0, NULL);
    }
}

/* display a y-axis ticmark - called by gen_ticks */
/* also uses global tic_start, tic_direction, tic_text and tic_just */
static void ytick2d_callback(axis, place, text, grid)
int axis;
double place;
char *text;
int grid; /* linetype or -2 */
{
    register struct termentry *t = term;
    /* minitick if text is NULL - v_tic is unsigned */
    int ticsize = tic_direction*(int)(t->h_tic)*(text ? ticscale : miniticscale);
    unsigned int y=map_y(place);
  if (grid > -2) {
    (*t->linetype)(grid);
    if (polar_grid_angle) {
	double x=0, y=place, s=sin(0.1), c=cos(0.1);
	int i;
	if (place > largest_polar_circle) largest_polar_circle=place;
	else if (-place > largest_polar_circle) largest_polar_circle=-place;
	clip_move(map_x(x),map_y(y));
	for (i=1;i<=63 /* 2pi/0.1*/; ++i) {
		{
			/* cos(t+dt)=cos(t)cos(dt)-sin(t)cos(dt) */
			double tx=x*c-y*s;
			/* sin(t+dt)=sin(t)cos(dt)+cos(t)sin(dt) */
			y=y*c+x*s;
			x=tx;
		}
		clip_vector( map_x(x), map_y(y) );
	}
    } else {
	     if (y < key_yt && y > key_yb && key_xl < xright /* catch TOUT */ ) {
		if ( key_xl > xleft ) {
		     (*t->move)(xleft, y);
		     (*t->vector)(key_xl, y);
		}
		if ( key_xr < xright ) {
		     (*t->move)(key_xr, y);
		     (*t->vector)(xright, y);
		}
	     } else {
	        (*t->move)(xleft, y);
	        (*t->vector)(xright, y);
	     }
    }
    (*t->linetype)(-2); /* border linetype */
  }
  
    /* we precomputed tic posn and text posn */

    (*t->move)(tic_start, y);
    (*t->vector)(tic_start+ticsize, y);

    if (tic_mirror >= 0) {
	(*t->move)(tic_mirror, y);
	(*t->vector)(tic_mirror-ticsize, y);
    }
    
    if (text) {
    	write_multiline(tic_text, y, text, tic_just, JUST_CENTRE, 0, NULL);
    }
}

int 
label_width(str,lines)
char *str;
int *lines;
{
	char lab[MAX_LINE_LEN+1], *s, *e;
	int mlen, len, l;

	l = mlen = len = 0;
	sprintf(lab,"%s\n",str);
	s = lab;
	while( (e=(char *)strchr(s,'\n')) ) {
		*e = '\0';
		len = strlen(s); /* = e-s ? */
		if ( len > mlen ) mlen = len;
		if ( len || l ) l++;
		s = ++e;
	}
	/* lines=NULL => not interested - div */
	if (lines) *lines = l;
	return(mlen);
}


void setup_tics(axis,ticdef, format, max)
int axis;
struct ticdef *ticdef;
char *format;
int max; /* approx max number of slots available */
{
	double tic;

	int fixmin=(auto_array[axis] & 1) != 0;
	int fixmax=(auto_array[axis] & 2) != 0;

	if (ticdef->type == TIC_SERIES) {
		ticstep[axis] = tic = ticdef->def.series.incr;
		fixmin &=  (ticdef->def.series.start == -VERYLARGE);
		fixmax &=  (ticdef->def.series.end   ==  VERYLARGE);
	} else if (ticdef->type == TIC_COMPUTED) {
		ticstep[axis] = tic = make_tics(axis, max);
	}

	if (fixmin) {
		if (min_array[axis] < max_array[axis])
			min_array[axis] = tic * floor(min_array[axis]/tic);
		else
			min_array[axis] = tic * ceil(min_array[axis]/tic);
	}

	if (fixmax) {
		if (min_array[axis] < max_array[axis])
			max_array[axis] = tic * ceil(max_array[axis]/tic);
		else
			max_array[axis] = tic * floor(max_array[axis]/tic);
	}
			
	if (datatype[axis] == TIME && format_is_numeric[axis])
		/* invent one for them */
		timetic_format(axis, min_array[axis],max_array[axis]);
	else
		strcpy(ticfmt[axis], format);
}

/*{{{  mant_exp - split into mantissa and/or exponent*/
static void mant_exp(log_base, x, scientific, m,p)
double log_base, x;
int scientific; /* round to power of 3 */
double *m;
int *p; /* results */
{
	int sign=1;
	double l10;
	int power;
	/*{{{  check 0*/
	if (x==0) {
		if (m) *m=0;
		if (p) *p=0;
		return;
	}
	/*}}}*/
	/*{{{  check -ve*/
	if (x<0) {
		sign=(-1);
		x=(-x);
	}
	/*}}}*/

	l10=log10(x) / log_base;
	power = floor(l10);
	if (scientific) {
		power = 3*(power/3);
	}

	if (m) *m = sign * pow( 10.0, (l10-power)*log_base );
	if (p) *p = power;
}
/*}}}*/
	

/*{{{  gprintf*/
/* extended sprintf */
void gprintf(dest, format, log_base, x)
char *dest, *format;
double log_base, x; /* we print one number in a number of different formats */
{
	char temp[MAX_LINE_LEN];
	char *t;

	for (;;) {
		/*{{{  copy to dest until %*/
		while (*format != '%')
			if (!(*dest++ = *format++))
				return;  /* end of format */
		/*}}}*/

		/*{{{  check for %%*/
		if (format[1]=='%') {
			*dest++ = '%';
			format += 2;
			continue;
		}
		/*}}}*/

		/*{{{  copy format part to temp, excluding conversion character*/
		t=temp;
		*t++ = '%';
		while (isdigit(*++format) || *format=='.' || *format=='-' || *format=='+')
			*t++ = *format;
		/*}}}*/

		/*{{{  convert conversion character*/
		switch (*format) {
			/*{{{  x and o*/
			case 'x': case 'X':
			case 'o': case 'O':
				t[0]=*format++;
				t[1]=0;
				sprintf(dest, temp, (int) x);
				dest += strlen(dest);
				break;
			/*}}}*/
			/*{{{  e, f and g*/
			case 'e': case 'E':
			case 'f': case 'F':
			case 'g': case 'G':
				t[0]=*format++;
				t[1]=0;
				sprintf(dest, temp, x);
				dest += strlen(dest);
				break;
			/*}}}*/
			/*{{{  l*/
			case 'l':
				{
					double mantissa;
					mant_exp(log_base, x, 0, &mantissa, NULL);
					t[0]='f';
					t[1]=0;
					sprintf(dest, temp, mantissa);
					dest += strlen(dest);
					++format;
					break;
				}
			/*}}}*/
			/*{{{  t*/
			case 't':
				{
					double mantissa;
					mant_exp(1.0, x, 0, &mantissa, NULL);
					t[0]='f';
					t[1]=0;
					sprintf(dest, temp, mantissa);
					dest += strlen(dest);
					++format;
					break;
				}
			/*}}}*/
			/*{{{  s*/
			case 's':
				{
					double mantissa;
					mant_exp(1.0, x, 1, &mantissa, NULL);
					t[0]='f';
					t[1]=0;
					sprintf(dest, temp, mantissa);
					dest += strlen(dest);
					++format;
					break;
				}
			/*}}}*/
			/*{{{  L*/
			case 'L':
				{
					int power;
					mant_exp(log_base, x, 0, NULL, &power);
					t[0]='d';
					t[1]=0;
					sprintf(dest, temp, power);
					dest += strlen(dest);
					++format;
					break;
				}
			/*}}}*/
			/*{{{  T*/
			case 'T':
				{
					int power;
					mant_exp(1.0, x, 0, NULL, &power);
					t[0]='d';
					t[1]=0;
					sprintf(dest, temp, power);
					dest += strlen(dest);
					++format;
					break;
				}
			/*}}}*/
			/*{{{  S*/
			case 'S':
				{
					int power;
					mant_exp(1.0, x, 1, NULL, &power);
					t[0]='d';
					t[1]=0;
					sprintf(dest, temp, power);
					dest += strlen(dest);
					++format;
					break;
				}
			/*}}}*/
			/*{{{  c*/
			case 'c':
				{
					int power;
					mant_exp(1.0, x, 0, NULL, &power);
					t[0]='c';
					t[1]=0;
					power = power / 3 + 6;
					if (power<0 || power>12) power=12;
					/* please extend the range ! */
					sprintf(dest, temp, "**pnum kMG***"[power]);
					dest += strlen(dest);
					++format;
					break;
				}
			/*}}}*/
			default:
				int_error("Bad format character", NO_CARET);
		}
		/*}}}*/
	}
}
/*}}}*/

/*{{{  gen_tics*/
/* uses global arrays ticstep[], ticfmt[], min_array[], max_array[],
 * auto_array[], log_array[], log_base_array[]
 * we use any of GRID_X/Y/X2/Y2 and  _MX/_MX2/etc - caller is expected
 * to clear the irrelevent fields from global grid bitmask
 * note this is also called from graph3d, so we need GRID_Z too
 */

void gen_tics(axis, def, grid, minitics, minifreq, callback)
int axis; /* FIRST_X_AXIS, etc */
struct ticdef *def; /* tic defn */
int grid; /* GRID_X | GRID_MX etc */
int minitics; /* minitics - off/default/auto/explicit */
double minifreq; /* frequency */
tic_callback callback;  /* fn to call to actually do the work */
{
    /* seperate main-tic part of grid */
    int lgrd= (grid&(GRID_X|GRID_Y|GRID_X2|GRID_Y2|GRID_Z)) ? grid_linetype : -2;
    int mgrd= (grid&(GRID_MX|GRID_MY|GRID_MX2|GRID_MY2|GRID_MZ)) ? mgrid_linetype : -2;

    if (def->type==TIC_USER) {  /* special case */
	/*{{{  do user tics then return*/
	struct ticmark *mark = def->def.user;
	double uncertain=(max_array[axis]-min_array[axis])/10;
	double ticmin=min_array[axis]-SIGNIF*uncertain;
	double internal_max=max_array[axis]+SIGNIF*uncertain;
	
	for (mark=def->def.user; mark; mark=mark->next) {
		char label[64];
		double internal = log_array[axis] ? log(mark->position)/log_base_array[axis] : mark->position;
		if (!inrange(internal, ticmin, internal_max))
			continue;
		/* polar labels always +ve */
		sprintf(label, mark->label ? mark->label : ticfmt[axis], polar ? fabs(mark->position):mark->position);
		(*callback)(axis, internal, label, lgrd);
	}
	
	return; /* NO MINITICS FOR USER-DEF TICS */
	   }
	/*}}}*/

    /* series-tics
     * need to distinguish user co-ords from internal co-ords.
     * - for logscale, internal=log(user), else internal=user
     *
     * The minitics are a bit of a drag - we need to distinuish
     * the cases step>1 from step==1.
     * If step=1, we are looking at 1,10,100,1000 for example, so
     * minitics are 2,5,8, ...  - done in user co-ordinates
     * If step>1, we are looking at 1,1e6,1e12 for example, so
     * minitics are 10,100,1000,... - done in internal co-ords
     */

    {
	double tic; /* loop counter */
	double internal; /* in internal co-ords */
	double user;     /* in user co-ords */
	double start,step,end;
	double lmin=min_array[axis], lmax=max_array[axis];
	double internal_min, internal_max;	/* to allow for rounding errors */
	double ministart=0, ministep=1, miniend=1; /* internal or user - depends on step */

	/* gprintf uses log10() of base - log_base_array is log() */
	double log_base = log_array[axis] ? log10(base_array[axis]) : 1.0;

	if (lmax < lmin) {
		/* hmm - they have set reversed range for some reason */
		double temp=lmin; lmin=lmax; lmax=temp;
	}
	
	/*{{{  choose start, step and end*/
	switch (def->type) {
		case TIC_SERIES:
			if (log_array[axis]) {
				/* we can tolerate start<=0 if step and end > 0 */
				if (def->def.series.end <= 0 ||
				    def->def.series.incr <= 0)
					return; /* just quietly ignore */
				step=log(def->def.series.incr)/log_base_array[axis];
				end=log(def->def.series.end)/log_base_array[axis];
				start=def->def.series.start > 0 ?
				      log(def->def.series.start) / log_base_array[axis] :
						step;
			} else {
				start=def->def.series.start;
				step=def->def.series.incr;
				end=def->def.series.end;
				if (start==-VERYLARGE)
					start=step*floor(lmin/step);
				if (end == VERYLARGE)
					end=step*ceil(lmax/step);
			}
			break;
		case TIC_COMPUTED:
			/* round to multiple of step */
			start=ticstep[axis]*floor(lmin/ticstep[axis]);
			step=ticstep[axis];
			end=ticstep[axis]*ceil(lmax/ticstep[axis]);
			break;
		case TIC_MONTH:
			start=floor(lmin);
			end=ceil(lmax);
			step=floor((end-start)/12);
			if (step<1) step=1;
			break;
		case TIC_DAY:
			start=floor(lmin);
			end=ceil(lmax);
			step=floor((end-start)/14);
			if (step<1) step=1;
			break;
		default:
			graph_error("Internal error : unknown tic type");
			return; /* avoid gcc -Wall warning about start */
	}
	/*}}}*/

	/*{{{  ensure ascending order*/
	if (end < start) {
		double temp;
		temp=end; end=start; start=temp;
	}
	step=fabs(step);
	/*}}}*/

	if (minitics) {
		/*{{{  figure out ministart, ministep, miniend*/
		if (minitics==MINI_USER) {
			/* they have said what they want */
			if (minifreq<=0)
				minitics=0;  /* not much else we can do */
			else {
				ministart = ministep=step/minifreq;
				miniend = step;
			}
		} else if (log_array[axis]) {
			if (step>1.5) { /* beware rounding errors */
				/*{{{  10,100,1000 case*/
				/* no more than five minitics */
				ministart = ministep = (int) (0.2*step);
				if (ministep < 1)
					ministart = ministep = 1;
				miniend = step;
				/*}}}*/
			} else {
				/*{{{  2,5,8 case*/
				miniend = base_array[axis];
				if (end-start >= 10)
					minitics=0; /* none */
				else if (end-start >= 5) {
					ministart=2;
					ministep=3;
				} else {
					ministart=2;
					ministep=1;
				}
				/*}}}*/
			}
		} else if (datatype[axis]==TIME) {
			ministart=ministep=make_ltic(timelevel[axis], step);
			miniend=step*0.9;
		} else if (minitics==MINI_AUTO) {
			ministart=ministep=0.1*step;
			miniend=step;
		} else
			minitics=0;
		
		if (ministep<=0)
			minitics=0; /* dont get stuck in infinite loop */
		/*}}}*/
	}
	
	/*{{{  a few tweaks and checks*/
	/* watch rounding errors */
	end += SIGNIF*step;
	internal_max=lmax + step*SIGNIF;
	internal_min=lmin - step*SIGNIF;
	
	if (step==0)
		return;  /* just quietly ignore them ! */
	/*}}}*/
	
	for (tic=start; tic<=end; tic+=step) {
		/*{{{  calc internal and user co-ords*/
		if (!log_array[axis]) {
			internal = datatype[axis]==TIME ? time_tic_just(timelevel[axis],tic): tic;
			user=CheckZero(internal,step);
		} else {
			/* log scale => dont need to worry about zero ? */
			internal=tic;
			user=pow(base_array[axis], internal);
		}
		/*}}}*/
		if (internal > internal_max)
			break; /* gone too far - end of series = VERYLARGE perhaps */
		if (internal >= internal_min) {
			/* continue; */ /* maybe minitics!!!. user series starts below min ? */

			/*{{{  draw tick via callback*/
			switch(def->type) {
				case TIC_DAY : {
					int d = (long)floor(user+0.5) % 7;
					if (d<0) d+=7;
					(*callback)(axis,internal, abbrev_day_names[d], lgrd);
					break;
				}
				case TIC_MONTH: {
					int m = (long)floor(user+0.5) % 12;
					if (m<0) m+=12;
					(*callback)(axis, internal, abbrev_month_names[m], lgrd);
					break;
				}
				default: { /* comp or series */
					char label[64];
					if ( datatype[axis] == TIME ) {
						/* If they are doing polar time plot, good luck to them */
						gstrftime(label,24,ticfmt[axis],(double)user);
					} else {
						gprintf(label, ticfmt[axis], log_base, polar?fabs(user):user);
					}
					(*callback)(axis, internal, label, lgrd);
				}
			}
			/*}}}*/

		}
		if (minitics) {
			/*{{{  process minitics*/
			double mplace, mtic;
			for (mplace=ministart; mplace < miniend; mplace += ministep) {
				if ( datatype[axis] == TIME )
					mtic = time_tic_just(timelevel[axis]-1,internal+mplace);
				else
					mtic=internal + (log_array[axis]&&step<=1.5 ? log(mplace)/log_base_array[axis] : mplace);
				if (inrange(mtic, internal_min, internal_max))
					 callback(axis, mtic, NULL, mgrd);
			}
			/*}}}*/
		} 
	}
    }
}
/*}}}*/

/*{{{  map_position*/
static void map_position(pos, x, y, what)
struct position *pos;
unsigned int *x, *y;
char *what;
{
	switch (pos->scalex) {
		case first_axes:
			{
				double xx=LogScale(pos->x, log_array[FIRST_X_AXIS], log_base_array[FIRST_X_AXIS], what, "x");
				*x=xleft + (xx-min_array[FIRST_X_AXIS])*scale[FIRST_X_AXIS]+0.5;
				break;
			}
		case second_axes:
			{
				double xx=LogScale(pos->x, log_array[SECOND_X_AXIS], log_base_array[SECOND_X_AXIS], what, "x");
				*x=xleft + (xx-min_array[SECOND_X_AXIS])*scale[SECOND_X_AXIS]+0.5;
				break;
			}
		case graph:
			{
				*x=xleft + pos->x*(xright-xleft) + 0.5;
				break;
			}
		case screen:
			{
				register struct termentry *t = term;
				*x = pos->x*(t->xmax) + 0.5;
				break;
			}
	}
	switch (pos->scaley) {
		case first_axes:
			{
				double yy=LogScale(pos->y, log_array[FIRST_Y_AXIS], log_base_array[FIRST_Y_AXIS], what, "y");
				*y=ybot  + (yy-min_array[FIRST_Y_AXIS])*scale[FIRST_Y_AXIS]+0.5;
				return;
			}
		case second_axes:
			{
				double yy=LogScale(pos->y, log_array[SECOND_Y_AXIS], log_base_array[SECOND_Y_AXIS], what, "y");
				*y=ybot  + (yy-min_array[SECOND_Y_AXIS])*scale[SECOND_Y_AXIS]+0.5;
				return;
			}
		case graph:
			{
				*y=ybot  + pos->y*(ytop-ybot) + 0.5;
				return;
			}
		case screen:
			{
				register struct termentry *t = term;
				*y = pos->y*(t->ymax) + 0.5;
				return;				
			}
	}
}
/*}}}*/
