#ifndef lint
static char *RCSid() { return RCSid("$Id: axis.c,v 1.97.2.5 2013/08/08 06:45:31 sfeam Exp $"); }
#endif

/* GNUPLOT - axis.c */

/*[
 * Copyright 2000, 2004   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 complete modified source code.  Modifications are to
 * be distributed as patches to the released version.  Permission to
 * distribute binaries produced by compiling modified sources is granted,
 * provided you
 *   1. distribute the corresponding source modifications from the
 *    released version in the form of a patch file along with the binaries,
 *   2. add special version identification to distinguish your version
 *    in addition to the base release version number,
 *   3. provide your name and address as the primary contact for the
 *    support of your modified version, and
 *   4. retain our contact information in regard to use of the base
 *    software.
 * Permission to distribute the released version of the source code along
 * with corresponding source modifications in the form of a patch file is
 * granted with same provisions 2 through 4 for binary distributions.
 *
 * This software is provided "as is" without express or implied warranty
 * to the extent permitted by applicable law.
]*/

#include "axis.h"

#include "stdfn.h"

#include "alloc.h"
#include "command.h"
#include "gadgets.h"
#include "gp_time.h"
#include "graphics.h"	/* For label_width() */
/*  #include "setshow.h" */
#include "term_api.h"
#include "variable.h"

/* HBB 20000416: this is the start of my try to centralize everything
 * related to axes, once and for all. It'll probably end up as a
 * global array of OO-style 'axis' objects, when it's done */

/* HBB 20000725: gather all per-axis variables into a struct, and set
 * up a single large array of such structs. Next step might be to use
 * isolated AXIS structs, instead of an array. At least for *some* of
 * the axes... */
AXIS axis_array[AXIS_ARRAY_SIZE]
    = AXIS_ARRAY_INITIALIZER(DEFAULT_AXIS_STRUCT);

/* Keep defaults varying by axis in their own array, to ease initialization
 * of the main array */
const AXIS_DEFAULTS axis_defaults[AXIS_ARRAY_SIZE] = {
    { -10, 10, "z" , TICS_ON_BORDER,               },
    { -10, 10, "y" , TICS_ON_BORDER | TICS_MIRROR, },
    { -10, 10, "x" , TICS_ON_BORDER | TICS_MIRROR, },
    { -10, 10, "cb", TICS_ON_BORDER | TICS_MIRROR, },
    { -10, 10, "z2", NO_TICS,                      },
    { -10, 10, "y2", NO_TICS,                      },
    { -10, 10, "x2", NO_TICS,                      },
    { - 0, 10, "r" , TICS_ON_AXIS,                 },
    { - 5,  5, "t" , NO_TICS,                      },
    { - 5,  5, "u" , NO_TICS,                      },
    { - 5,  5, "v" , NO_TICS,                      },
};


/* either the 'set format <axis>' or an automatically invented time
 * format string */
static char ticfmt[AXIS_ARRAY_SIZE][MAX_ID_LEN+1];

/* HBB 20010831: new enum typedef, to make code using this more
 * self-explanatory */
/* The unit the tics of a given time/date axis are to interpreted in */
/* HBB 20040318: start at one, to avoid undershoot */
typedef enum e_timelevel {
    TIMELEVEL_SECONDS = 1, TIMELEVEL_MINUTES, TIMELEVEL_HOURS,
    TIMELEVEL_DAYS, TIMELEVEL_WEEKS, TIMELEVEL_MONTHS,
    TIMELEVEL_YEARS
} t_timelevel;
static t_timelevel timelevel[AXIS_ARRAY_SIZE];

/* The <increment> given in a 'set {x|y|...}tics', or an automatically
 * generated one, if automatic tic placement is active */
static double ticstep[AXIS_ARRAY_SIZE];

/* HBB 20000506 new variable: parsing table for use with the table
 * module, to help generalizing set/show/unset/save, where possible */
const struct gen_table axisname_tbl[AXIS_ARRAY_SIZE + 1] =
{
    { "z", FIRST_Z_AXIS},
    { "y", FIRST_Y_AXIS},
    { "x", FIRST_X_AXIS},
    { "cb",COLOR_AXIS},
    { "z2",SECOND_Z_AXIS},
    { "y2",SECOND_Y_AXIS},
    { "x2",SECOND_X_AXIS},
    { "r", POLAR_AXIS},
    { "t", T_AXIS},
    { "u", U_AXIS},
    { "v", V_AXIS},
    { NULL, -1}
};


/* 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 */
/* HBB 20000416: they may not need to be array[]ed, but it'd sure
 * make coding easier, in some places... */
/* HBB 20000416: for the testing, these are global... */
/* static */ int tic_start, tic_direction, tic_text,
    rotate_tics, tic_hjust, tic_vjust, tic_mirror;

/* These are declare volatile in order to fool the compiler into not */
/* optimizing out intermediate values, thus hiding loss of precision.*/
volatile double vol_this_tic;
volatile double vol_previous_tic;

const struct ticdef default_axis_ticdef = DEFAULT_AXIS_TICDEF;

/* axis labels */
const text_label default_axis_label = EMPTY_LABELSTRUCT;

/* zeroaxis drawing */
const lp_style_type default_axis_zeroaxis = DEFAULT_AXIS_ZEROAXIS;

/* grid drawing */
/* int grid_selection = GRID_OFF; */
#define DEFAULT_GRID_LP {0, LT_AXIS, 0, 0, 1.0, 0.0, FALSE, DEFAULT_COLORSPEC}
const struct lp_style_type default_grid_lp = DEFAULT_GRID_LP;
struct lp_style_type grid_lp   = DEFAULT_GRID_LP;
struct lp_style_type mgrid_lp  = DEFAULT_GRID_LP;
int grid_layer = -1;
double polar_grid_angle = 0;	/* nonzero means a polar grid */
TBOOLEAN raxis = TRUE;

/* Length of the longest tics label, set by widest_tic_callback(): */
int widest_tic_strlen;

/* flag to indicate that in-line axis ranges should be ignored */
TBOOLEAN inside_zoom;

/* axes being used by the current plot */
/* These are mainly convenience variables, replacing separate copies of
 * such variables originally found in the 2D and 3D plotting code */
AXIS_INDEX x_axis = FIRST_X_AXIS;
AXIS_INDEX y_axis = FIRST_Y_AXIS;
AXIS_INDEX z_axis = FIRST_Z_AXIS;

/* --------- internal prototypes ------------------------- */
static double make_auto_time_minitics __PROTO((t_timelevel, double));
static double make_tics __PROTO((AXIS_INDEX, int));
static double quantize_time_tics __PROTO((AXIS_INDEX, double, double, int));
static double time_tic_just __PROTO((t_timelevel, double));
static double round_outward __PROTO((AXIS_INDEX, TBOOLEAN, double));
static TBOOLEAN axis_position_zeroaxis __PROTO((AXIS_INDEX));
static double quantize_duodecimal_tics __PROTO((double, int));
static void get_position_type __PROTO((enum position_type * type, int *axes));

/* ---------------------- routines ----------------------- */

/* check range and take logs of min and max if logscale
 * this also restores min and max for ranges like [10:-10]
 */
#define LOG_MSG(x) x " range must be greater than 0 for scale"

/* {{{ axis_unlog_interval() */

/* this is used in a few places all over the code: undo logscaling of
 * a given range if necessary. If checkrange is TRUE, will int_error() if
 * range is invalid */
void
axis_unlog_interval(AXIS_INDEX axis, double *min, double *max, TBOOLEAN checkrange)
{
    if (axis_array[axis].log) {
	if (checkrange && (*min<= 0.0 || *max <= 0.0))
	    int_error(NO_CARET,
		      "%s range must be greater than 0 for log scale",
		      axis_defaults[axis].name);
	*min = (*min<=0) ? -VERYLARGE : AXIS_DO_LOG(axis,*min);
	*max = (*max<=0) ? -VERYLARGE : AXIS_DO_LOG(axis,*max);
    }
}

/* }}} */

/* {{{ axis_revert_and_unlog_range() */

void
axis_revert_and_unlog_range(AXIS_INDEX axis)
{
  if (axis_array[axis].range_is_reverted) {
    double temp = axis_array[axis].min;
    axis_array[axis].min = axis_array[axis].max;
    axis_array[axis].max = temp;
  }
  axis_unlog_interval(axis, &axis_array[axis].min, &axis_array[axis].max, 1);
}

/* }}} */

/* {{{ axis_log_value_checked() */
double
axis_log_value_checked(AXIS_INDEX axis, double coord, const char *what)
{
    if (axis_array[axis].log) {
	if (coord <= 0.0) {
	    graph_error("%s has %s coord of %g; must be above 0 for log scale!",
			what, axis_defaults[axis].name, coord);
	} else
	    return (AXIS_DO_LOG(axis,coord));
    }
    return (coord);
}

/* }}} */

/* {{{ axis_checked_extend_empty_range() */
/*
 * === SYNOPSIS ===
 *
 * This function checks whether the data and/or plot range in a given axis
 * is too small (which would cause divide-by-zero and/or infinite-loop
 * problems later on).  If so,
 * - if autoscaling is in effect for this axis, we widen the range
 * - otherwise, we abort with a call to  int_error()  (which prints out
 *   a suitable error message, then (hopefully) aborts this command and
 *   returns to the command prompt or whatever).
 *
 *
 * === HISTORY AND DESIGN NOTES ===
 *
 * 1998 Oct 4, Jonathan Thornburg <jthorn@galileo.thp.univie.ac.at>
 *
 * This function used to be a (long) macro  FIXUP_RANGE(AXIS, WHICH)
 * which was (identically!) defined in  plot2d.c  and  plot3d.c .  As
 * well as now being a function instead of a macro, the logic is also
 * changed:  The "too small" range test no longer depends on 'set zero'
 * and is now properly scaled relative to the data magnitude.
 *
 * The key question in designing this function is the policy for just how
 * much to widen the data range by, as a function of the data magnitude.
 * This is to some extent a matter of taste.  IMHO the key criterion is
 * that (at least) all of the following should (a) not infinite-loop, and
 * (b) give correct plots, regardless of the 'set zero' setting:
 *      plot 6.02e23            # a huge number >> 1 / FP roundoff level
 *      plot 3                  # a "reasonable-sized" number
 *      plot 1.23e-12           # a small number still > FP roundoff level
 *      plot 1.23e-12 * sin(x)  # a small function still > FP roundoff level
 *      plot 1.23e-45           # a tiny number << FP roundoff level
 *      plot 1.23e-45 * sin(x)  # a tiny function << FP roundoff level
 *      plot 0          # or (more commonly) a data file of all zeros
 * That is, IMHO gnuplot should *never* infinite-loop, and it should *never*
 * producing an incorrect or misleading plot.  In contrast, the old code
 * would infinite-loop on most of these examples with 'set zero 0.0' in
 * effect, or would plot the small-amplitude sine waves as the zero function
 * with 'zero' set larger than the sine waves' amplitude.
 *
 * The current code plots all the above examples correctly and without
 * infinite looping.
 *
 * HBB 2000/05/01: added an additional up-front test, active only if
 *   the new 'mesg' parameter is non-NULL.
 *
 * === USAGE ===
 *
 * Arguments:
 * axis = (in) An integer specifying which axis (x1, x2, y1, y2, z, etc)
 *             we should do our stuff for.  We use this argument as an
 *             index into the global arrays  {min,max,auto}_array .  In
 *             practice this argument will typically be one of the constants
 *              {FIRST,SECOND}_{X,Y,Z}_AXIS  defined in plot.h.
 * mesg = (in) if non-NULL, will check if the axis range is valid (min
 *             not +VERYLARGE, max not -VERYLARGE), and int_error() out
 *             if it isn't.
 *
 * Global Variables:
 * auto_array, min_array, max_array (in out) (defined in axis.[ch]):
 *    variables describing the status of autoscaling and range ends, for
 *    each of the possible axes.
 *
 * c_token = (in) (defined in plot.h) Used in formatting an error message.
 *
 */
void
axis_checked_extend_empty_range(AXIS_INDEX axis, const char *mesg)
{
    /* These two macro definitions set the range-widening policy: */

    /* widen [0:0] by +/- this absolute amount */
#define FIXUP_RANGE__WIDEN_ZERO_ABS	1.0
    /* widen [nonzero:nonzero] by -/+ this relative amount */
#define FIXUP_RANGE__WIDEN_NONZERO_REL	0.01

    double dmin = axis_array[axis].min;
    double dmax = axis_array[axis].max;

    /* HBB 20000501: this same action was taken just before most of
     * the invocations of this function, so I moved it into here.
     * Only do this if 'mesg' is non-NULL --> pass NULL if you don't
     * want the test */
    if (mesg
	&& (axis_array[axis].min == VERYLARGE
	    || axis_array[axis].max == -VERYLARGE))
	int_error(c_token, mesg);

    if (dmax - dmin == 0.0) {
	/* empty range */
	if (axis_array[axis].autoscale) {
	    /* range came from autoscaling ==> widen it */
	    double widen = (dmax == 0.0) ?
		FIXUP_RANGE__WIDEN_ZERO_ABS
		: FIXUP_RANGE__WIDEN_NONZERO_REL * fabs(dmax);
	    if (!(axis == FIRST_Z_AXIS && !mesg)) /* set view map */
		fprintf(stderr, "Warning: empty %s range [%g:%g], ",
		    axis_defaults[axis].name, dmin, dmax);
	    /* HBB 20010525: correctly handle single-ended
	     * autoscaling, too: */
	    if (axis_array[axis].autoscale & AUTOSCALE_MIN)
		axis_array[axis].min -= widen;
	    if (axis_array[axis].autoscale & AUTOSCALE_MAX)
		axis_array[axis].max += widen;
	    if (!(axis == FIRST_Z_AXIS && !mesg)) /* set view map */
		fprintf(stderr, "adjusting to [%g:%g]\n",
		    axis_array[axis].min, axis_array[axis].max);
	} else {
	    /* user has explicitly set the range (to something empty)
               ==> we're in trouble */
	    int_error(NO_CARET, "Can't plot with an empty %s range!",
		      axis_defaults[axis].name);
	}
    }
}

/* }}} */

/* {{{ make smalltics for time-axis */
static double
make_auto_time_minitics(t_timelevel tlevel, double incr)
{
    double tinc = 0.0;

    if ((int)tlevel < TIMELEVEL_SECONDS)
	tlevel = TIMELEVEL_SECONDS;
    switch (tlevel) {
    case TIMELEVEL_SECONDS:
    case TIMELEVEL_MINUTES:
	if (incr >= 5)
	    tinc = 1;
	if (incr >= 10)
	    tinc = 5;
	if (incr >= 20)
	    tinc = 10;
	if (incr >= 60)
	    tinc = 20;
	if (incr >= 2 * 60)
	    tinc = 60;
	if (incr >= 6 * 60)
	    tinc = 2 * 60;
	if (incr >= 12 * 60)
	    tinc = 3 * 60;
	if (incr >= 24 * 60)
	    tinc = 6 * 60;
	break;
    case TIMELEVEL_HOURS:
	if (incr >= 20 * 60)
	    tinc = 10 * 60;
	if (incr >= 3600)
	    tinc = 30 * 60;
	if (incr >= 2 * 3600)
	    tinc = 3600;
	if (incr >= 6 * 3600)
	    tinc = 2 * 3600;
	if (incr >= 12 * 3600)
	    tinc = 3 * 3600;
	if (incr >= 24 * 3600)
	    tinc = 6 * 3600;
	break;
    case TIMELEVEL_DAYS:
	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 TIMELEVEL_WEEKS:
	if (incr > 2 * DAY_SEC)
	    tinc = DAY_SEC;
	if (incr > 7 * DAY_SEC)
	    tinc = 7 * DAY_SEC;
	break;
    case TIMELEVEL_MONTHS:
	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 TIMELEVEL_YEARS:
	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);
}
/* }}} */

/* {{{ copy_or_invent_formatstring() */
/* Either copies the axis formatstring over to the ticfmt[] array, or
 * in case that's not applicable because the format hasn't been
 * specified correctly, invents a time/date output format by looking
 * at the range of values.  Considers time/date fields that don't
 * change across the range to be unimportant */
/* HBB 20010803: removed two arguments, and renamed function */
char *
copy_or_invent_formatstring(AXIS_INDEX axis)
{
    struct tm t_min, t_max;

    /* HBB 20010803: moved this here ... was done whenever this was called,
     * anyway */
    if ( axis_array[axis].datatype != DT_TIMEDATE
	|| !axis_array[axis].format_is_numeric) {
	/* The simple case: formatstring is usable, so use it! */
	strcpy(ticfmt[axis], axis_array[axis].formatstring);
	/* Ensure enough precision to distinguish tics */
	if (!strcmp(ticfmt[axis], DEF_FORMAT)) {
	    double axmin = AXIS_DE_LOG_VALUE(axis,axis_array[axis].min);
	    double axmax = AXIS_DE_LOG_VALUE(axis,axis_array[axis].max);
	    int precision = (ceil(-log10(fabs(axmax-axmin))));
	    if ((axmin*axmax > 0) && precision > 4)
		sprintf(ticfmt[axis],"%%.%df", (precision>14) ? 14 : precision);
	}

	return ticfmt[axis];
    }

    /* Else, have to invent an output format string. */
    *ticfmt[axis] = 0;		/* make sure we strcat to empty string */

    ggmtime(&t_min, time_tic_just(timelevel[axis], axis_array[axis].min));
    ggmtime(&t_max, time_tic_just(timelevel[axis], axis_array[axis].max));

    if (t_max.tm_year == t_min.tm_year
	&& t_max.tm_yday == t_min.tm_yday) {
	/* same day, skip date */
	if (t_max.tm_hour != t_min.tm_hour) {
	    strcpy(ticfmt[axis], "%H");
	}
	if (timelevel[axis] < TIMELEVEL_DAYS) {
	    if (ticfmt[axis][0])
		strcat(ticfmt[axis], ":");
	    strcat(ticfmt[axis], "%M");
	}
	if (timelevel[axis] < TIMELEVEL_HOURS) {
	    strcat(ticfmt[axis], ":%S");
	}
    } else {
	if (t_max.tm_year != t_min.tm_year) {
	    /* different years, include year in ticlabel */
	    /* check convention, day/month or month/day */
	    if (strchr(axis_array[axis].timefmt, 'm')
		< strchr(axis_array[axis].timefmt, 'd')) {
		strcpy(ticfmt[axis], "%m/%d/%");
	    } else {
		strcpy(ticfmt[axis], "%d/%m/%");
	    }
	    if (((int) (t_max.tm_year / 100)) != ((int) (t_min.tm_year / 100))) {
		strcat(ticfmt[axis], "Y");
	    } else {
		strcat(ticfmt[axis], "y");
	    }

	} else {
	    /* Copy day/month order over from input format */
	    if (strchr(axis_array[axis].timefmt, 'm')
		< strchr(axis_array[axis].timefmt, 'd')) {
		strcpy(ticfmt[axis], "%m/%d");
	    } else {
		strcpy(ticfmt[axis], "%d/%m");
	    }
	}
	if (timelevel[axis] < TIMELEVEL_WEEKS) {
	    /* Note: seconds can't be useful if there's more than 1
	     * day's worth of data... */
	    strcat(ticfmt[axis], "\n%H:%M");
	}
    }
    return ticfmt[axis];
}

/* }}} */

/* {{{ quantize_normal_tics() */
/* the guide parameter was intended to allow the number of tics
 * to depend on the relative sizes of the plot and the font.
 * It is the approximate upper limit on number of tics allowed.
 * But it did not go down well with the users.
 * A value of 20 gives the same behaviour as 3.5, so that is
 * hardwired into the calls to here. Maybe we will restore it
 * to the automatic calculation one day
 */

/* HBB 20020220: Changed to use value itself as first argument, not
 * log10(value).  Done to allow changing the calculation method
 * to avoid numerical problems */
double
quantize_normal_tics(double arg, int guide)
{
    /* order of magnitude of argument: */
    double power = pow(10.0, floor(log10(arg)));
    double xnorm = arg / power;	/* approx number of decades */
    /* we expect 1 <= xnorm <= 10 */
    double posns = guide / xnorm; /* approx number of tic posns per decade */
    /* with guide=20, we expect 2 <= posns <= 20 */
    double tics;

    /* FIXME HBB 20020220: Looking at these, I would normally expect
     * to see posns*tics to be always about the same size. But we
     * rather suddenly drop from 2.0 to 1.0 at tic step 0.5. Why? */
    /* JRV 20021117: fixed this by changing next to last threshold
       from 1 to 2.  However, with guide=20, this doesn't matter. */
    if (posns > 40)
	tics = 0.05;		/* eg 0, .05, .10, ... */
    else if (posns > 20)
	tics = 0.1;		/* eg 0, .1, .2, ... */
    else 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 > 2)
	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 * power);
}

/* }}} */

/* {{{ make_tics() */
/* Implement TIC_COMPUTED case, i.e. automatically choose a usable
 * ticking interval for the given axis. For the meaning of the guide
 * parameter, see the comment on quantize_normal_tics() */
static double
make_tics(AXIS_INDEX axis, int guide)
{
    double xr, tic;

    xr = fabs(axis_array[axis].min - axis_array[axis].max);
    if (xr == 0)
	return 1;	/* Anything will do, since we'll never use it */
    if (xr >= VERYLARGE)
	int_error(NO_CARET,"%s axis range undefined or overflow",
		axis_defaults[axis].name);
    tic = quantize_normal_tics(xr, guide);
    /* FIXME HBB 20010831: disabling this might allow short log axis
     * to receive better ticking... */
    if (axis_array[axis].log && tic < 1.0)
	  tic = 1.0;

    if (axis_array[axis].datatype == DT_TIMEDATE)
	return quantize_time_tics(axis, tic, xr, guide);
    else
	return tic;
}
/* }}} */

/* {{{ quantize_duodecimal_tics */
/* HBB 20020220: New function, to be used to properly tic axes with a
 * duodecimal reference, as used in times (60 seconds, 60 minuts, 24
 * hours, 12 months). Derived from quantize_normal_tics(). The default
 * guide is assumed to be 12, here, not 20 */
static double
quantize_duodecimal_tics(double arg, int guide)
{
    /* order of magnitude of argument: */
    double power = pow(12.0, floor(log(arg)/log(12.0)));
    double xnorm = arg / power;	/* approx number of decades */
    double posns = guide / xnorm; /* approx number of tic posns per decade */

    if (posns > 24)
	return power / 24;	/* half a smaller unit --- shouldn't happen */
    else if (posns > 12)
	return power / 12;	/* one smaller unit */
    else if (posns > 6)
	return power / 6;	/* 2 smaller units = one-6th of a unit */
    else if (posns > 4)
	return power / 4;	/* 3 smaller units = quarter unit */
    else if (posns > 2)
	return power / 2;	/* 6 smaller units = half a unit */
    else if (posns > 1)
	return power;		/* 0, 1, 2, ..., 11 */
    else if (posns > 0.5)
	return power * 2;		/* 0, 2, 4, ..., 10 */
    else if (posns > 1.0/3)
	return power * 3;		/* 0, 3, 6, 9 */
    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
	 */
	return power * ceil(xnorm);
}
/* }}} */

/* {{{ quantize_time_tics */
/* HBB 20010831: newly isolated subfunction. Used to be part of
 * make_tics() */
/* Look at the tic interval given, and round it to a nice figure
 * suitable for time/data axes, i.e. a small integer number of
 * seconds, minutes, hours, days, weeks or months. As a side effec,
 * this routine also modifies the static timelevel[axis] to indicate
 * the units these tics are calculated in. */
static double
quantize_time_tics(AXIS_INDEX axis, double tic, double xr, int guide)
{
    int guide12 = guide * 3 / 5; /* --> 12 for default of 20 */

    timelevel[axis] = TIMELEVEL_SECONDS;
    if (tic > 5) {
	/* turn tic into units of minutes */
	tic = quantize_duodecimal_tics(xr / 60.0, guide12) * 60;
	if (tic >= 60)
	    timelevel[axis] = TIMELEVEL_MINUTES;
    }
    if (tic > 5 * 60) {
	/* turn tic into units of hours */
	tic = quantize_duodecimal_tics(xr / 3600.0, guide12) * 3600;
	if (tic >= 3600)
	    timelevel[axis] = TIMELEVEL_HOURS;
    }
    if (tic > 3600) {
	/* turn tic into units of days */
        tic = quantize_duodecimal_tics(xr / DAY_SEC, guide12) * DAY_SEC;
	if (tic >= DAY_SEC)
	    timelevel[axis] = TIMELEVEL_DAYS;
    }
    if (tic > 2 * DAY_SEC) {
	/* turn tic into units of weeks */
	tic = quantize_normal_tics(xr / WEEK_SEC, guide) * WEEK_SEC;
	if (tic < WEEK_SEC) {	/* force */
	    tic = WEEK_SEC;
	}
	if (tic >= WEEK_SEC)
	    timelevel[axis] = TIMELEVEL_WEEKS;
    }
    if (tic > 3 * WEEK_SEC) {
	/* turn tic into units of month */
	tic = quantize_normal_tics(xr / MON_SEC, guide) * MON_SEC;
	if (tic < MON_SEC) {	/* force */
	    tic = MON_SEC;
	}
	if (tic >= MON_SEC)
	    timelevel[axis] = TIMELEVEL_MONTHS;
    }
    if (tic > MON_SEC) {
	/* turn tic into units of years */
	tic = quantize_duodecimal_tics(xr / YEAR_SEC, guide12) * YEAR_SEC;
	if (tic >= YEAR_SEC)
	    timelevel[axis] = TIMELEVEL_YEARS;
    }
    return (tic);
}

/* }}} */


/* {{{ round_outward */
/* HBB 20011204: new function (repeated code ripped out of setup_tics)
 * that rounds an axis endpoint outward. If the axis is a time/date
 * one, take care to round towards the next whole time unit, not just
 * a multiple of the (averaged) tic size */
static double
round_outward(
    AXIS_INDEX axis,		/* Axis to work on */
    TBOOLEAN upwards,		/* extend upwards or downwards? */
    double input)		/* the current endpoint */
{
    double tic = ticstep[axis];
    double result = tic * (upwards
			   ? ceil(input / tic)
			   : floor(input / tic));

    if (axis_array[axis].datatype == DT_TIMEDATE) {
	double ontime = time_tic_just(timelevel[axis], result);

	/* FIXME: how certain is it that we don't want to *always*
	 * return 'ontime'? */
	if ((upwards && (ontime > result))
	    || (!upwards && (ontime <result)))
	    return ontime;
    }

    return result;
}

/* }}} */

/* {{{ setup_tics */
/* setup_tics allows max number of tics to be specified but users dont
 * like it to change with size and font, so we use value of 20, which
 * is 3.5 behaviour.  Note also that if format is '', yticlin = 0, so
 * this gives division by zero.  */

void
setup_tics(AXIS_INDEX axis, int max)
{
    double tic = 0;
    AXIS *this = axis_array + axis;
    struct ticdef *ticdef = &(this->ticdef);

    /* HBB 20010703: New: allow _not_ to autoextend the axis endpoints
     * to an integer multiple of the ticstep, for autoscaled axes with
     * automatic tics */
    TBOOLEAN autoextend_min = (this->autoscale & AUTOSCALE_MIN)
	&& ! (this->autoscale & AUTOSCALE_FIXMIN);
    TBOOLEAN autoextend_max = (this->autoscale & AUTOSCALE_MAX)
	&& ! (this->autoscale & AUTOSCALE_FIXMAX);

    /*  Apply constraints on autoscaled axis if requested:
     *  The range is _expanded_ here only.  Limiting the range is done
     *  in the macro STORE_WITH_LOG_AND_UPDATE_RANGE() of axis.h  */
    if (this->autoscale & AUTOSCALE_MIN) {
      	if (this->min_constraint & CONSTRAINT_UPPER) {
	    if (this->min > this->min_ub)
		this->min = this->min_ub;
	}
    }
    if (this->autoscale & AUTOSCALE_MAX) {
	if (this->max_constraint & CONSTRAINT_LOWER) {
	    if (this->max < this->max_lb)
		this->max = this->max_lb;
	}
    }

    /* HBB 20000506: if no tics required for this axis, do
     * nothing. This used to be done exactly before each call of
     * setup_tics, anyway... */
    if (! this->ticmode)
	return;

    if (ticdef->type == TIC_SERIES) {
	ticstep[axis] = tic = ticdef->def.series.incr;
	autoextend_min = autoextend_min
	                 && (ticdef->def.series.start == -VERYLARGE);
	autoextend_max = autoextend_max
	                 && (ticdef->def.series.end == VERYLARGE);
    } else if (ticdef->type == TIC_COMPUTED) {
	ticstep[axis] = tic = make_tics(axis, max);
    } else {
	/* user-defined, day or month */
	autoextend_min = autoextend_max = FALSE;
    }

    /* If an explicit stepsize was set, timelevel[axis] wasn't defined,
     * leading to strange misbehaviours of minor tics on time axes.
     * We used to call quantize_time_tics, but that also caused strangeness.
     */
    if (this->datatype == DT_TIMEDATE && ticdef->type == TIC_SERIES) {
	if      (tic >= 365*24*60*60.) timelevel[axis] = TIMELEVEL_YEARS;
	else if (tic >=  28*24*60*60.) timelevel[axis] = TIMELEVEL_MONTHS;
	else if (tic >=   7*24*60*60.) timelevel[axis] = TIMELEVEL_WEEKS;
	else if (tic >=     24*60*60.) timelevel[axis] = TIMELEVEL_DAYS;
	else if (tic >=        60*60.) timelevel[axis] = TIMELEVEL_HOURS;
	else if (tic >=           60.) timelevel[axis] = TIMELEVEL_MINUTES;
	else                           timelevel[axis] = TIMELEVEL_SECONDS;
    }

    if (autoextend_min) {
	this->min = round_outward(axis, ! (this->min < this->max), this->min);
	if (this->min_constraint & CONSTRAINT_LOWER && this->min < this->min_lb)
	    this->min = this->min_lb;
    }

    if (autoextend_max) {
	this->max = round_outward(axis, this->min < this->max, this->max);
	if (this->max_constraint & CONSTRAINT_UPPER && this->max > this->max_ub)
	    this->max = this->max_ub;
    }


    /* Set up ticfmt[axis] correctly. If necessary (time axis, but not
     * time/date output format), make up a formatstring that suits the
     * range of data */
    copy_or_invent_formatstring(axis);
}

/* }}} */

/* {{{  gen_tics */
/* uses global arrays ticstep[], ticfmt[], axis_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_INDEX axis, tic_callback callback)
{
    struct ticdef *def = &axis_array[axis].ticdef;
    int minitics = axis_array[axis].minitics; /* off/default/auto/explicit */
    double minifreq = axis_array[axis].mtic_freq; /* minitic frequency */

    struct lp_style_type lgrd = grid_lp;
    struct lp_style_type mgrd = mgrid_lp;

    if (! axis_array[axis].gridmajor)
	lgrd.l_type = LT_NODRAW;
    if (! axis_array[axis].gridminor)
	mgrd.l_type = LT_NODRAW;


    if (def->def.user) {	/* user-defined tic entries */
	struct ticmark *mark = def->def.user;
	double uncertain = (axis_array[axis].max - axis_array[axis].min) / 10;
	double internal_min = axis_array[axis].min - SIGNIF * uncertain;
	double internal_max = axis_array[axis].max + SIGNIF * uncertain;
	double log10_base = axis_array[axis].log ? log10(axis_array[axis].base) : 1.0;
	double polar_shift = 0;

	/* polar labels always +ve, and if rmin has been set, they are
	 * relative to rmin.
	 */
	if (polar) {
	    if (!(R_AXIS.autoscale & AUTOSCALE_MIN))
		polar_shift = R_AXIS.min;
	    internal_min = X_AXIS.min - SIGNIF * uncertain;
	    internal_max = X_AXIS.max + SIGNIF * uncertain;
	}

	for (mark = def->def.user; mark; mark = mark->next) {
	    char label[MAX_ID_LEN];
	    char *ticlabel;
	    double internal;

	    /* This condition is only possible if we are in polar mode */
	    if (axis == POLAR_AXIS)
		internal = AXIS_LOG_VALUE(POLAR_AXIS,mark->position)
			 - AXIS_LOG_VALUE(POLAR_AXIS,polar_shift);
	    else
		internal = AXIS_LOG_VALUE(axis,mark->position) - polar_shift;

	    if (!inrange(internal, internal_min, internal_max))
		continue;

	    if (mark->level < 0) {
		/* label read from data file */
		ticlabel = mark->label;
	    } else if (mark->label && !strchr(mark->label, '%')) {
		/* string constant that contains no format keys */
		ticlabel = mark->label;
	    } else if (axis_array[axis].datatype == DT_TIMEDATE) {
		gstrftime(label, MAX_ID_LEN-1, mark->label ? mark->label : ticfmt[axis], mark->position);
		ticlabel = label;
	    } else {
		gprintf(label, sizeof(label), mark->label ? mark->label : ticfmt[axis], log10_base, mark->position);
		ticlabel = label;
	    }

	    /* use NULL instead of label for minitic */
	    (*callback) (axis, internal,
	    		(mark->level>0)?NULL:ticlabel,
	    		(mark->level>0)?mgrd:lgrd, NULL);

	    /* Polar axis tics are mirrored across the origin */
	    if (axis == POLAR_AXIS && (R_AXIS.ticmode & TICS_MIRROR)) {
		int save_gridline = lgrd.l_type;
		lgrd.l_type = LT_NODRAW;
		(*callback) (axis, -internal,
			(mark->level>0)?NULL:ticlabel,
	    		(mark->level>0)?mgrd:lgrd, NULL);
		lgrd.l_type = save_gridline;
	    }

	}
	if (def->type == TIC_USER)
	    return;
    }

    /* 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 = axis_array[axis].min, lmax = axis_array[axis].max;
	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 log10_base = axis_array[axis].log ? log10(axis_array[axis].base) : 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 (axis_array[axis].log && axis != POLAR_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 = AXIS_DO_LOG(axis, def->def.series.incr);
		if (def->def.series.start <= 0)	/* includes case 'undefined, i.e. -VERYLARGE */
		    start = step * floor(lmin / step);
		else
		    start = AXIS_DO_LOG(axis, def->def.series.start);
		if (def->def.series.end == VERYLARGE)
		    end = step * ceil(lmax / step);
		else
		    end = AXIS_DO_LOG(axis, def->def.series.end);
	    } 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 && axis_array[axis].miniticscale != 0) {
	    /* {{{  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 if (axis_array[axis].log) {
 		    ministart = ministep = step / minifreq * axis_array[axis].base;
 		    miniend = step * axis_array[axis].base;
 		} else {
		    ministart = ministep = step / minifreq;
		    miniend = step;
 		}
	    } else if (axis_array[axis].log) {
		if (step > 1.5) {	/* beware rounding errors */
		    /* {{{  10,100,1000 case */
		    /* no more than five minitics */
		    if (step < 65535) /* should be MAXINT */
			ministart = ministep = (int)(0.2 * step);
		    else
			ministart = ministep = 0.2 * step;
		    if (ministep < 1)
			ministart = ministep = 1;
		    miniend = step;
		    /* }}} */
		} else {
		    /* {{{  2,5,8 case */
		    miniend = axis_array[axis].base;
		    if (end - start >= 10)
			minitics = 0;	/* none */
		    else if (end - start >= 5) {
			ministart = 2;
			ministep = 3;
		    } else {
			ministart = 2;
			ministep = 1;
		    }
		    /* }}} */
		}
	    } else if (axis_array[axis].datatype == DT_TIMEDATE) {
		ministart = ministep =
		    make_auto_time_minitics(timelevel[axis], step);
		miniend = step * 0.9;
	    } else if (minitics == MINI_AUTO) {
		int k = fabs(step)/pow(10.,floor(log10(fabs(step))));

		/* so that step == k times some power of 10 */
		ministart = ministep = (k==2 ? 0.5 : 0.2) * 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;
	/* HBB 20011002: adjusting the endpoints doesn't make sense if
	 * some oversmart user used a ticstep (much) larger than the
	 * yrange itself */
	if (step < (fabs(lmax) + fabs(lmin))) {
	    internal_max = lmax + step * SIGNIF;
	    internal_min = lmin - step * SIGNIF;
	} else {
	    internal_max = lmax;
	    internal_min = lmin;
	}

	if (step == 0)
	    return;		/* just quietly ignore them ! */
	/* }}} */

	/* This protects against user error, not precision errors */
	if ( (internal_max-internal_min)/step > term->xmax) {
	    int_warn(NO_CARET,"Too many axis ticks requested (>%.0g)",
		(internal_max-internal_min)/step);
	    return;
	}

	/* This protects against infinite loops if the separation between   */
	/* two ticks is less than the precision of the control variables.   */
	/* The for(...) loop here must be identical to the true loop below. */
	/* Furthermore, compiler optimization can muck up this test, so we  */
	/* tell the compiler that the control variables are volatile.       */
	if (1) /* (some-test-for-range-and-or-step-size) */ {
	    vol_previous_tic = start-step;
	    for (vol_this_tic = start; vol_this_tic <= end; vol_this_tic += step) {
		if (fabs(vol_this_tic - vol_previous_tic) < (step/4.)) {
		    step = end - start;
		    int_warn(NO_CARET, "tick interval too small for machine precision");
		    break;
		}
		vol_previous_tic = vol_this_tic;
	    }
	}

	for (tic = start; tic <= end; tic += step) {

	    /* {{{  calc internal and user co-ords */
	    if (axis == POLAR_AXIS) {
		/* Defer translation until after limit check */
		internal = tic;
	    } else if (!axis_array[axis].log) {
		internal = (axis_array[axis].datatype == DT_TIMEDATE)
		    ? time_tic_just(timelevel[axis], tic)
		    : tic;
		user = CheckZero(internal, step);
	    } else {
		/* log scale => dont need to worry about zero ? */
		internal = tic;
		user = AXIS_UNDO_LOG(axis, internal);
	    }
	    /* }}} */
	    if (internal > internal_max)
		break;		/* gone too far - end of series = VERYLARGE perhaps */
	    if (internal >= internal_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,
					def->def.user);
			break;
		    }
		case TIC_MONTH:{
			int m = (long) floor(user - 1) % 12;
			if (m < 0)
			    m += 12;
			(*callback) (axis, internal, abbrev_month_names[m], lgrd,
					def->def.user);
			break;
		    }
		default:{	/* comp or series */
			char label[MAX_ID_LEN]; /* Leave room for enhanced text markup */
			if (axis_array[axis].datatype == DT_TIMEDATE) {
			    /* If they are doing polar time plot, good luck to them */
			    gstrftime(label, MAX_ID_LEN-1, ticfmt[axis], (double) user);
			} else if (polar) {
			    double min = (R_AXIS.autoscale & AUTOSCALE_MIN) ? 0 : R_AXIS.min;
			    double r = fabs(user) + min;
			    /* POLAR_AXIS is the only sane axis, where the actual value */
			    /* is stored and we shift its position just before plotting.*/
			    if (axis == POLAR_AXIS) {
				internal = AXIS_LOG_VALUE(axis, tic)
					 - AXIS_LOG_VALUE(axis, R_AXIS.min);
				r = tic;
			    }

			    gprintf(label, sizeof(label), ticfmt[axis], log10_base, r);
			} else {
			    gprintf(label, sizeof(label), ticfmt[axis], log10_base, user);
			}

			/* Range-limited tic placement */
			if (def->rangelimited
			&&  !inrange(internal,axis_array[axis].data_min,axis_array[axis].data_max))
			    continue;

			(*callback) (axis, internal, label, lgrd, def->def.user);

	 		/* Polar axis tics are mirrored across the origin */
			if (axis == POLAR_AXIS && (R_AXIS.ticmode & TICS_MIRROR)) {
			    int save_gridline = lgrd.l_type;
			    lgrd.l_type = LT_NODRAW;
			    (*callback) (axis, -internal, label, lgrd, def->def.user);
			    lgrd.l_type = save_gridline;
			}
	    		
		    }
		}
		/* }}} */

	    }
	    if (minitics && axis_array[axis].miniticscale != 0) {
		/* {{{  process minitics */
		double mplace, mtic;
		for (mplace = ministart; mplace < miniend; mplace += ministep) {
		    if (axis_array[axis].datatype == DT_TIMEDATE)
			mtic = time_tic_just(timelevel[axis] - 1,
					     internal + mplace);
		    else
			mtic = internal
			    + (axis_array[axis].log && step <= 1.5
			       ? AXIS_DO_LOG(axis,mplace)
			       : mplace);
		    if (inrange(mtic, internal_min, internal_max)
			&& inrange(mtic, start - step * SIGNIF, end + step * SIGNIF))
			(*callback) (axis, mtic, NULL, mgrd, NULL);
		}
		/* }}} */
	    }
	}
    }
}

/* }}} */

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

    if (level <= TIMELEVEL_SECONDS) {
	return (ticplace);
    }
    ggmtime(&tm, ticplace);
    if (level >= TIMELEVEL_MINUTES) { /* units of minutes */
	if (tm.tm_sec > 55)
	    tm.tm_min++;
	tm.tm_sec = 0;
    }
    if (level >= TIMELEVEL_HOURS) { /* units of hours */
	if (tm.tm_min > 55)
	    tm.tm_hour++;
	tm.tm_min = 0;
    }
    if (level >= TIMELEVEL_DAYS) { /* units of days */
	if (tm.tm_hour > 22) {
	    tm.tm_hour = 0;
	    tm.tm_mday = 0;
	    tm.tm_yday++;
	    ggmtime(&tm, gtimegm(&tm));
	}
    }
    /* skip it, I have not bothered with weekday so far */
    if (level >= TIMELEVEL_MONTHS) {/* 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;
    }

    ticplace = gtimegm(&tm);
    return (ticplace);
}
/* }}} */


/* {{{ axis_output_tics() */
/* HBB 20000416: new routine. Code like this appeared 4 times, once
 * per 2D axis, in graphics.c. Always slightly different, of course,
 * but generally, it's always the same. I distinguish two coordinate
 * directions, here. One is the direction of the axis itself (the one
 * it's "running" along). I refer to the one orthogonal to it as
 * "non-running", below. */
void
axis_output_tics(
     AXIS_INDEX axis,		/* axis number we're dealing with */
     int *ticlabel_position,	/* 'non-running' coordinate */
     AXIS_INDEX zeroaxis_basis,	/* axis to base 'non-running' position of
				 * zeroaxis on */
     tic_callback callback)	/* tic-drawing callback function */
{
    struct termentry *t = term;
    TBOOLEAN axis_is_vertical = ((axis % SECOND_AXES) == FIRST_Y_AXIS);
    TBOOLEAN axis_is_second = ((axis / SECOND_AXES) == 1);
    int axis_position;		/* 'non-running' coordinate */
    int mirror_position;	/* 'non-running' coordinate, 'other' side */

    if (zeroaxis_basis / SECOND_AXES) {
	axis_position = axis_array[zeroaxis_basis].term_upper;
	mirror_position = axis_array[zeroaxis_basis].term_lower;
    } else {
	axis_position = axis_array[zeroaxis_basis].term_lower;
	mirror_position = axis_array[zeroaxis_basis].term_upper;
    }

    if (axis_array[axis].ticmode) {
	/* set the globals needed by the _callback() function */

	if (axis_array[axis].tic_rotate == TEXT_VERTICAL
	    && (*t->text_angle)(TEXT_VERTICAL)) {
	    tic_hjust = axis_is_vertical
		? CENTRE
		: (axis_is_second ? LEFT : RIGHT);
	    tic_vjust = axis_is_vertical
		? (axis_is_second ? JUST_TOP : JUST_BOT)
		: JUST_CENTRE;
	    rotate_tics = TEXT_VERTICAL;
	    /* FIXME HBB 20000501: why would we want this? */
	    if (axis == FIRST_Y_AXIS)
		(*ticlabel_position) += t->v_char / 2;
	/* EAM - allow rotation by arbitrary angle in degrees      */
	/*       Justification of ytic labels is a problem since   */
	/*	 the position is already [mis]corrected for length */
	} else if (axis_array[axis].tic_rotate
		   && (*t->text_angle)(axis_array[axis].tic_rotate)) {
	    switch (axis) {
	    case FIRST_Y_AXIS:		/* EAM Purely empirical shift - is there a better? */
	    				*ticlabel_position += t->h_char * 2.5;
	    				tic_hjust = RIGHT; break;
	    case SECOND_Y_AXIS:		tic_hjust = LEFT;  break;
	    case FIRST_X_AXIS:		tic_hjust = LEFT;  break;
	    case SECOND_X_AXIS:		tic_hjust = LEFT;  break;
	    default:			tic_hjust = LEFT;  break;
	    }
	    tic_vjust = JUST_CENTRE;
	    rotate_tics = axis_array[axis].tic_rotate;
	} else {
	    tic_hjust = axis_is_vertical
		? (axis_is_second ? LEFT : RIGHT)
		: CENTRE;
	    tic_vjust = axis_is_vertical
		? JUST_CENTRE
		: (axis_is_second ? JUST_BOT : JUST_TOP);
	    rotate_tics = 0;
	}

	if (axis_array[axis].manual_justify)
	    tic_hjust = axis_array[axis].label.pos;
	else
	    axis_array[axis].label.pos = tic_hjust;

	if (axis_array[axis].ticmode & TICS_MIRROR)
	    tic_mirror = mirror_position;
	else
	    tic_mirror = -1;	/* no thank you */

	if ((axis_array[axis].ticmode & TICS_ON_AXIS)
	    && !axis_array[zeroaxis_basis].log
	    && inrange(0.0, axis_array[zeroaxis_basis].min,
		       axis_array[zeroaxis_basis].max)
	    ) {
	    tic_start = AXIS_MAP(zeroaxis_basis, 0.0);
	    tic_direction = axis_is_second ? 1 : -1;
	    if (axis_array[axis].ticmode & TICS_MIRROR)
		tic_mirror = tic_start;
	    /* put text at boundary if axis is close to boundary and the
	     * corresponding boundary is switched on */
	    if (axis_is_vertical) {
		if (((axis_is_second ? -1 : 1) * (tic_start - axis_position)
		     > (3 * t->h_char))
		    || (!axis_is_second && (!(draw_border & 2)))
		    || (axis_is_second && (!(draw_border & 8))))
		    tic_text = tic_start;
		else
		    tic_text = axis_position;
		tic_text += (axis_is_second ? 1 : -1) * t->h_char;
	    } else {
		if (((axis_is_second ? -1 : 1) * (tic_start - axis_position)
		     > (2 * t->v_char))
		    || (!axis_is_second && (!(draw_border & 1)))
		    || (axis_is_second && (!(draw_border & 4))))
		    tic_text = tic_start +
			(axis_is_second ? 0
			 : - axis_array[axis].ticscale * t->v_tic);
		else
		    tic_text = axis_position;
		tic_text -= t->v_char;
	    }
	} else {
	    /* tics not on axis --> on border */
	    tic_start = axis_position;
	    tic_direction = (axis_array[axis].tic_in ? 1 : -1) * (axis_is_second ? -1 : 1);
	    tic_text = (*ticlabel_position);
	}
	/* go for it */
	gen_tics(axis, callback);
	(*t->text_angle) (0);	/* reset rotation angle */
    }
}

/* }}} */
		 
/* {{{ axis_set_graphical_range() */

void
axis_set_graphical_range(AXIS_INDEX axis, unsigned int lower, unsigned int upper)
{
    axis_array[axis].term_lower = lower;
    axis_array[axis].term_upper = upper;
}
/* }}} */


/* {{{ axis_position_zeroaxis */
static TBOOLEAN
axis_position_zeroaxis(AXIS_INDEX axis)
{
    TBOOLEAN is_inside = FALSE;
    AXIS *this = axis_array + axis;

    /* NB: This is the only place that axis->term_zero is set. */
    /*     So it is important to reach here before plotting.   */
    if ((this->min > 0.0 && this->max > 0.0)
	|| this->log) {
	this->term_zero = (this->max < this->min)
	    ? this->term_upper : this->term_lower;
    } else if (this->min < 0.0 && this->max < 0.0) {
	this->term_zero = (this->max < this->min)
	    ? this->term_lower : this->term_upper;
    } else {
	this->term_zero = AXIS_MAP(axis, 0.0);
	is_inside = TRUE;
    }

    return is_inside;
}
/* }}} */


/* {{{ axis_draw_2d_zeroaxis() */
void
axis_draw_2d_zeroaxis(AXIS_INDEX axis, AXIS_INDEX crossaxis)
{
    AXIS *this = axis_array + axis;

    if (axis_position_zeroaxis(crossaxis)
	    && (this->zeroaxis.l_type > LT_NODRAW)) {
	term_apply_lp_properties(&this->zeroaxis);
	if ((axis % SECOND_AXES) == FIRST_X_AXIS) {
	    (*term->move) (this->term_lower, axis_array[crossaxis].term_zero);
	    (*term->vector) (this->term_upper, axis_array[crossaxis].term_zero);
	} else {
	    (*term->move) (axis_array[crossaxis].term_zero, this->term_lower);
	    (*term->vector) (axis_array[crossaxis].term_zero, this->term_upper);
	}
    }
}
/* }}} */

void load_one_range(AXIS_INDEX axis, double *a, t_autoscale *autoscale, t_autoscale which )
{
    double number;
    
    assert( which==AUTOSCALE_MIN || which==AUTOSCALE_MAX );

    if (equals(c_token, "*")) {
	/*  easy:  do autoscaling!  */
	*autoscale |= which;
	if (which==AUTOSCALE_MIN) {
	    axis_array[axis].min_constraint &= ~CONSTRAINT_LOWER;
	    axis_array[axis].min_lb = 0;  /*  dummy entry  */
	} else {
	    axis_array[axis].max_constraint &= ~CONSTRAINT_LOWER;
	    axis_array[axis].max_lb = 0;  /*  dummy entry  */
	}
	c_token++;
    } else {
	/*  this _might_ be autoscaling with constraint or fixed value */
	/*  The syntax of '0 < *...' confuses the parser as he will try to
            include the '<' as a comparison operator in the expression.
            Setting scanning_range_in_progress will stop the parser from
            trying to build an action table if he finds '<' followed by '*'
            (which would normaly trigger a 'invalid expression'),  */
	scanning_range_in_progress = TRUE;
	GET_NUM_OR_TIME(number, axis);
	scanning_range_in_progress = FALSE;

	if (END_OF_COMMAND)
	    int_error(c_token, "unfinished range");

	if (equals(c_token, "<")) {
	    /*  this _seems_ to be autoscaling with lower bound  */
	    c_token++;
	    if (END_OF_COMMAND) {
		int_error(c_token, "unfinished range with constraint");
	    } else if (equals(c_token, "*")) {
		/*  okay:  this _is_ autoscaling with lower bound!  */
		*autoscale |= which;
		if (which==AUTOSCALE_MIN) {
		    axis_array[axis].min_constraint |= CONSTRAINT_LOWER;
		    axis_array[axis].min_lb = number;
		} else {
		    axis_array[axis].max_constraint |= CONSTRAINT_LOWER;
		    axis_array[axis].max_lb = number;
		}
		c_token++;
	    } else {
		int_error(c_token, "malformed range with constarint");
            }
        } else if (equals(c_token, ">")) {
	    int_error(c_token, "malformed range with constraint (use '<' only)");
	} else { 
	    /*  no autoscaling-with-lower-bound but simple fixed value only  */
	    *autoscale &= ~which;
	    if (which==AUTOSCALE_MIN) {
		axis_array[axis].min_constraint = CONSTRAINT_NONE;
		axis_array[axis].min_ub = 0;  /*  dummy entry  */
	    } else {
		axis_array[axis].max_constraint = CONSTRAINT_NONE;
		axis_array[axis].max_ub = 0;  /*  dummy entry  */
	    }
	    *a = number;
        }
    }
    
    if (*autoscale & which) {
	/*  check for upper bound only if autoscaling is on  */
	if (END_OF_COMMAND)  int_error(c_token, "unfinished range");
	if (equals(c_token, "<")) {
	    /*  looks like upper bound up to now...  */

	    c_token++;
	    if (END_OF_COMMAND) int_error(c_token, "unfinished range with constraint");

	    GET_NUM_OR_TIME(number, axis);
	    /*  this autoscaling has an upper bound:  */

	    if (which==AUTOSCALE_MIN) {
		axis_array[axis].min_constraint |= CONSTRAINT_UPPER;
		axis_array[axis].min_ub = number;
	    } else {
		axis_array[axis].max_constraint |= CONSTRAINT_UPPER;
		axis_array[axis].max_ub = number;
	    }
	} else if (equals(c_token, ">")) {
	    int_error(c_token, "malformed range with constraint (use '<' only)");
	} else {
	    /*  there is _no_ upper bound on this autoscaling  */
	    if (which==AUTOSCALE_MIN) {
		axis_array[axis].min_constraint &= ~CONSTRAINT_UPPER;
		axis_array[axis].min_ub = 0;  /*  dummy entry  */
	    } else {
		axis_array[axis].max_constraint &= ~CONSTRAINT_UPPER;
		axis_array[axis].max_ub = 0;  /*  dummy entry  */
	    }
	}
    } else if (!END_OF_COMMAND){
	/*  no autoscaling = fixed value --> complain about constraints  */
	if (equals(c_token, "<") || equals(c_token, ">") ) {
	    int_error(c_token, "no upper bound constraint allowed if not autoscaling");
	}
    }

    /*  Consitency check  */
    if (*autoscale & which) {
	if (which==AUTOSCALE_MIN && axis_array[axis].min_constraint==CONSTRAINT_BOTH) {
	    if (axis_array[axis].min_ub < axis_array[axis].min_lb ) {
		int_warn(c_token,"Upper bound of constraint < lower bound:  Turning of constraints.");
		axis_array[axis].min_constraint = CONSTRAINT_NONE;
	    }
	}
	if (which==AUTOSCALE_MAX && axis_array[axis].max_constraint==CONSTRAINT_BOTH) {
	    if (axis_array[axis].max_ub < axis_array[axis].max_lb ) {
		int_warn(c_token,"Upper bound of constraint < lower bound:  Turning of constraints.");
		axis_array[axis].max_constraint = CONSTRAINT_NONE;
	    }
	}
    }
}


/* {{{ load_range() */
/* loads a range specification from the input line into variables 'a'
 * and 'b' */
t_autoscale
load_range(AXIS_INDEX axis, double *a, double *b, t_autoscale autoscale)
{
    if (equals(c_token, "]")) {
	axis_array[axis].min_constraint = CONSTRAINT_NONE;
	axis_array[axis].max_constraint = CONSTRAINT_NONE;
	return (autoscale);
    }

    if (END_OF_COMMAND) {
	int_error(c_token, "starting range value or ':' or 'to' expected");
    } else if (!equals(c_token, "to") && !equals(c_token, ":")) {
	load_one_range(axis, a, &autoscale, AUTOSCALE_MIN );
    }

    if (!equals(c_token, "to") && !equals(c_token, ":"))
	int_error(c_token, "':' or keyword 'to' expected");
    c_token++;

    if (!equals(c_token, "]")) {
	load_one_range(axis, b, &autoscale, AUTOSCALE_MAX );
    }

    /* HBB 20030127: If range input backwards, automatically turn on
       the "reverse" option, too. */
    if ((autoscale & AUTOSCALE_BOTH) == AUTOSCALE_NONE) {
      if (*b < *a) {
	double temp = *a;

	*a = *b; *b = temp;
	axis_array[axis].range_flags |= RANGE_REVERSE;
      }
    }

    return (autoscale);
}

/* }}} */


/* we determine length of the widest tick label by getting gen_ticks to
 * call this routine with every label
 */

void
widest_tic_callback(AXIS_INDEX axis, double place, char *text,
    struct lp_style_type grid,
    struct ticmark *userlabels)
{
    (void) axis;		/* avoid "unused parameter" warnings */
    (void) place;
    (void) grid;
    if (text) {			/* minitics have no text at all */
	int len = label_width(text, NULL);
	if (len > widest_tic_strlen)
	    widest_tic_strlen = len;
    }
}


/*
 * get and set routines for range writeback
 * ULIG *
 */

double
get_writeback_min(AXIS_INDEX axis)
{
    /* printf("get min(%d)=%g\n",axis,axis_array[axis].writeback_min); */
    return axis_array[axis].writeback_min;
}

double
get_writeback_max(AXIS_INDEX axis)
{
    /* printf("get max(%d)=%g\n",axis,axis_array[axis].writeback_min); */
    return axis_array[axis].writeback_max;
}

void
set_writeback_min(AXIS_INDEX axis)
{
    double val = AXIS_DE_LOG_VALUE(axis,axis_array[axis].min);
    /* printf("set min(%d)=%g\n",axis,val); */
    axis_array[axis].writeback_min = val;
}

void
set_writeback_max(AXIS_INDEX axis)
{
    double val = AXIS_DE_LOG_VALUE(axis,axis_array[axis].max);
    /* printf("set max(%d)=%g\n",axis,val); */
    axis_array[axis].writeback_max = val;
}

void
save_writeback_all_axes()
{
    AXIS_INDEX axis;

    for (axis = 0; axis < AXIS_ARRAY_SIZE; axis++)
	if (axis_array[axis].range_flags & RANGE_WRITEBACK) {
	    set_writeback_min(axis);
	    set_writeback_max(axis);
	}
}

void
check_axis_reversed( AXIS_INDEX axis )
{
    AXIS *this = axis_array + axis;
    if (((this->autoscale & AUTOSCALE_BOTH) == AUTOSCALE_NONE)
    &&  (this->max < this->min)) {
	double temp = this->min;
	this->min = this->max;
	this->max = temp;
	this->range_is_reverted = 1;
    } else {
	this->range_is_reverted = (this->range_flags & RANGE_REVERSE);
    }
}

TBOOLEAN
some_grid_selected()
{
    AXIS_INDEX i;
    for (i = 0; i <= LAST_REAL_AXIS; i++)
	if (axis_array[i].gridmajor || axis_array[i].gridminor)
	    return TRUE;
    return FALSE;
}

/*
   Check and set the cb-range for use by pm3d or other palette using styles.
   Return 0 on wrong range, otherwise 1.
 */
int
set_cbminmax()
{
    if (CB_AXIS.set_autoscale & AUTOSCALE_MIN) {
	/* -VERYLARGE according to AXIS_INI3D */
	if (CB_AXIS.min >= VERYLARGE)
	    CB_AXIS.min = AXIS_DE_LOG_VALUE(FIRST_Z_AXIS,Z_AXIS.min);
    }
    CB_AXIS.min = axis_log_value_checked(COLOR_AXIS, CB_AXIS.min, "color axis");

    if (CB_AXIS.set_autoscale & AUTOSCALE_MAX) {
	/* -VERYLARGE according to AXIS_INI3D */
	if (CB_AXIS.max <= -VERYLARGE)
	    CB_AXIS.max = AXIS_DE_LOG_VALUE(FIRST_Z_AXIS,Z_AXIS.max);
    }
    CB_AXIS.max = axis_log_value_checked(COLOR_AXIS, CB_AXIS.max, "color axis");

    if (CB_AXIS.min > CB_AXIS.max) {
	/* exchange min and max values */
	double tmp = CB_AXIS.max;
	CB_AXIS.max = CB_AXIS.min;
	CB_AXIS.min = tmp;
    }

    return 1;
}

static void
get_position_type(enum position_type *type, int *axes)
{
    if (almost_equals(c_token, "fir$st")) {
	++c_token;
	*type = first_axes;
    } else if (almost_equals(c_token, "sec$ond")) {
	++c_token;
	*type = second_axes;
    } else if (almost_equals(c_token, "gr$aph")) {
	++c_token;
	*type = graph;
    } else if (almost_equals(c_token, "sc$reen")) {
	++c_token;
	*type = screen;
    } else if (almost_equals(c_token, "char$acter")) {
	++c_token;
	*type = character;
    }
    switch (*type) {
    case first_axes:
	*axes = FIRST_AXES;
	return;
    case second_axes:
	*axes = SECOND_AXES;
	return;
    default:
	*axes = (-1);
	return;
    }
}

/* get_position() - reads a position for label,arrow,key,... */

void
get_position(struct position *pos)
{
    get_position_default(pos,first_axes);
}

/* get_position() - reads a position for label,arrow,key,... 
 * with given default coordinate system
 */
void
get_position_default(struct position *pos, enum position_type default_type)
{
    int axes;
    enum position_type type = default_type;

    memset(pos, 0, sizeof(struct position));

    get_position_type(&type, &axes);
    pos->scalex = type;
    GET_NUMBER_OR_TIME(pos->x, axes, FIRST_X_AXIS);

    if (equals(c_token, ",")) {
	++c_token;
	get_position_type(&type, &axes);
	pos->scaley = type;
	GET_NUMBER_OR_TIME(pos->y, axes, FIRST_Y_AXIS);
    } else {
	pos->y = 0;
	pos->scaley = type;
    }

    /* z is not really allowed for a screen co-ordinate, but keep it simple ! */
    if (equals(c_token, ",")
       /* Partial fix for ambiguous syntax when trailing comma ends a plot command */
	&& !(isstringvalue(c_token+1)) && !(almost_equals(c_token+1,"newhist$ogram"))
	&& !(almost_equals(c_token+1,"for"))
       ) {
	++c_token;
	get_position_type(&type, &axes);
	pos->scalez = type;
	GET_NUMBER_OR_TIME(pos->z, axes, FIRST_Z_AXIS);
    } else {
	pos->z = 0;
	pos->scalez = type;	/* same as y */
    }
}

/*
 * Add a single tic mark, with label, to the list for this axis.
 * To avoid duplications and overprints, sort the list and allow
 * only one label per position.
 * EAM - called from set.c during `set xtics` (level = 0 or 1)
 *       called from datafile.c during `plot using ::xtic()` (level = -1)
 */
void
add_tic_user(AXIS_INDEX axis, char *label, double position, int level)
{
    struct ticmark *tic, *newtic;
    struct ticmark listhead;

    if (!label && level < 0)
	return;

    /* Mark this axis as user-generated ticmarks only, unless the */
    /* mix flag indicates that both user- and auto- tics are OK.  */
    if (!axis_array[axis].ticdef.def.mix)
	axis_array[axis].ticdef.type = TIC_USER;

    /* Walk along list to sorted positional order */
    listhead.next = axis_array[axis].ticdef.def.user;
    listhead.position = -DBL_MAX;
    for (tic = &listhead;
	 tic->next && (position > tic->next->position);
	 tic = tic->next) {
    }

    if ((tic->next == NULL) || (position < tic->next->position)) {
	/* Make a new ticmark */
	newtic = (struct ticmark *) gp_alloc(sizeof(struct ticmark), (char *) NULL);
	newtic->position = position;
	newtic->level = level;
	/* Insert it in the list */
	newtic->next = tic->next;
	tic->next = newtic;
    } else {
	/* The new tic must duplicate position of tic->next */
	if (position != tic->next->position)
	    int_warn(NO_CARET,"add_tic_user: list sort error");
	newtic = tic->next;
	/* Don't over-write a major tic with a minor tic */
	if (newtic->level < level)
	    return;
	if (newtic->label) {
	    free(newtic->label);
	    newtic->label = NULL;
	}
    }

    if (label)
	newtic->label = gp_strdup(label);
    else
	newtic->label = NULL;

    /* Make sure the listhead is kept */
    axis_array[axis].ticdef.def.user = listhead.next;
}

/*
 * EAM Oct 2011
 * These routines used to be macros PARSE_RANGE, PARSE_NAMED_RANGE
 * Since they take only normal parameters, it saves code/space to make
 * them subroutines instead.
 */

/* get optional [min:max] */
void
parse_range(AXIS_INDEX axis)
{
    if (equals(c_token, "[")) {
	c_token++;
	axis_array[axis].autoscale =
	    load_range(axis, &axis_array[axis].min, &axis_array[axis].max,
		       axis_array[axis].autoscale);
	if (!equals(c_token, "]"))
	    int_error(c_token, "']' expected");
	c_token++;
    }
}

/* like parse_range, but for named ranges as in 'plot [phi=3.5:7] sin(phi)' */
int
parse_named_range(AXIS_INDEX axis, int dummy)
{
    int dummy_token = dummy;

    if (equals(c_token, "[")) {
	c_token++;
	if (isletter(c_token)) {
	    if (equals(c_token + 1, "=")) {
		dummy_token = c_token;
		c_token += 2;
	    }
		/* oops; probably an expression with a variable: act
		 * as if no variable name had been seen, by
		 * fallthrough */
	}
	axis_array[axis].autoscale = load_range(axis, &axis_array[axis].min,
				      &axis_array[axis].max,
				      axis_array[axis].autoscale);
	if (!equals(c_token, "]"))
	    int_error(c_token, "']' expected");
	c_token++;
    }				/* first '[' */

    return dummy_token;
}

/* Called if an in-line range is encountered while inside a zoom command */
void
parse_skip_range()
{
    while (!equals(c_token++,"]"))
	if (END_OF_COMMAND)
	    break;
    return;
}

