
/*
 * bltGrAxis.c --
 *
 *	This module implements coordinate axes for the BLT graph widget.
 *
 * Copyright 1993-1998 Lucent Technologies, Inc.
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose and without fee is hereby
 * granted, provided that the above copyright notice appear in all
 * copies and that both that the copyright notice and warranty
 * disclaimer appear in supporting documentation, and that the names
 * of Lucent Technologies any of their entities not be used in
 * advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 *
 * Lucent Technologies disclaims all warranties with regard to this
 * software, including all implied warranties of merchantability and
 * fitness.  In no event shall Lucent Technologies be liable for any
 * special, indirect or consequential damages or any damages
 * whatsoever resulting from loss of use, data or profits, whether in
 * an action of contract, negligence or other tortuous action, arising
 * out of or in connection with the use or performance of this
 * software.
 */

#include "bltGraph.h"
#include "bltGrElem.h"
#include <X11/Xutil.h>
#include <X11/Xatom.h>

#define HIDE_ALL		-1

#define DEF_NUM_TICKS		4	/* Each minor tick is 20% */
#define STATIC_TICK_SPACE	10

#define TICK_LABEL_SIZE		200
#define MAXTICKS		10001

#define CLAMP(val,low,high)	\
	(((val) < (low)) ? (low) : ((val) > (high)) ? (high) : (val))

/*
 * Round x in terms of units
 */
#define UROUND(x,u)		(Round((x)/(u))*(u))
#define UCEIL(x,u)		(ceil((x)/(u))*(u))
#define UFLOOR(x,u)		(floor((x)/(u))*(u))

#define LENGTH_MAJOR_TICK 	0.030	/* Length of a major tick */
#define LENGTH_MINOR_TICK 	0.015	/* Length of a minor (sub)tick */
#define LENGTH_LABEL_TICK 	0.040	/* Distance from graph to start of the
					 * label */
#define NUMDIGITS		15	/* Specifies the number of
					 * digits of accuracy used when
					 * outputting axis tick labels. */
#define AVG_TICK_NUM_CHARS	16	/* Assumed average tick label size */

#define TICK_RANGE_TIGHT	0
#define TICK_RANGE_LOOSE	1
#define TICK_RANGE_ALWAYS_LOOSE	2

#define AXIS_TITLE_PAD		2	/* Padding for axis title. */
#define AXIS_LINE_PAD		1	/* Padding for axis line. */

#define HORIZMARGIN(m)	(!((m)->site & 0x1))	/* Even sites are horizontal */
/* Map graph coordinates to normalized coordinates [0..1] */
#define NORMALIZE(A,x) 	(((x) - (A)->tickRange.min) / (A)->tickRange.range)

typedef enum AxisComponents {
    MAJOR_TICK, MINOR_TICK, TICK_LABEL, AXIS_LINE
} AxisComponent;


typedef struct {
    int axisLine;		/* Length of the axis.  */
    int majorTick;		/* Length of a major tick (in pixels). */
    int minorTick;		/* Length of a minor tick (in pixels). */
    int tickLabel;		/* Distance from axis to tick label.  */
} AxisInfo;

static int StringToBounds _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));
static char *BoundsToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset, 
	Tcl_FreeProc **freeProcPtr));

static int StringToTicks _ANSI_ARGS_((ClientData clientData, 
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec, 
	int offset));
static char *TicksToString _ANSI_ARGS_((ClientData clientData, Tk_Window tkwin,
	char *widgRec, int offset, Tcl_FreeProc **freeProcPtr));

static int StringToAxis _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp,
	Tk_Window tkwin, char *string, char *widgRec, int offset));
static char *AxisToString _ANSI_ARGS_((ClientData clientData, Tk_Window tkwin,
	char *widgRec, int offset, Tcl_FreeProc **freeProcPtr));

static Tk_CustomOption minOption =
{
    StringToBounds, BoundsToString, (ClientData)AXIS_CONFIG_MIN,
};

static Tk_CustomOption maxOption =
{
    StringToBounds, BoundsToString, (ClientData)AXIS_CONFIG_MAX,
};

static Tk_CustomOption majorTicksOption =
{
    StringToTicks, TicksToString, (ClientData)AXIS_CONFIG_MAJOR,
};

static Tk_CustomOption minorTicksOption =
{
    StringToTicks, TicksToString, (ClientData)AXIS_CONFIG_MINOR,
};

Tk_CustomOption bltAxisOption =
{
    StringToAxis, AxisToString, (ClientData)0
};

Tk_CustomOption bltXAxisOption =
{
    StringToAxis, AxisToString, (ClientData)AXIS_TYPE_X
};

Tk_CustomOption bltYAxisOption =
{
    StringToAxis, AxisToString, (ClientData)AXIS_TYPE_Y
};

Tk_CustomOption bltAnyXAxisOption =
{
    StringToAxis, AxisToString, (ClientData)(AXIS_TYPE_X | AXIS_ALLOW_NULL)
};

Tk_CustomOption bltAnyYAxisOption =
{
    StringToAxis, AxisToString, (ClientData)(AXIS_TYPE_Y | AXIS_ALLOW_NULL)
};

static int StringToFormat _ANSI_ARGS_((ClientData clientData, 
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec, 
	int offset));
static char *FormatToString _ANSI_ARGS_((ClientData clientData, Tk_Window tkwin,
	char *widgRec, int offset, Tcl_FreeProc **freeProcPtr));

static Tk_CustomOption formatOption =
{
    StringToFormat, FormatToString, (ClientData)0,
};

static int StringToLoose _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));
static char *LooseToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset, 
	Tcl_FreeProc **freeProcPtr));

static Tk_CustomOption looseOption =
{
    StringToLoose, LooseToString, (ClientData)0,
};

static int StringToHide _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *string, char *widgRec,
	int offset));
static char *HideToString _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset, 
	Tcl_FreeProc **freeProcPtr));

static Tk_CustomOption hideOption =
{
    StringToHide, HideToString, (ClientData)0,
};

extern Tk_CustomOption bltDistanceOption;
extern Tk_CustomOption bltPositiveDistanceOption;
extern Tk_CustomOption bltShadowOption;

/* Axis flags: */

#define DEF_AXIS_ALT_HIDE		"no"
#define DEF_AXIS_COMMAND		(char *)NULL
#define DEF_AXIS_DESCENDING		"no"
#define DEF_AXIS_FG_COLOR		RGB_BLACK
#define DEF_AXIS_FG_MONO		RGB_BLACK
#define DEF_AXIS_HIDE			"no"
#define DEF_AXIS_HIDE_ALT		"no"
#define DEF_AXIS_HIDE_STD		"yes"
#define DEF_AXIS_JUSTIFY		"center"
#define DEF_AXIS_LIMITS_FORMAT	        (char *)NULL
#define DEF_AXIS_LINE_WIDTH		"1"
#define DEF_AXIS_LOGSCALE		"no"
#define DEF_AXIS_LOOSE			"no"
#define DEF_AXIS_MAJOR_TICKS		(char *)NULL
#define DEF_AXIS_MAX			(char *)NULL
#define DEF_AXIS_MIN			(char *)NULL
#define DEF_AXIS_RANGE			"0.0"
#define DEF_AXIS_ROTATE			"0.0"
#define DEF_AXIS_SCROLL_INCREMENT 	"10"
#define DEF_AXIS_SHADOW_COLOR		(char *)NULL
#define DEF_AXIS_SHADOW_MONO		(char *)NULL
#define DEF_AXIS_SHIFTBY		"0.0"
#define DEF_AXIS_SHOWTICKS		"yes"
#define DEF_AXIS_STEPSIZE		"0.0"
#define DEF_AXIS_SUBDIVISIONS		"2"
#define DEF_AXIS_TICKS			"0"
#ifdef WIN32
#define DEF_AXIS_TICK_FONT		"{Arial Narrow} 8"
#else
#define DEF_AXIS_TICK_FONT		"*-Helvetica-Medium-R-Normal-*-10-*"
#endif
#define DEF_AXIS_TICK_LENGTH		"8"
#define DEF_AXIS_TICK_VALUES		(char *)NULL
#define DEF_AXIS_TITLE			(char *)NULL
#define DEF_AXIS_TITLE_FG		RGB_BLACK
#define DEF_AXIS_TITLE_FONT		STD_FONT
#define DEF_AXIS_X_STEPSIZE_BARCHART	"1.0"
#define DEF_AXIS_X_SUBDIVISIONS_BARCHART "0"

static Tk_ConfigSpec configSpecs[] =
{
    {TK_CONFIG_DOUBLE, "-autorange", "autoRange", "AutoRange",
	DEF_AXIS_RANGE, Tk_Offset(Axis, autoRange),
        BARCHART | STRIPCHART | TK_CONFIG_DONT_SET_DEFAULT}, 
    {TK_CONFIG_COLOR, "-color", "color", "Color",
	DEF_AXIS_FG_COLOR, Tk_Offset(Axis, tickStyle.color),
	TK_CONFIG_COLOR_ONLY | ALL_GRAPHS},
    {TK_CONFIG_COLOR, "-color", "color", "Color",
	DEF_AXIS_FG_MONO, Tk_Offset(Axis, tickStyle.color),
	TK_CONFIG_MONO_ONLY | ALL_GRAPHS},
    {TK_CONFIG_STRING, "-command", "command", "Command",
	DEF_AXIS_COMMAND, Tk_Offset(Axis, formatCmd),
	TK_CONFIG_NULL_OK | ALL_GRAPHS},
    {TK_CONFIG_BOOLEAN, "-descending", "descending", "Descending",
	DEF_AXIS_DESCENDING, Tk_Offset(Axis, descending),
	ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-hide", "hide", "Hide",
	DEF_AXIS_HIDE, Tk_Offset(Axis, hidden),
	ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT, &hideOption},
    {TK_CONFIG_JUSTIFY, "-justify", "justify", "Justify",
	DEF_AXIS_JUSTIFY, Tk_Offset(Axis, titleStyle.justify),
	ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_COLOR, "-limitscolor", "limitsColor", "Color",
	DEF_AXIS_FG_COLOR, Tk_Offset(Axis, limitsStyle.color),
	TK_CONFIG_COLOR_ONLY | ALL_GRAPHS},
    {TK_CONFIG_COLOR, "-limitscolor", "limitsColor", "Color",
	DEF_AXIS_FG_MONO, Tk_Offset(Axis, limitsStyle.color),
	TK_CONFIG_MONO_ONLY | ALL_GRAPHS},
    {TK_CONFIG_FONT, "-limitsfont", "limitsFont", "Font",
	DEF_AXIS_TICK_FONT, Tk_Offset(Axis, limitsStyle.font), ALL_GRAPHS},
    {TK_CONFIG_CUSTOM, "-limitsformat", "limitsFormat", "LimitsFormat",
	DEF_AXIS_LIMITS_FORMAT, Tk_Offset(Axis, limitsFormats),
	TK_CONFIG_NULL_OK | ALL_GRAPHS, &formatOption},
    {TK_CONFIG_CUSTOM, "-limitsshadow", "limitsShadow", "Shadow",
	DEF_AXIS_SHADOW_COLOR, Tk_Offset(Axis, limitsStyle.shadow),
	TK_CONFIG_COLOR_ONLY | ALL_GRAPHS, &bltShadowOption},
    {TK_CONFIG_CUSTOM, "-limitsshadow", "limitsShadow", "Shadow",
	DEF_AXIS_SHADOW_MONO, Tk_Offset(Axis, limitsStyle.shadow),
	TK_CONFIG_MONO_ONLY | ALL_GRAPHS, &bltShadowOption},
    {TK_CONFIG_CUSTOM, "-linewidth", "lineWidth", "LineWidth",
	DEF_AXIS_LINE_WIDTH, Tk_Offset(Axis, lineWidth),
	ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_BOOLEAN, "-logscale", "logScale", "LogScale",
	DEF_AXIS_LOGSCALE, Tk_Offset(Axis, logScale),
	ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-loose", "loose", "Loose",
	DEF_AXIS_LOOSE, 0, ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT,
	&looseOption},
    {TK_CONFIG_CUSTOM, "-majorticks", "majorTicks", "MajorTicks",
	DEF_AXIS_MAJOR_TICKS, Tk_Offset(Axis, t1Ptr),
	TK_CONFIG_NULL_OK | ALL_GRAPHS, &majorTicksOption},
    {TK_CONFIG_CUSTOM, "-max", "max", "Max",
	DEF_AXIS_MIN, 0, TK_CONFIG_NULL_OK | ALL_GRAPHS, &maxOption},
    {TK_CONFIG_CUSTOM, "-min", "min", "Min",
	DEF_AXIS_MAX, 0, TK_CONFIG_NULL_OK | ALL_GRAPHS, &minOption},
    {TK_CONFIG_CUSTOM, "-minorticks", "minorTicks", "MinorTicks",
	DEF_AXIS_TICK_VALUES, Tk_Offset(Axis, t2Ptr),
	TK_CONFIG_NULL_OK | ALL_GRAPHS, &minorTicksOption},
    {TK_CONFIG_DOUBLE, "-rotate", "rotate", "Rotate",
	DEF_AXIS_ROTATE, Tk_Offset(Axis, tickStyle.theta),
	ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_STRING, "-scrollcommand", "scrollCommand", "ScrollCommand",
	(char *)NULL, Tk_Offset(Axis, scrollCmdPrefix),
	ALL_GRAPHS | TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-scrollincrement", "scrollIncrement", "ScrollIncrement",
	DEF_AXIS_SCROLL_INCREMENT, Tk_Offset(Axis, scrollUnits),
	ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT, &bltPositiveDistanceOption},
    {TK_CONFIG_DOUBLE, "-shiftby", "shiftBy", "ShiftBy",
	DEF_AXIS_SHIFTBY, Tk_Offset(Axis, shiftBy),
	BARCHART | STRIPCHART | TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_BOOLEAN, "-showticks", "showTicks", "ShowTicks",
	DEF_AXIS_SHOWTICKS, Tk_Offset(Axis, showTicks),
	ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_DOUBLE, "-stepsize", "stepSize", "StepSize",
	DEF_AXIS_STEPSIZE, Tk_Offset(Axis, reqStep),
	ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_INT, "-subdivisions", "subdivisions", "Subdivisions",
	DEF_AXIS_SUBDIVISIONS, Tk_Offset(Axis, reqNumMinorTicks),
	ALL_GRAPHS},
    {TK_CONFIG_FONT, "-tickfont", "tickFont", "Font",
	DEF_AXIS_TICK_FONT, Tk_Offset(Axis, tickStyle.font), ALL_GRAPHS},
    {TK_CONFIG_PIXELS, "-ticklength", "tickLength", "TickLength",
	DEF_AXIS_TICK_LENGTH, Tk_Offset(Axis, tickLength), ALL_GRAPHS},
    {TK_CONFIG_CUSTOM, "-tickshadow", "tickShadow", "Shadow",
	DEF_AXIS_SHADOW_COLOR, Tk_Offset(Axis, tickStyle.shadow),
	TK_CONFIG_COLOR_ONLY | ALL_GRAPHS, &bltShadowOption},
    {TK_CONFIG_CUSTOM, "-tickshadow", "tickShadow", "Shadow",
	DEF_AXIS_SHADOW_MONO, Tk_Offset(Axis, tickStyle.shadow),
	TK_CONFIG_MONO_ONLY | ALL_GRAPHS, &bltShadowOption},
    {TK_CONFIG_STRING, "-title", "title", "Title",
	DEF_AXIS_TITLE, Tk_Offset(Axis, titleText),
	TK_CONFIG_DONT_SET_DEFAULT | TK_CONFIG_NULL_OK | ALL_GRAPHS},
    {TK_CONFIG_COLOR, "-titlecolor", "titleColor", "Color",
	DEF_AXIS_FG_COLOR, Tk_Offset(Axis, titleStyle.color),
	TK_CONFIG_COLOR_ONLY | ALL_GRAPHS},
    {TK_CONFIG_COLOR, "-titlecolor", "titleColor", "TitleColor",
	DEF_AXIS_FG_MONO, Tk_Offset(Axis, titleStyle.color),
	TK_CONFIG_MONO_ONLY | ALL_GRAPHS},
    {TK_CONFIG_FONT, "-titlefont", "titleFont", "Font",
	DEF_AXIS_TITLE_FONT, Tk_Offset(Axis, titleStyle.font), ALL_GRAPHS},
    {TK_CONFIG_CUSTOM, "-titleshadow", "titleShadow", "Shadow",
	DEF_AXIS_SHADOW_COLOR, Tk_Offset(Axis, titleStyle.shadow),
	TK_CONFIG_COLOR_ONLY | ALL_GRAPHS, &bltShadowOption},
    {TK_CONFIG_CUSTOM, "-titleshadow", "titleShadow", "Shadow",
	DEF_AXIS_SHADOW_MONO, Tk_Offset(Axis, titleStyle.shadow),
	TK_CONFIG_MONO_ONLY | ALL_GRAPHS, &bltShadowOption},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};

/* Forward declarations */
static void DestroyAxis _ANSI_ARGS_((Graph *graphPtr, Axis *axisPtr));
static int GetAxis _ANSI_ARGS_((Graph *graphPtr, char *name, unsigned int flags,
	Axis **axisPtrPtr));
static void FreeAxis _ANSI_ARGS_((Graph *graphPtr, Axis *axisPtr));

INLINE static int
Round(x)
    register double x;
{
    return (int) (x + ((x < 0.0) ? -0.5 : 0.5));
}

INLINE static double
Fabs(x)
    register double x;
{
    return ((x < 0.0) ? -x : x);
}

/*
 * ----------------------------------------------------------------------
 *
 * OutOfRange --
 *
 *	Determines if a value does not lie within a given range.
 *
 *	The value is normalized and compared against the interval
 *	[0..1], where 0.0 is the minimum and 1.0 is the maximum.
 *	DBL_EPSILON is the smallest number that can be represented
 *	on the host machine, such that (1.0 + epsilon) != 1.0.
 *
 *	Please note, *max* can't equal *min*.
 *
 * Results:
 *	Returns whether the value lies outside of the given range.
 *	If value is outside of the interval [min..max], 1 is returned;
 *	0 otherwise.
 *
 * ----------------------------------------------------------------------
 */
INLINE static int
OutOfRange(value, rangePtr)
    register double value;
    Range *rangePtr;
{
    register double x;

    x = (value - rangePtr->min) / (rangePtr->range);
    return (((x - 1.0) > DBL_EPSILON) || (((1.0 - x) - 1.0) > DBL_EPSILON));
}

INLINE static int
AxisIsHoriz(graphPtr, axisPtr)
    Graph *graphPtr;
    Axis *axisPtr;
{
    return ((axisPtr->type == AXIS_TYPE_Y) == graphPtr->inverted);
}


/* ----------------------------------------------------------------------
 * Custom option parse and print procedures
 * ----------------------------------------------------------------------
 */

/*
 *----------------------------------------------------------------------
 *
 * StringToAxis --
 *
 *	Converts the name of an axis to a pointer to its axis structure.
 *
 * Results:
 *	The return value is a standard Tcl result.  The axis flags are
 *	written into the widget record.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToAxis(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;	/* Flag indicating whether to allow the
				 * empty string as a valid axis name.*/
    Tcl_Interp *interp;		/* Interpreter to send results back to. */
    Tk_Window tkwin;		/* Used to look up pointer to graph. */
    char *string;		/* String representing new value. */
    char *widgRec;		/* Pointer to structure record. */
    int offset;			/* Offset of field in structure. */
{
    Axis **axisPtrPtr = (Axis **)(widgRec + offset);
    unsigned int flags = (unsigned int)clientData;
    Graph *graphPtr;

    graphPtr = Blt_GetGraphFromWindowData(tkwin);
    if (*axisPtrPtr != NULL) {
	FreeAxis(graphPtr, *axisPtrPtr);
    }
    if ((flags & AXIS_ALLOW_NULL) && (string[0] == '\0')) {
	*axisPtrPtr = NULL;
    } else if (GetAxis(graphPtr, string, flags, axisPtrPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * AxisToString --
 *
 *	Convert the window coordinates into a string.
 *
 * Results:
 *	The string representing the coordinate position is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
AxisToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Not used. */
    Tk_Window tkwin;		/* Not used. */
    char *widgRec;		/* Pointer to structure record .*/
    int offset;			/* Offset of field in structure. */
    Tcl_FreeProc **freeProcPtr;	/* Not used. */
{
    Axis *axisPtr = *(Axis **)(widgRec + offset);

    if (axisPtr == NULL) {
	return "";
    }
    return axisPtr->name;
}

/*
 *----------------------------------------------------------------------
 *
 * StringToFormat --
 *
 *	Convert the name of virtual axis to an pointer.
 *
 * Results:
 *	The return value is a standard Tcl result.  The axis flags are
 *	written into the widget record.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToFormat(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;	/* Not used. */
    Tcl_Interp *interp;		/* Interpreter to send results back to. */
    Tk_Window tkwin;		/* Used to look up pointer to graph */
    char *string;		/* String representing new value. */
    char *widgRec;		/* Pointer to structure record. */
    int offset;			/* Offset of field in structure. */
{
    Axis *axisPtr = (Axis *)(widgRec);
    char **elemArr;
    int nElem;

    if (axisPtr->limitsFormats != NULL) {
	free((char *)axisPtr->limitsFormats);
    }
    axisPtr->limitsFormats = NULL;
    axisPtr->nFormats = 0;

    if ((string == NULL) || (*string == '\0')) {
	return TCL_OK;
    }
    if (Tcl_SplitList(interp, string, &nElem, &elemArr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (nElem > 2) {
	Tcl_AppendResult(interp, "too many elements in limits format list \"",
	    string, "\"", (char *)NULL);
	free((char *)elemArr);
	return TCL_ERROR;
    }
    axisPtr->limitsFormats = elemArr;
    axisPtr->nFormats = nElem;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * FormatToString --
 *
 *	Convert the window coordinates into a string.
 *
 * Results:
 *	The string representing the coordinate position is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
FormatToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Not used. */
    Tk_Window tkwin;		/* Not used. */
    char *widgRec;		/* Widget record */
    int offset;			/* offset of limits field */
    Tcl_FreeProc **freeProcPtr;	/* Not used. */
{
    Axis *axisPtr = (Axis *)(widgRec);
    
    if (axisPtr->nFormats == 0) {
	return "";
    }
    *freeProcPtr = (Tcl_FreeProc *)free;
    return Tcl_Merge(axisPtr->nFormats, axisPtr->limitsFormats); 
}

/*
 * ----------------------------------------------------------------------
 *
 * StringToBounds --
 *
 *	Convert the string representation of an axis limit into its numeric
 *	form.
 *
 * Results:
 *	The return value is a standard Tcl result.  The symbol type is
 *	written into the widget record.
 *
 * ----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToBounds(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;	/* Either AXIS_CONFIG_MIN or AXIS_CONFIG_MAX.
				 * Indicates which axis limit to set. */
    Tcl_Interp *interp;		/* Interpreter to send results back to */
    Tk_Window tkwin;		/* Not used. */
    char *string;		/* String representing new value. */
    char *widgRec;		/* Pointer to structure record. */
    int offset;			/* Offset of field in structure. */
{
    Axis *axisPtr = (Axis *)(widgRec);
    unsigned int mask = (int)clientData;

    if ((string == NULL) || (*string == '\0')) {
	if (mask == AXIS_CONFIG_MIN) {
	    axisPtr->min = bltPosInfinity;
	} else {
	    axisPtr->max = bltNegInfinity;
	}
	axisPtr->flags &= ~mask;
    } else {
	double value;

	if (Tcl_ExprDouble(interp, string, &value) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (mask == AXIS_CONFIG_MIN) {
	    axisPtr->min = value;
	} else {
	    axisPtr->max = value;
	}
	axisPtr->flags |= mask;
    }
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * BoundsToString --
 *
 *	Convert the floating point axis limits into a string.
 *
 * Results:
 *	The string representation of the limits is returned.
 *
 * ----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
BoundsToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Either LMIN or LMAX */
    Tk_Window tkwin;		/* Not used. */
    char *widgRec;		/* */
    int offset;
    Tcl_FreeProc **freeProcPtr;
{
    Axis *axisPtr = (Axis *)(widgRec);
    unsigned int mask = (int)clientData;
    char *result;

    result = "";
    if (axisPtr->flags & mask) {
	char string[TCL_DOUBLE_SPACE + 1];
	double value;
	Graph *graphPtr;

	graphPtr = Blt_GetGraphFromWindowData(tkwin);
	value = (mask == AXIS_CONFIG_MIN) ? axisPtr->min : axisPtr->max;
	Tcl_PrintDouble(graphPtr->interp, value, string);
	result = strdup(string);
	if (result == NULL) {
	    return "";
	}
	*freeProcPtr = (Tcl_FreeProc *)free;
    }
    return result;
}

/*
 * ----------------------------------------------------------------------
 *
 * StringToTicks --
 *
 *
 * Results:
 *
 * ----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToTicks(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;	/* Not used. */
    Tcl_Interp *interp;		/* Interpreter to send results back to */
    Tk_Window tkwin;		/* Not used. */
    char *string;		/* String representing new value. */
    char *widgRec;		/* Pointer to structure record. */
    int offset;			/* Offset of field in structure. */
{
    unsigned int mask = (unsigned int)clientData;
    Axis *axisPtr = (Axis *)widgRec;
    Ticks **ticksPtrPtr = (Ticks **) (widgRec + offset);
    int nTicks;
    Ticks *ticksPtr;

    nTicks = 0;
    ticksPtr = NULL;
    if ((string != NULL) && (*string != '\0')) {
	int nExprs;
	char **exprArr;

	if (Tcl_SplitList(interp, string, &nExprs, &exprArr) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (nExprs > 0) {
	    register int i;
	    int result = TCL_ERROR;
	    double value;

	    ticksPtr = (Ticks *)
		malloc(sizeof(Ticks) + (nExprs * sizeof(double)));
	    assert(ticksPtr);
	    for (i = 0; i < nExprs; i++) {
		result = Tcl_ExprDouble(interp, exprArr[i], &value);
		if (result != TCL_OK) {
		    break;
		}
		ticksPtr->tickArr[i] = value;
	    }
	    free((char *)exprArr);
	    if (result != TCL_OK) {
		free((char *)ticksPtr);
		return TCL_ERROR;
	    }
	    nTicks = nExprs;
	}
    }
    axisPtr->flags &= ~mask;
    if (ticksPtr != NULL) {
	axisPtr->flags |= mask;
	ticksPtr->nTicks = nTicks;
    }
    if (*ticksPtrPtr != NULL) {
	free((char *)(*ticksPtrPtr));
    }
    *ticksPtrPtr = ticksPtr;
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * TicksToString --
 *
 *	Convert array of tick coordinates to a list.
 *
 * Results:
 *
 * ----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
TicksToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Not used. */
    Tk_Window tkwin;		/* Not used. */
    char *widgRec;		/* */
    int offset;
    Tcl_FreeProc **freeProcPtr;
{
    Ticks *ticksPtr = *(Ticks **) (widgRec + offset);
    char string[TCL_DOUBLE_SPACE + 1];
    register int i;
    char *result;
    Tcl_DString dString;
    Graph *graphPtr;

    if (ticksPtr == NULL) {
	return "";
    }
    Tcl_DStringInit(&dString);
    graphPtr = Blt_GetGraphFromWindowData(tkwin);
    for (i = 0; i < ticksPtr->nTicks; i++) {
	Tcl_PrintDouble(graphPtr->interp, ticksPtr->tickArr[i], string);
	Tcl_DStringAppendElement(&dString, string);
    }
    *freeProcPtr = (Tcl_FreeProc *)free;
    result = strdup(Tcl_DStringValue(&dString));
    Tcl_DStringFree(&dString);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * StringToLoose --
 *
 *	Convert a string to one of three values.
 *		0 - false, no, off
 *		1 - true, yes, on
 *		2 - always
 * Results:
 *	If the string is successfully converted, TCL_OK is returned.
 *	Otherwise, TCL_ERROR is returned and an error message is left in
 *	interpreter's result field.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToLoose(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;	/* Not used. */
    Tcl_Interp *interp;		/* Interpreter to send results back to */
    Tk_Window tkwin;		/* Not used. */
    char *string;		/* String representing new value. */
    char *widgRec;		/* Pointer to structure record. */
    int offset;			/* Offset of field in structure. */
{
    Axis *axisPtr = (Axis *)(widgRec);
    register int i;
    int nElems;
    char **elemArr;
    int values[2];

    if (Tcl_SplitList(interp, string, &nElems, &elemArr) != TCL_OK) {
	return TCL_ERROR;
    }
    if ((nElems < 1) || (nElems > 2)) {
	Tcl_AppendResult(interp, "wrong # elements in loose value \"",
	    string, "\"", (char *)NULL);
	return TCL_ERROR;
    }
    for (i = 0; i < nElems; i++) {
	if ((elemArr[i][0] == 'a') && (strcmp(elemArr[i], "always") == 0)) {
	    values[i] = TICK_RANGE_ALWAYS_LOOSE;
	} else {
	    int bool;

	    if (Tcl_GetBoolean(interp, elemArr[i], &bool) != TCL_OK) {
		free((char *)elemArr);
		return TCL_ERROR;
	    }
	    values[i] = bool;
	}
    }
    axisPtr->looseMin = axisPtr->looseMax = values[0];
    if (nElems > 1) {
	axisPtr->looseMax = values[1];
    }
    free((char *)elemArr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * LooseToString --
 *
 * Results:
 *	The string representation of the auto boolean is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
LooseToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Not used. */
    Tk_Window tkwin;		/* Not used. */
    char *widgRec;		/* Widget record */
    int offset;			/* offset of flags field in record */
    Tcl_FreeProc **freeProcPtr;	/* Memory deallocation scheme to use */
{
    Axis *axisPtr = (Axis *)widgRec;
    Tcl_DString dString;
    char *result;

    Tcl_DStringInit(&dString);
    if (axisPtr->looseMin == TICK_RANGE_TIGHT) {
	Tcl_DStringAppendElement(&dString, "0");
    } else if (axisPtr->looseMin == TICK_RANGE_LOOSE) {
	Tcl_DStringAppendElement(&dString, "1");
    } else if (axisPtr->looseMin == TICK_RANGE_ALWAYS_LOOSE) {
	Tcl_DStringAppendElement(&dString, "always");
    }
    if (axisPtr->looseMin != axisPtr->looseMax) {
	if (axisPtr->looseMax == TICK_RANGE_TIGHT) {
	    Tcl_DStringAppendElement(&dString, "0");
	} else if (axisPtr->looseMax == TICK_RANGE_LOOSE) {
	    Tcl_DStringAppendElement(&dString, "1");
	} else if (axisPtr->looseMax == TICK_RANGE_ALWAYS_LOOSE) {
	    Tcl_DStringAppendElement(&dString, "always");
	}
    }
    result = strdup(Tcl_DStringValue(&dString));
    Tcl_DStringFree(&dString);
    *freeProcPtr = (Tcl_FreeProc *)free;
    return result;
}
/*
 *----------------------------------------------------------------------
 *
 * StringToHide --
 *
 *	Convert a string to one of three values.
 *		0 - false, no, off
 *		1 - true, yes, on
 *		2 - all
 * Results:
 *	If the string is successfully converted, TCL_OK is returned.
 *	Otherwise, TCL_ERROR is returned and an error message is left in
 *	interpreter's result field.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToHide(clientData, interp, tkwin, string, widgRec, offset)
    ClientData clientData;	/* Not used. */
    Tcl_Interp *interp;		/* Interpreter to send results back to */
    Tk_Window tkwin;		/* Not used. */
    char *string;		/* String representation of new value. */
    char *widgRec;		/* Widget record */
    int offset;			/* Offset in the widget structure. */
{
    int *hidePtr = (int *)(widgRec + offset);

    if ((string[0] == 'a') && (strcmp(string, "all") == 0)) {
	*hidePtr = HIDE_ALL;
    } else {
	int bool;

	if (Tcl_GetBoolean(interp, string, &bool) != TCL_OK) {
	    return TCL_ERROR;
	}
	*hidePtr = bool;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * HideToString --
 *
 * Results:
 *	The string representation of the button boolean is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
HideToString(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Not used. */
    Tk_Window tkwin;		/* Not used. */
    char *widgRec;		/* Widget record */
    int offset;			/* offset of flags field in record */
    Tcl_FreeProc **freeProcPtr;	/* Memory deallocation scheme to use */
{
    int hide = *(int *)(widgRec + offset);

    switch (hide) {
    case FALSE:
	return "0";
    case TRUE:
	return "1";
    case HIDE_ALL:
	return "all";
    default:
	return "unknown hide value";
    }
}


/*
 * ----------------------------------------------------------------------
 *
 * MakeLabel --
 *
 *	Converts a floating point tick value to a string to be used as its
 *	label.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Returns a new label in the string character buffer.  The formatted
 *	tick label will be displayed on the graph.
 *
 * ---------------------------------------------------------------------- 
 */
static void
MakeLabel(graphPtr, axisPtr, value, string)
    Graph *graphPtr;
    Axis *axisPtr;		/* Axis structure */
    double value;		/* Value to be convert to a decimal string */
    char string[];		/* String (length is TICK_LABEL_SIZE+1)
				 * containing the formatted label */
{

    /* Generate a default tick label based upon the tick value.  */
    if (axisPtr->logScale) {
	sprintf(string, "1E%d", ROUND(value));
    } else {
	sprintf(string, "%.*g", NUMDIGITS, value);
    }

    if (axisPtr->formatCmd != NULL) {
	Tcl_Interp *interp = graphPtr->interp;
	Tk_Window tkwin = graphPtr->tkwin;

	/*
	 * A Tcl proc was designated to format tick labels. Append the path
	 * name of the widget and the default tick label as arguments when
	 * invoking it. Copy and save the new label from interp->result.
	 */
	Tcl_ResetResult(interp);
	if (Tcl_VarEval(interp, axisPtr->formatCmd, " ", Tk_PathName(tkwin),
		" ", string, (char *)NULL) != TCL_OK) {
	    Tcl_BackgroundError(interp);
	    return;		/* Error in format proc */
	}
	/* 
	 * The proc could return a string of any length, so arbitrarily 
	 * limit it to what will fit in the return string. 
	 */
	strncpy(string, Tcl_GetStringResult(interp), TICK_LABEL_SIZE);
	string[TICK_LABEL_SIZE] = '\0';

	Tcl_ResetResult(interp); /* Clear the interpreter's result. */
    }
}

/*
 * ----------------------------------------------------------------------
 *
 * InvHMap --
 *
 *	Maps the given screen coordinate back to a graph coordinate.
 *	Called by the graph locater routine.
 *
 * Results:
 *	Returns the graph coordinate value at the given window
 *	y-coordinate.
 *
 * ----------------------------------------------------------------------
 */
static double
InvHMap(graphPtr, axisPtr, x)
    Graph *graphPtr;
    Axis *axisPtr;
    double x;
{
    double norm, value;

    norm = (double)(x - graphPtr->hOffset) / (double)(graphPtr->hRange);
    if (axisPtr->descending) {
	norm = 1.0 - norm;
    }
    value = (norm * axisPtr->tickRange.range) + axisPtr->tickRange.min;
    if (axisPtr->logScale) {
	value = EXP10(value);
    }
    return value;
}

/*
 * ----------------------------------------------------------------------
 *
 * InvVMap --
 *
 *	Maps the given window y-coordinate back to a graph coordinate
 *	value. Called by the graph locater routine.
 *
 * Results:
 *	Returns the graph coordinate value at the given window
 *	y-coordinate.
 *
 * ----------------------------------------------------------------------
 */
static double
InvVMap(graphPtr, axisPtr, y)
    Graph *graphPtr;
    Axis *axisPtr;
    double y;
{
    double norm, value;

    norm = (double)(y - graphPtr->vOffset) / (double)graphPtr->vRange;
    if (axisPtr->descending) {
	norm = 1.0 - norm;
    }
    /* Note: This assumes that the tick range is as big or bigger than the
     * the range of data points. */
    value = ((1.0 - norm) * axisPtr->tickRange.range) + axisPtr->tickRange.min;
    if (axisPtr->logScale) {
	value = EXP10(value);
    }
    return value;
}

/*
 * ----------------------------------------------------------------------
 *
 * HMap --
 *
 *	Map the given graph coordinate value to its axis, returning a window
 *	position.
 *
 * Results:
 *	Returns a double precision number representing the window coordinate
 *	position on the given axis.
 *
 * ----------------------------------------------------------------------
 */
static double
HMap(graphPtr, axisPtr, x)
    Graph *graphPtr;
    Axis *axisPtr;
    double x;
{
    register double norm;

    if ((axisPtr->logScale) && (x != 0.0)) {
	x = log10(Fabs(x));
    }
    norm = NORMALIZE(axisPtr, x);
    if (axisPtr->descending) {
	norm = 1.0 - norm;
    }
    return ((norm * (graphPtr->hRange)) + graphPtr->hOffset);
}

/*
 * ----------------------------------------------------------------------
 *
 * VMap --
 *
 *	Map the given graph coordinate value to its axis, returning a window
 *	position.
 *
 * Results:
 *	Returns a double precision number representing the window coordinate
 *	position on the given axis.
 *
 * ----------------------------------------------------------------------
 */
static double
VMap(graphPtr, axisPtr, y)
    Graph *graphPtr;
    Axis *axisPtr;
    double y;
{
    register double norm;

    if ((axisPtr->logScale) && (y != 0.0)) {
	y = log10(Fabs(y));
    }
    norm = NORMALIZE(axisPtr, y);
    if (axisPtr->descending) {
	norm = 1.0 - norm;
    }
    return (((1.0 - norm) * (graphPtr->vRange)) + graphPtr->vOffset);
}

/*
 * ----------------------------------------------------------------------
 *
 * Blt_Map2D --
 *
 *	Maps the given graph x,y coordinate values to a window position.
 *
 * Results:
 *	Returns a XPoint structure containing the window coordinates of
 *	the given graph x,y coordinate.
 *
 * ----------------------------------------------------------------------
 */

Point2D
Blt_Map2D(graphPtr, x, y, axesPtr)
    Graph *graphPtr;
    double x, y;		/* Graph x and y coordinates */
    Axis2D *axesPtr;		/* Specifies which axes to use */
{
    Point2D point;

    if (graphPtr->inverted) {
	point.x = HMap(graphPtr, axesPtr->y, y);
	point.y = VMap(graphPtr, axesPtr->x, x);
    } else {
	point.x = HMap(graphPtr, axesPtr->x, x);
	point.y = VMap(graphPtr, axesPtr->y, y);
    }
    return point;
}

/*
 * ----------------------------------------------------------------------
 *
 * Blt_InvMap2D --
 *
 *	Maps the given window x,y coordinates to graph values.
 *
 * Results:
 *	Returns a structure containing the graph coordinates of
 *	the given window x,y coordinate.
 *
 * ----------------------------------------------------------------------
 */
Point2D
Blt_InvMap2D(graphPtr, x, y, axesPtr)
    Graph *graphPtr;
    double x, y;		/* Window x and y coordinates */
    Axis2D *axesPtr;		/* Specifies which axes to use */
{
    Point2D point;

    if (graphPtr->inverted) {
	point.x = InvVMap(graphPtr, axesPtr->x, y);
	point.y = InvHMap(graphPtr, axesPtr->y, x);
    } else {
	point.x = InvHMap(graphPtr, axesPtr->x, x);
	point.y = InvVMap(graphPtr, axesPtr->y, y);
    }
    return point;
}


static void
GetDataLimits(axisPtr, min, max)
    Axis *axisPtr;
    double min, max;
{
    if (axisPtr->dataRange.min > min) {
	axisPtr->dataRange.min = min;
    }
    if (axisPtr->dataRange.max < max) {
	axisPtr->dataRange.max = max;
    }
    axisPtr->flags |= AXIS_MAPS_ELEM;	/* Mark axis in use */
}

static void
FixAxisRange(axisPtr)
    Axis *axisPtr;
{
    /*
     * When auto-scaling, the axis limits are the bounds of the element
     * data.  If no data exists, set arbitrary limits (wrt to log/linear
     * scale).
     */
    if (axisPtr->dataRange.min == bltPosInfinity) {
	axisPtr->dataRange.min = (axisPtr->logScale) ? 0.001 : 0.0;
    }
    if (axisPtr->dataRange.max == bltNegInfinity) {
	axisPtr->dataRange.max = 1.0;
    }
    if (axisPtr->dataRange.min >= axisPtr->dataRange.max) {
	double value;

	/*
	 * There is no range of data (i.e. min is not less than max), 
	 * so manufacture one.
	 */
	value = axisPtr->dataRange.min;
	if (value == 0.0) {
	    axisPtr->dataRange.min = -0.1, axisPtr->dataRange.max = 0.1;
	} else {
	    double x;

	    x = Fabs(value * 0.1);
	    axisPtr->dataRange.min = value - x;
	    axisPtr->dataRange.max = value + x;
	}
    }
    SetRange(axisPtr->dataRange);
    /*   
     * If the user hasn't already specified axis limits with the
     * -min or -max options, set the default axis limits to the be
     * current extents of the data.  
     */
    if (!(axisPtr->flags & AXIS_CONFIG_MIN)) {
	axisPtr->min = axisPtr->dataRange.min;
    }
    if (!(axisPtr->flags & AXIS_CONFIG_MAX)) {
	axisPtr->max = axisPtr->dataRange.max;
    }
    if (axisPtr->max < axisPtr->min) {
	/*   
	 * If the limits still don't make sense, it's because only
	 * one limit configuration option (-min or -max) was set and
	 * the other default (based upon the data) is too small/large.  
	 * Make up a new limit from the one that was set.  
	 */
	if (!(axisPtr->flags & AXIS_CONFIG_MIN)) {
	    axisPtr->min = axisPtr->max - (Fabs(axisPtr->max) * 0.1);
	}
	if (!(axisPtr->flags & AXIS_CONFIG_MAX)) {
	    axisPtr->max = axisPtr->min + (Fabs(axisPtr->max) * 0.1);
	}
    }
    /* Auto range */
    if ((axisPtr->autoRange > 0.0) && (!(axisPtr->flags & AXIS_CONFIG_MAX))
	&& (!(axisPtr->flags & AXIS_CONFIG_MIN))) {
	double max;

#ifdef notdef
	fprintf(stderr, "before min=%g, max=%g\n", axisPtr->min, axisPtr->max);
#endif
	if (axisPtr->shiftBy < 0.0) {
	    axisPtr->shiftBy = 0.0;
	}
	max = axisPtr->min + axisPtr->autoRange;
	if (axisPtr->max >= max) {
	    if (axisPtr->shiftBy > 0.0) {
		max = UCEIL(axisPtr->max, axisPtr->shiftBy);
	    }
	    axisPtr->min = max - axisPtr->autoRange;
	}
	axisPtr->max = max;
#ifdef notdef
	fprintf(stderr, "after min=%g, max=%g\n", axisPtr->min, axisPtr->max);
#endif
    }
    if ((axisPtr->max != axisPtr->prevMax) ||
	(axisPtr->min != axisPtr->prevMin)) {
	/* Indicate if the axis limits have changed */
	axisPtr->flags |= AXIS_CONFIG_DIRTY;
	/* and save the previous minimum and maximum values */
	axisPtr->prevMin = axisPtr->min;
	axisPtr->prevMax = axisPtr->max;
    }
}

/*
 * ----------------------------------------------------------------------
 *
 * NiceNum --
 *
 *	Reference: Paul Heckbert, "Nice Numbers for Graph Labels",
 *		   Graphics Gems, pp 61-63.  
 *
 *	Finds a "nice" number approximately equal to x.
 *
 * ----------------------------------------------------------------------
 */
static double
NiceNum(x, round)
    double x;
    int round;			/* If non-zero, round. Otherwise take ceiling
				 * of value. */
{
    double exponX;		/* exponent of x */
    double fractX;		/* fractional part of x */
    double nf;			/* nice, rounded fraction */

    exponX = floor(log10(x));
    fractX = x / EXP10(exponX);	/* between 1 and 10 */
    if (round) {
	if (fractX < 1.5) {
	    nf = 1.0;
	} else if (fractX < 3.0) {
	    nf = 2.0;
	} else if (fractX < 7.0) {
	    nf = 5.0;
	} else {
	    nf = 10.0;
	}
    } else if (fractX <= 1.0) {
	nf = 1.0;
    } else if (fractX <= 2.0) {
	nf = 2.0;
    } else if (fractX <= 5.0) {
	nf = 5.0;
    } else {
	nf = 10.0;
    }
    return nf * EXP10(exponX);
}

static Ticks *
GenerateTicks(sweepPtr)
    TickSweep *sweepPtr;
{
    Ticks *ticksPtr;
    register int i;
    double value;

    static float logTable[] =	/* Precomputed log10 values [1..10] */
    {
	(float)0.0, (float)0.301, (float)0.477, (float)0.602, (float)0.699,
	(float)0.778, (float)0.845, (float)0.903, (float)0.954, (float)1.0
    };
    ticksPtr = (Ticks *) malloc(sizeof(Ticks) + 
		(sweepPtr->nSteps * sizeof(double)));
    assert(ticksPtr);
    value = sweepPtr->initial;	/* Start from smallest axis tick */

    if (sweepPtr->step == 0.0) { /* Hack: Zero step indicates to use
				  * log values */
	for (i = 0; i < sweepPtr->nSteps; i++) {
	    ticksPtr->tickArr[i] = (double)logTable[i];
	}
    } else {
	for (i = 0; i < sweepPtr->nSteps; i++) {
	    value = UROUND(value, sweepPtr->step);
	    ticksPtr->tickArr[i] = value;
	    value += sweepPtr->step;
	}
    }
    ticksPtr->nTicks = sweepPtr->nSteps;
    return ticksPtr;
}

/*
 * ----------------------------------------------------------------------
 *
 * LogScaleAxis --
 *
 * 	Determine the range and units of a log scaled axis.
 *
 * 	Unless the axis limits are specified, the axis is scaled
 * 	automatically, where the smallest and largest major ticks encompass
 * 	the range of actual data values.  When an axis limit is specified,
 * 	that value represents the smallest(min)/largest(max) value in the
 * 	displayed range of values.
 *
 * 	Both manual and automatic scaling are affected by the step used.  By
 * 	default, the step is the largest power of ten to divide the range in
 * 	more than one piece.
 *
 *	Automatic scaling:
 *	Find the smallest number of units which contain the range of values.
 *	The minimum and maximum major tick values will be represent the
 *	range of values for the axis. This greatest number of major ticks
 *	possible is 10.
 *
 * 	Manual scaling:
 *   	Make the minimum and maximum data values the represent the range of
 *   	the values for the axis.  The minimum and maximum major ticks will be
 *   	inclusive of this range.  This provides the largest area for plotting
 *   	and the expected results when the axis min and max values have be set
 *   	by the user (.e.g zooming).  The maximum number of major ticks is 20.
 *
 *   	For log scale, there's the possibility that the minimum and
 *   	maximum data values are the same magnitude.  To represent the
 *   	points properly, at least one full decade should be shown.
 *   	However, if you zoom a log scale plot, the results should be
 *   	predictable. Therefore, in that case, show only minor ticks.
 *   	Lastly, there should be an appropriate way to handle numbers
 *   	<=0.
 *
 *          maxY
 *            |    units = magnitude (of least significant digit)
 *            |    high  = largest unit tick < max axis value
 *      high _|    low   = smallest unit tick > min axis value
 *            |
 *            |    range = high - low
 *            |    # ticks = greatest factor of range/units
 *           _|
 *        U   |
 *        n   |
 *        i   |
 *        t  _|
 *            |
 *            |
 *            |
 *       low _|
 *            |
 *            |_minX________________maxX__
 *            |   |       |      |       |
 *     minY  low                        high
 *           minY
 *
 *
 * 	numTicks = Number of ticks
 * 	min = Minimum value of axis
 * 	max = Maximum value of axis
 * 	range    = Range of values (max - min)
 *
 * 	If the number of decades is greater than ten, it is assumed
 *	that the full set of log-style ticks can't be drawn properly.
 *
 * Results:
 *	None
 *
 * ---------------------------------------------------------------------- */
static void
LogScaleAxis(axisPtr)
    Axis *axisPtr;
{
    double range;
    double min, max;
    double tickMin, tickMax;
    double stepMajor, stepMinor;
    int nMajor, nMinor;

    min = axisPtr->min;
    min = (min > 0.0) ? log10(Fabs(min)) : 0.0;
    max = axisPtr->max;
    max = (max > 0.0) ? log10(Fabs(max)) : 1.0;
    tickMin = floor(min);
    tickMax = ceil(max);
    range = tickMax - tickMin;

    if (range > 10) {
	/* There are too many decades to display a major tick at every
	 * decade.  Instead, treat the axis as a linear scale.  */
	range = NiceNum(range, 0);
	stepMajor = NiceNum(range / DEF_NUM_TICKS, 1);
	tickMin = UFLOOR(tickMin, stepMajor);
	tickMax = UCEIL(tickMax, stepMajor);
	nMajor = (int)((tickMax - tickMin) / stepMajor) + 1;
	stepMinor = EXP10(floor(log10(stepMajor)));
	if (stepMinor == stepMajor) {
	    nMinor = 4, stepMinor = 0.2;
	} else {
	    nMinor = Round(stepMajor / stepMinor) - 1;
	}
    } else {
	if (tickMin == tickMax) {
	    tickMax++;
	}
	stepMajor = 1.0;
	nMajor = (int)((tickMax - tickMin) + 1); /* FIXME: Check this. */

	stepMinor = 0.0;	/* This is a special hack to pass
				 * information to the GenerateTicks
				 * routine. An interval of 0.0 tells
				 *	1) this is a minor sweep and 
				 *	2) the axis is log scale.  
				 */
	nMinor = 10;
    }
    if ((axisPtr->looseMin == TICK_RANGE_TIGHT) ||
	((axisPtr->looseMin == TICK_RANGE_LOOSE) &&
	 (axisPtr->flags & AXIS_CONFIG_MIN))) {
	tickMin = min;
    }
    if ((axisPtr->looseMax == TICK_RANGE_TIGHT) ||
	((axisPtr->looseMax == TICK_RANGE_LOOSE) &&
	 (axisPtr->flags & AXIS_CONFIG_MAX))) {
	tickMax = max;
    }
    SetLimits(axisPtr->tickRange, tickMin, tickMax);

    axisPtr->majorSweep.step = stepMajor;
    axisPtr->majorSweep.initial = tickMin;
    axisPtr->majorSweep.nSteps = nMajor;
    axisPtr->minorSweep.initial = axisPtr->minorSweep.step = stepMinor;
    axisPtr->minorSweep.nSteps = nMinor;
    
}

/*
 * ----------------------------------------------------------------------
 *
 * LinearScaleAxis --
 *
 * 	Determine the units of a linear scaled axis.
 *
 *	The axis limits are either the range of the data values mapped
 *	to the axis (autoscaled), or the values specified by the -min
 *	and -max options (manual).
 *
 *	If autoscaled, the smallest and largest major ticks will
 *	encompass the range of data values.  If the -loose option is
 *	selected, the next outer ticks are choosen.  If tight, the
 *	ticks are at or inside of the data limits are used.
 *
 * 	If manually set, the ticks are at or inside the data limits
 * 	are used.  This makes sense for zooming.  You want the
 * 	selected range to represent the next limit, not something a
 * 	bit bigger.
 *
 *	Note: I added an "always" value to the -loose option to force
 *	      the manually selected axes to be loose. It's probably
 *	      not a good idea.
 *
 *          maxY
 *            |    units = magnitude (of least significant digit)
 *            |    high  = largest unit tick < max axis value
 *      high _|    low   = smallest unit tick > min axis value
 *            |
 *            |    range = high - low
 *            |    # ticks = greatest factor of range/units
 *           _|
 *        U   |
 *        n   |
 *        i   |
 *        t  _|
 *            |
 *            |
 *            |
 *       low _|
 *            |
 *            |_minX________________maxX__
 *            |   |       |      |       |
 *     minY  low                        high
 *           minY
 *
 *
 * 	numTicks = Number of ticks
 * 	min = Minimum value of axis
 * 	max = Maximum value of axis
 * 	range    = Range of values (max - min)
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The axis tick information is set.  The actual tick values will
 *	be generated later.
 *
 * ----------------------------------------------------------------------
 */
static void
LinearScaleAxis(axisPtr)
    Axis *axisPtr;
{
    double range;
    double min, max;
    double tickMin, tickMax;
    double stepMajor, stepMinor;
    int nMajor, nMinor;

    min = axisPtr->min;
    max = axisPtr->max;
    range = max - min;

    /* Calculate the major step.  If a step interval was designated,
     * use it only if it fits within the current range of the axis.  */
    if ((axisPtr->reqStep > 0.0) && (axisPtr->reqStep <= range) &&
	((int)(range / axisPtr->reqStep) <= MAXTICKS)) {
	stepMajor = axisPtr->reqStep;
    } else {
	range = NiceNum(range, 0);
	stepMajor = NiceNum(range / DEF_NUM_TICKS, 1);
    }

    /* Get the outer tick values. Add 0.0 to prevent getting an IEEE -0.0. */

    tickMin = UFLOOR(min, stepMajor) + 0.0;
    tickMax = UCEIL(max, stepMajor) + 0.0;
    nMajor = Round((tickMax - tickMin) / stepMajor) + 1;

    axisPtr->majorSweep.step = stepMajor;
    axisPtr->majorSweep.initial = tickMin;
    axisPtr->majorSweep.nSteps = nMajor;

    /*
     * The limits of the axis are either the range of the data
     * ("tight"), or at the next outer tick interval ("loose").  The
     * looseness or tightness has to do with how the axis fits the
     * range of data values.  This option is usually overridden when
     * the user sets an axis limit (by either -min or -max option).
     * The axis limit is always at the selected limit (otherwise the
     * user would have picked a different number).
     */
    if ((axisPtr->looseMin == TICK_RANGE_TIGHT) ||
	((axisPtr->looseMin == TICK_RANGE_LOOSE) && 
	 (axisPtr->flags & AXIS_CONFIG_MIN))) {
	tickMin = min;
    }
    if ((axisPtr->looseMax == TICK_RANGE_TIGHT) ||
	((axisPtr->looseMax == TICK_RANGE_LOOSE) &&
	 (axisPtr->flags & AXIS_CONFIG_MAX))) {
	tickMax = max;
    }
    SetLimits(axisPtr->tickRange, tickMin, tickMax);

    /* Now calculate the minor tick step and number. */

    if ((axisPtr->reqNumMinorTicks > 0) && 
	((axisPtr->flags & AXIS_CONFIG_MAJOR) == 0)) {
	nMinor = axisPtr->reqNumMinorTicks - 1;
	stepMinor = 1.0 / (nMinor + 1);
    } else {
	nMinor = 0;		/* No minor ticks. */
	stepMinor = 0.5;	/* Don't set the minor tick interval
				 * to 0.0. It makes the GenerateTicks
				 * routine create minor log-scale tick
				 * marks.  */
    }
    axisPtr->minorSweep.initial = axisPtr->minorSweep.step = stepMinor;
    axisPtr->minorSweep.nSteps = nMinor;
}


static void
SweepTicks(axisPtr)
    Axis *axisPtr;
{
    if ((axisPtr->flags & AXIS_CONFIG_MAJOR) == 0) {
	if (axisPtr->t1Ptr != NULL) {
	    free((char *)axisPtr->t1Ptr);
	}
	axisPtr->t1Ptr = GenerateTicks(&(axisPtr->majorSweep));
    }
    if ((axisPtr->flags & AXIS_CONFIG_MINOR) == 0) {
	if (axisPtr->t2Ptr != NULL) {
	    free((char *)axisPtr->t2Ptr);
	}
	axisPtr->t2Ptr = GenerateTicks(&(axisPtr->minorSweep));
    }
}

/*
 * ----------------------------------------------------------------------
 *
 * Blt_ResetAxes --
 *
 * Results:
 *	None.
 *
 * ----------------------------------------------------------------------
 */
void
Blt_ResetAxes(graphPtr)
    Graph *graphPtr;
{
    Blt_ChainLink *linkPtr;
    Element *elemPtr;
    Axis *axisPtr;
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch cursor;
    Extents2D extents;

    /* FIXME: This should be called whenever the display list of
     *	      elements change. Maybe yet another flag INIT_STACKS to
     *	      indicate that the element display list has changed.
     *	      Needs to be done before the axis limits are set.
     */
    Blt_InitFreqTable(graphPtr);
    if ((graphPtr->mode == MODE_STACKED) && (graphPtr->nStacks > 0)) {
	Blt_ComputeStacks(graphPtr);
    }
    /*
     * Step 1:  Reset all axes. Initialize the data limits of the axis to
     *		impossible values.
     */
    for (hPtr = Tcl_FirstHashEntry(&(graphPtr->axisTable), &cursor);
	hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	axisPtr = (Axis *)Tcl_GetHashValue(hPtr);
	axisPtr->flags &= ~AXIS_MAPS_ELEM;
	axisPtr->dataRange.min = bltPosInfinity;
	axisPtr->dataRange.max = bltNegInfinity;
    }

    /*
     * Step 2:  For each element that's to be displayed, get the smallest
     *		and largest data values mapped to each X and Y-axis.  This
     *		will be the axis limits if the user doesn't override them 
     *		with -min and -max options.
     */
    for (linkPtr = Blt_ChainFirstLink(graphPtr->elements.chainPtr);
	linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	elemPtr = (Element *)Blt_ChainGetValue(linkPtr);
	if (!elemPtr->hidden) {
	    (*elemPtr->classPtr->extentsProc) (elemPtr, &extents);
	    GetDataLimits(elemPtr->axes.x, extents.left, extents.right);
	    GetDataLimits(elemPtr->axes.y, extents.top, extents.bottom);
	}
    }

    /*
     * Step 3:  Now that we know the range of data values for each axis,
     *		set axis limits and compute a sweep to generate tick values.
     */
    for (hPtr = Tcl_FirstHashEntry(&(graphPtr->axisTable), &cursor);
	hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	axisPtr = (Axis *)Tcl_GetHashValue(hPtr);
	if (axisPtr->hidden == HIDE_ALL) {
	    continue;
	}
	FixAxisRange(axisPtr);
	/* Calculate min/max tick (major/minor) layouts */
	if (axisPtr->logScale) {
	    LogScaleAxis(axisPtr);
	} else {
	    LinearScaleAxis(axisPtr);
	}
	axisPtr->flags |= AXIS_CONFIG_DIRTY;
    }

    graphPtr->flags &= ~RESET_AXES;
    graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED);
    /*
     * When any axis changes, we need to layout the entire graph.
     */
    graphPtr->flags |= (COORDS_WORLD | REDRAW_WORLD);
}

/*
 * ----------------------------------------------------------------------
 *
 * ResetTextStyles --
 *
 *	Configures axis attributes (font, line width, label, etc) and
 *	allocates a new (possibly shared) graphics context.  Line cap
 *	style is projecting.  This is for the problem of when a tick
 *	sits directly at the end point of the axis.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 * Side Effects:
 *	Axis resources are allocated (GC, font). Axis layout is
 *	deferred until the height and width of the window are known.
 *
 * ----------------------------------------------------------------------
 */
static void
ResetTextStyles(graphPtr, axisPtr)
    Graph *graphPtr;
    Axis *axisPtr;
{
    GC newGC;
    XGCValues gcValues;
    unsigned long gcMask;

    Blt_ResetTextStyle(graphPtr->tkwin, &(axisPtr->titleStyle));
    Blt_ResetTextStyle(graphPtr->tkwin, &(axisPtr->tickStyle));
    Blt_ResetTextStyle(graphPtr->tkwin, &(axisPtr->limitsStyle));

    gcMask = (GCForeground | GCLineWidth | GCCapStyle);
    gcValues.foreground = axisPtr->tickStyle.color->pixel;
    gcValues.line_width = LineWidth(axisPtr->lineWidth);
    gcValues.cap_style = CapProjecting;

    newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues);
    if (axisPtr->tickGC != NULL) {
	Tk_FreeGC(graphPtr->display, axisPtr->tickGC);
    }
    axisPtr->tickGC = newGC;
}

/*
 * ----------------------------------------------------------------------
 *
 * DestroyAxis --
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Resources (font, color, gc, labels, etc.) associated with the
 *	axis are deallocated.
 *
 * ----------------------------------------------------------------------
 */
static void
DestroyAxis(graphPtr, axisPtr)
    Graph *graphPtr;
    Axis *axisPtr;
{
    int flags;

    flags = GraphType(graphPtr);
    Tk_FreeOptions(configSpecs, (char *)axisPtr, graphPtr->display, flags);

    if (axisPtr->linkPtr != NULL) {
	Blt_ChainDeleteLink(axisPtr->chainPtr, axisPtr->linkPtr);
    }
    if (axisPtr->name != NULL) {
	free(axisPtr->name);
    }
    if (axisPtr->hashPtr != NULL) {
	Tcl_DeleteHashEntry(axisPtr->hashPtr);
    }
    Blt_FreeTextStyle(graphPtr->display, &(axisPtr->titleStyle));
    Blt_FreeTextStyle(graphPtr->display, &(axisPtr->limitsStyle));
    Blt_FreeTextStyle(graphPtr->display, &(axisPtr->tickStyle));

    if (axisPtr->tickGC != NULL) {
	Tk_FreeGC(graphPtr->display, axisPtr->tickGC);
    }
    if (axisPtr->t1Ptr != NULL) {
	free((char *)axisPtr->t1Ptr);
    }
    if (axisPtr->t2Ptr != NULL) {
	free((char *)axisPtr->t2Ptr);
    }
    if (axisPtr->limitsFormats != NULL) {
	free((char *)axisPtr->limitsFormats);
    }
    Blt_ListDestroy(axisPtr->labelList);
    if (axisPtr->segArr != NULL) {
	free((char *)axisPtr->segArr);
    }
    free((char *)axisPtr);
}

static float titleRotate[4] =	/* Rotation for each axis title */
{
    0.0, 90.0, 0.0, 270.0
};

/*
 * ----------------------------------------------------------------------
 *
 * AxisOffsets --
 *
 *	Determines the sites of the axis, major and minor ticks,
 *	and title of the axis.
 *
 * Results:
 *	None.
 *
 * ----------------------------------------------------------------------
 */
static void
AxisOffsets(graphPtr, axisPtr, margin, axisOffset, infoPtr)
    Graph *graphPtr;
    Axis *axisPtr;
    int margin;
    int axisOffset;
    AxisInfo *infoPtr;
{
    int pad;			/* Offset of axis from interior region. This
				 * includes a possible border and the axis
				 * line width. */
    int p;
    int majorOffset, minorOffset, labelOffset;
    int isMultiple;

    isMultiple = (graphPtr->margins[margin].nAxes > 1);
    axisPtr->titleStyle.theta = (double)titleRotate[margin];

    majorOffset = minorOffset = 0;
    labelOffset = AXIS_TITLE_PAD;
    if (axisPtr->lineWidth > 0) {
	majorOffset = ABS(axisPtr->tickLength);
	minorOffset = 10 * majorOffset / 15;
	labelOffset = majorOffset + AXIS_TITLE_PAD + axisPtr->lineWidth / 2;
    }
    /* Adjust offset for the interior border width and the line width */
    pad = graphPtr->plotBW + axisPtr->lineWidth + 1;
    if (graphPtr->plotBW > 0) {
	pad++;
    }

    if ((margin == MARGIN_LEFT) || (margin == MARGIN_TOP)) {
	majorOffset = -majorOffset;
	minorOffset = -minorOffset;
	labelOffset = -labelOffset;
    }
    /*
     * Pre-calculate the x-coordinate positions of the axis, tick labels, and
     * the individual major and minor ticks.
     */
    p = 0;		/* Suppress compiler warning */
    
    switch (margin) {
    case MARGIN_BOTTOM:
	p = graphPtr->bottom + axisOffset + pad;
	if (isMultiple) {
	    axisPtr->titleX = graphPtr->right + AXIS_TITLE_PAD;
	    axisPtr->titleY = graphPtr->bottom + axisOffset + 
		(axisPtr->height / 2);
	    axisPtr->titleStyle.anchor = TK_ANCHOR_W; 
	} else {
	    axisPtr->titleX = (graphPtr->right + graphPtr->left) / 2;
	    axisPtr->titleY = graphPtr->bottom + axisOffset + axisPtr->height + 
		AXIS_TITLE_PAD;
	    axisPtr->titleStyle.anchor = TK_ANCHOR_S; 
	}
	axisPtr->tickStyle.anchor = TK_ANCHOR_N;
	break;

    case MARGIN_LEFT:
	p = graphPtr->left - axisOffset - pad;
	if (isMultiple) {
	    axisPtr->titleX = graphPtr->left - axisOffset - 
		(axisPtr->width / 2);
	    axisPtr->titleY = graphPtr->top - AXIS_TITLE_PAD;
	    axisPtr->titleStyle.anchor = TK_ANCHOR_SW; 
	} else {
	    axisPtr->titleX = graphPtr->left - axisOffset - axisPtr->width -
		graphPtr->plotBW;
	    axisPtr->titleY = (graphPtr->bottom + graphPtr->top) / 2;
	    axisPtr->titleStyle.anchor = TK_ANCHOR_W; 
	}
	axisPtr->tickStyle.anchor = TK_ANCHOR_E;
	break;

    case MARGIN_TOP:
	p = graphPtr->top - axisOffset - pad;
	if (isMultiple) {
	    axisPtr->titleX = graphPtr->right + AXIS_TITLE_PAD;
	    axisPtr->titleY = graphPtr->top - axisOffset - 
		(axisPtr->height  / 2);
	    axisPtr->titleStyle.anchor = TK_ANCHOR_W;
	} else {
	    axisPtr->titleX = (graphPtr->right + graphPtr->left) / 2;
	    axisPtr->titleY = graphPtr->top - axisOffset - axisPtr->height - 
		AXIS_TITLE_PAD;
	    axisPtr->titleStyle.anchor = TK_ANCHOR_N;
	}
	axisPtr->tickStyle.anchor = TK_ANCHOR_S;
	break;

    case MARGIN_RIGHT:
	p = graphPtr->right + axisOffset + pad;
	if (isMultiple) {
	    axisPtr->titleX = graphPtr->right + axisOffset + (axisPtr->width / 2);
	    axisPtr->titleY = graphPtr->top - AXIS_TITLE_PAD;
	    axisPtr->titleStyle.anchor = TK_ANCHOR_SE; 
	} else {
	    axisPtr->titleX = graphPtr->right + axisOffset + axisPtr->width + 
		AXIS_TITLE_PAD;
	    axisPtr->titleY = (graphPtr->bottom + graphPtr->top) / 2;
	    axisPtr->titleStyle.anchor = TK_ANCHOR_E;
	}
	axisPtr->tickStyle.anchor = TK_ANCHOR_W;
	break;

    case MARGIN_NONE:
	break;
    }
    infoPtr->axisLine = p - (axisPtr->lineWidth / 2);
    infoPtr->majorTick = p + majorOffset;
    infoPtr->minorTick = p + minorOffset;
    infoPtr->tickLabel = p + labelOffset;
    if (axisPtr->tickLength < 0) {
	int hold;
	
	hold = infoPtr->majorTick;
	infoPtr->majorTick = infoPtr->axisLine;
	infoPtr->axisLine = hold;
    }
}

static XSegment
AxisLine(graphPtr, axisPtr, line)
    Graph *graphPtr;
    Axis *axisPtr;		/* Axis information */
    int line;
{
    XSegment segment;
    double min, max;

    min = axisPtr->tickRange.min;
    max = axisPtr->tickRange.max;
    if (axisPtr->logScale) {
	min = EXP10(min);
	max = EXP10(max);
    }
    if (AxisIsHoriz(graphPtr, axisPtr)) {
	segment.y1 = segment.y2 = line;
	segment.x1 = (short int)HMap(graphPtr, axisPtr, min);
	segment.x2 = (short int)HMap(graphPtr, axisPtr, max);
    } else {
	segment.x1 = segment.x2 = line;
	segment.y1 = (short int)VMap(graphPtr, axisPtr, min);
	segment.y2 = (short int)VMap(graphPtr, axisPtr, max);
    }
    return segment;
}


static XSegment
Tick(graphPtr, axisPtr, value, tick, line)
    Graph *graphPtr;
    Axis *axisPtr;
    double value;
    int tick, line;		/* Lengths of tick and axis line. */
{
    XSegment segment;

    if (axisPtr->logScale) {
	value = EXP10(value);
    }
    if (AxisIsHoriz(graphPtr, axisPtr)) {
	segment.y1 = line, segment.y2 = tick;
	segment.x1 = segment.x2 = (short int)HMap(graphPtr, axisPtr, value);
    } else {
	segment.x1 = line, segment.x2 = tick;
	segment.y1 = segment.y2 = (short int)VMap(graphPtr, axisPtr, value);
    }
    return segment;
}

/*
 * -----------------------------------------------------------------
 *
 * MapAxis --
 *
 *	Pre-calculates positions of the axis, ticks, and labels (to be
 *	used later when displaying the axis).  Calculates the values
 *	for each major and minor tick and checks to see if they are in
 *	range (the outer ticks may be outside of the range of plotted
 *	values).
 *
 *	Line segments for the minor and major ticks are saved into one
 *	XSegment array so that they can be drawn by a single
 *	XDrawSegments call. The positions of the tick labels are also
 *	computed and saved.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Line segments and tick labels are saved and used later to draw
 *	the axis.
 *
 * -----------------------------------------------------------------
 */
static void
MapAxis(graphPtr, axisPtr, offset, margin)
    Graph *graphPtr;
    Axis *axisPtr;
    int offset;
    int margin;
{
    int arraySize;
    int nMajorTicks, nMinorTicks;
    register int s;
    Blt_ListNode node;
    AxisInfo info;

    AxisOffsets(graphPtr, axisPtr, margin, offset, &info);

    /* Save all line coordinates in an array of line segments. */

    if (axisPtr->segArr != NULL) {
	free((char *)axisPtr->segArr);
    }
    nMajorTicks = nMinorTicks = 0;
    if (axisPtr->t1Ptr != NULL) {
	nMajorTicks = axisPtr->t1Ptr->nTicks;
    }
    if (axisPtr->t2Ptr != NULL) {
	nMinorTicks = axisPtr->t2Ptr->nTicks;
    }
    arraySize = 1 + (nMajorTicks * (nMinorTicks + 1));
    axisPtr->segArr = (XSegment *)malloc(arraySize * sizeof(XSegment));
    assert(axisPtr->segArr);

    s = 0;
    if (axisPtr->lineWidth > 0) {
	/* Axis baseline */
	axisPtr->segArr[s++] = AxisLine(graphPtr, axisPtr, info.axisLine);
    }
    node = Blt_ListFirstNode(axisPtr->labelList);
    if (axisPtr->showTicks) {
	double t1, t2;
	short int labelPos;
	register int i, j;
	int position;
	int horizontal;
	TickLabel *labelPtr = (TickLabel *)&position;

	horizontal = AxisIsHoriz(graphPtr, axisPtr);
	for (i = 0; i < axisPtr->t1Ptr->nTicks; i++) {
	    t1 = axisPtr->t1Ptr->tickArr[i];

	    /* Minor ticks */
	    for (j = 0; j < axisPtr->t2Ptr->nTicks; j++) {
		t2 = t1 +
		    (axisPtr->majorSweep.step * axisPtr->t2Ptr->tickArr[j]);
		if (OutOfRange(t2, &(axisPtr->tickRange))) {
		    continue;
		}
		axisPtr->segArr[s] = 
		    Tick(graphPtr, axisPtr, t2, info.minorTick, info.axisLine);
		s++;
	    }
	    if (OutOfRange(t1, &(axisPtr->tickRange))) {
		continue;
	    }
	    /* Major tick and label position */
	    axisPtr->segArr[s] = 
		Tick(graphPtr, axisPtr, t1, info.majorTick, info.axisLine);
	    labelPos = (short int)info.tickLabel;

	    /* Save tick label X-Y position. */
	    if (horizontal) {
		labelPtr->x = axisPtr->segArr[s].x1;
		labelPtr->y = labelPos;
	    } else {
		labelPtr->x = labelPos;
		labelPtr->y = axisPtr->segArr[s].y1;
	    }
	    assert(node != NULL);
	    Blt_ListSetValue(node, (ClientData)position);
	    node = Blt_ListNextNode(node);
	    s++;
	}
    }
    assert(s <= arraySize);
    axisPtr->nSegments = s;
}

/*
 *----------------------------------------------------------------------
 *
 * AdjustViewport --
 *
 *	Adjusts the offsets of the viewport according to the scroll mode.
 *	This is to accommodate both "listbox" and "canvas" style scrolling.
 *
 *	"canvas"	The viewport scrolls within the range of world
 *			coordinates.  This way the viewport always displays
 *			a full page of the world.  If the world is smaller
 *			than the viewport, then (bizarrely) the world and
 *			viewport are inverted so that the world moves up
 *			and down within the viewport.
 *
 *	"listbox"	The viewport can scroll beyond the range of world
 *			coordinates.  Every entry can be displayed at the
 *			top of the viewport.  This also means that the
 *			scrollbar thumb weirdly shrinks as the last entry
 *			is scrolled upward.
 *
 * Results:
 *	The corrected offset is returned.
 *
 *----------------------------------------------------------------------
 */
static double
AdjustViewport(offset, windowSize)
    double offset, windowSize;
{
    /*
     * Canvas-style scrolling allows the world to be scrolled
     * within the window.
     */
    if (windowSize > 1.0) {
	if (windowSize < (1.0 - offset)) {
	    offset = 1.0 - windowSize;
	}
	if (offset > 0.0) {
	    offset = 0.0;
	}
    } else {
	if ((offset + windowSize) > 1.0) {
	    offset = 1.0 - windowSize;
	}
	if (offset < 0.0) {
	    offset = 0.0;
	}
    }
    return offset;
}

static int
GetAxisScrollInfo(interp, argc, argv, offsetPtr, windowSize, scrollUnits)
    Tcl_Interp *interp;
    int argc;
    char **argv;
    double *offsetPtr;
    double windowSize;
    double scrollUnits;
{
    char c;
    unsigned int length;
    double offset;
    int count;
    double fract;

    offset = *offsetPtr;
    c = argv[0][0];
    length = strlen(argv[0]);
    if ((c == 's') && (strncmp(argv[0], "scroll", length) == 0)) {
	assert(argc == 3);
	/* scroll number unit/page */
	if (Tcl_GetInt(interp, argv[1], &count) != TCL_OK) {
	    return TCL_ERROR;
	}
	c = argv[2][0];
	length = strlen(argv[2]);
	if ((c == 'u') && (strncmp(argv[2], "units", length) == 0)) {
	    fract = (double)count * scrollUnits;
	} else if ((c == 'p') && (strncmp(argv[2], "pages", length) == 0)) {
	    /* A page is 90% of the view-able window. */
	    fract = (double)count * windowSize * 0.9;
	} else {
	    Tcl_AppendResult(interp, "unknown \"scroll\" units \"", argv[2],
		"\"", (char *)NULL);
	    return TCL_ERROR;
	}
	offset += fract;
    } else if ((c == 'm') && (strncmp(argv[0], "moveto", length) == 0)) {
	assert(argc == 2);
	/* moveto fraction */
	if (Tcl_GetDouble(interp, argv[1], &fract) != TCL_OK) {
	    return TCL_ERROR;
	}
	offset = fract;
    } else {
	/* Treat like "scroll units" */
	if (Tcl_GetInt(interp, argv[0], &count) != TCL_OK) {
	    return TCL_ERROR;
	}
	fract = (double)count * scrollUnits;
	offset += fract;
	return TCL_OK;
    }
    *offsetPtr = AdjustViewport(offset, windowSize);
    return TCL_OK;
}

/*
 * -----------------------------------------------------------------
 *
 * DrawAxis --
 *
 *	Draws the axis, ticks, and labels onto the canvas.
 *
 *	Initializes and passes text attribute information through
 *	TextStyle structure.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Axis gets drawn on window.
 *
 * -----------------------------------------------------------------
 */
static void
DrawAxis(graphPtr, drawable, axisPtr)
    Graph *graphPtr;
    Drawable drawable;
    Axis *axisPtr;
{
    if (axisPtr->titleText != NULL) {
	Blt_DrawText(graphPtr->tkwin, drawable, axisPtr->titleText,
	    &(axisPtr->titleStyle), axisPtr->titleX, axisPtr->titleY);
    }
    if (axisPtr->scrollCmdPrefix != NULL) {
	double width, worldWidth;
	double dataMin, dataMax;
	double fract, min, max;
	int horizontal;

	dataMin = axisPtr->dataRange.min;
	dataMax = axisPtr->dataRange.max;
	min = axisPtr->min;
	max = axisPtr->max;
	if (axisPtr->logScale) {
	    dataMin = log10(dataMin);
	    dataMax = log10(dataMax);
	    min = log10(min);
	    max = log10(max);
	}
	worldWidth = dataMax - dataMin;	
	width = max - min;
	horizontal = AxisIsHoriz(graphPtr, axisPtr);
	if (horizontal != axisPtr->descending) {
	    fract = (min - dataMin) / worldWidth;
	} else {
	    fract = (dataMax - max) / worldWidth;
	}
	fract = AdjustViewport(fract, width / worldWidth);

	if (horizontal != axisPtr->descending) {
	    min = (fract * worldWidth);
	    axisPtr->min = min + dataMin;
	    axisPtr->max = axisPtr->min + width;
	    max = min + width;
	    if (axisPtr->logScale) {
		axisPtr->min = EXP10(axisPtr->min);
		axisPtr->max = EXP10(axisPtr->max);
	    }
	    Blt_UpdateScrollbar(graphPtr->interp, axisPtr->scrollCmdPrefix,
			(min / worldWidth), (max / worldWidth));
	} else {
	    max = (fract * worldWidth);
	    axisPtr->max = dataMax - max;
	    axisPtr->min = axisPtr->max - width;
	    min = max + width;
	    if (axisPtr->logScale) {
		axisPtr->min = EXP10(axisPtr->min);
		axisPtr->max = EXP10(axisPtr->max);
	    }
	    Blt_UpdateScrollbar(graphPtr->interp, axisPtr->scrollCmdPrefix,
			(max / worldWidth), (min / worldWidth));
	}
    }
    if (axisPtr->showTicks) {
	register Blt_ListNode node;
	int position;
	TickLabel *labelPtr = (TickLabel *)&position;

	for (node = Blt_ListFirstNode(axisPtr->labelList); node != NULL;
	    node = Blt_ListNextNode(node)) {	/* Draw major tick labels */
	    position = (int)Blt_ListGetValue(node);
	    Blt_DrawText(graphPtr->tkwin, drawable, Blt_ListGetKey(node),
		&(axisPtr->tickStyle), labelPtr->x, labelPtr->y);
	}
    }
    if ((axisPtr->nSegments > 0) && (axisPtr->lineWidth > 0)) {	
	/* Draw the tick marks and axis line. */
	XDrawSegments(graphPtr->display, drawable, axisPtr->tickGC,
	    axisPtr->segArr, axisPtr->nSegments);
    }
}

/*
 * -----------------------------------------------------------------
 *
 * PrintAxis --
 *
 *	Generates PostScript output to draw the axis, ticks, and
 *	labels.
 *
 *	Initializes and passes text attribute information through
 *	TextStyle structure.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	PostScript output is left in graphPtr->interp->result;
 *
 * -----------------------------------------------------------------
 */
/* ARGSUSED */
static void
PrintAxis(printable, axisPtr)
    Printable printable;
    Axis *axisPtr;
{
    if (axisPtr->titleText != NULL) {
	Blt_PrintText(printable, axisPtr->titleText, &(axisPtr->titleStyle),
	    axisPtr->titleX, axisPtr->titleY);
    }
    if (axisPtr->showTicks) {
	register Blt_ListNode node;
	int position;
	TickLabel *labelPtr = (TickLabel *)&position;

	for (node = Blt_ListFirstNode(axisPtr->labelList); node != NULL;
	    node = Blt_ListNextNode(node)) {
	    position = (int)Blt_ListGetValue(node);
	    Blt_PrintText(printable, Blt_ListGetKey(node),
		&(axisPtr->tickStyle), labelPtr->x, labelPtr->y);
	}
    }
    if ((axisPtr->nSegments > 0) && (axisPtr->lineWidth > 0)) {
	Blt_LineAttributesToPostScript(printable, axisPtr->tickStyle.color,
	    axisPtr->lineWidth, (Dashes *)NULL, CapButt, JoinMiter);
	Blt_SegmentsToPostScript(printable, axisPtr->segArr,
	    axisPtr->nSegments);
    }
}

static XSegment
GridLine(graphPtr, axisPtr, value)
    Graph *graphPtr;
    Axis *axisPtr;
    double value;
{
    XSegment segment;

    if (axisPtr->logScale) {
	value = EXP10(value);
    }
    /* Grid lines run orthogonally to the axis */
    if (AxisIsHoriz(graphPtr, axisPtr)) {
	segment.y1 = graphPtr->top;
	segment.y2 = graphPtr->bottom;
	segment.x1 = segment.x2 = (short int)HMap(graphPtr, axisPtr, value);
    } else {
	segment.x1 = graphPtr->left;
	segment.x2 = graphPtr->right;
	segment.y1 = segment.y2 = (short int)VMap(graphPtr, axisPtr, value);
    }
    return segment;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_GetAxisSegments --
 *
 *	Assembles the grid lines associated with an axis. Generates
 *	tick positions if necessary (this happens when the axis is
 *	not a logical axis too).
 *
 * Results:
 *	None.
 *
 *----------------------------------------------------------------------
 */
int
Blt_GetAxisSegments(graphPtr, axisPtr, segPtrPtr)
    Graph *graphPtr;
    Axis *axisPtr;
    XSegment **segPtrPtr;
{
    register int count;
    int needed;
    Ticks *t1Ptr, *t2Ptr;
    Grid *gridPtr = graphPtr->gridPtr;
    register int i;
    double value;
    XSegment *segArr;

    if (axisPtr == NULL) {
	return 0;
    }
    t1Ptr = axisPtr->t1Ptr;
    if (t1Ptr == NULL) {
	t1Ptr = GenerateTicks(&(axisPtr->majorSweep));
    }
    t2Ptr = axisPtr->t2Ptr;
    if (t2Ptr == NULL) {
	t2Ptr = GenerateTicks(&(axisPtr->minorSweep));
    }

    needed = t1Ptr->nTicks;
    if (gridPtr->minorGrid) {
	needed += (t1Ptr->nTicks * t2Ptr->nTicks);
    }
    if (needed == 0) {
	return 0;
    }
    segArr = (XSegment *)malloc(sizeof(XSegment) * needed);
    assert(segArr);

    count = 0;
    for (i = 0; i < t1Ptr->nTicks; i++) {
	value = t1Ptr->tickArr[i];
	if (gridPtr->minorGrid) {
	    register int j;
	    double subValue;

	    for (j = 0; j < t2Ptr->nTicks; j++) {
		subValue = value +
		    (axisPtr->majorSweep.step * t2Ptr->tickArr[j]);
		if (OutOfRange(subValue, &(axisPtr->tickRange))) {
		    continue;
		}
		segArr[count] = GridLine(graphPtr, axisPtr, subValue);
		count++;
	    }
	}
	if (OutOfRange(value, &(axisPtr->tickRange))) {
	    continue;
	}
	segArr[count] = GridLine(graphPtr, axisPtr, value);
	count++;
    }

    if (t1Ptr != axisPtr->t1Ptr) {
	free((char *)t1Ptr);	/* Free generated ticks. */
    }
    if (t2Ptr != axisPtr->t2Ptr) {
	free((char *)t2Ptr);	/* Free generated ticks. */
    }
    assert(count <= needed);
    *segPtrPtr = segArr;
    return count;
}

/*
 *----------------------------------------------------------------------
 *
 * GetAxisGeometry --
 *
 * Results:
 *	None.
 *
 *----------------------------------------------------------------------
 */
static void
GetAxisGeometry(graphPtr, axisPtr, isMultiple)
    Graph *graphPtr;
    Axis *axisPtr;
    int isMultiple;
{
    int height;

    /* Release any previously generated ticks labels */
    Blt_ListReset(axisPtr->labelList);
    height = 0;
    if (axisPtr->lineWidth > 0) {
	/* Leave room for axis baseline (and pad) */
	height += axisPtr->lineWidth + 2;
    }
    if (axisPtr->showTicks) {
	int pad;
	register int i, nLabels;
	char label[TICK_LABEL_SIZE + 1];
	int lw, lh;
	double dist, x;
	int tickWidth, tickHeight;
	
	SweepTicks(axisPtr);
	
	/* Hey Sani, does this check fail under AIX? */
	assert((axisPtr->t1Ptr->nTicks >= 0) && 
	       (axisPtr->t1Ptr->nTicks <= MAXTICKS));
	
	tickHeight = tickWidth = 0;
	nLabels = 0;
	for (i = 0; i < axisPtr->t1Ptr->nTicks; i++) {
	    x = axisPtr->t1Ptr->tickArr[i];
	    if (OutOfRange(x, &(axisPtr->tickRange))) {
		continue;
	    }
	    MakeLabel(graphPtr, axisPtr, x, label);
	    Blt_ListNewAppend(axisPtr->labelList, label, 0);
	    nLabels++;
	    
	    /* Get dimensions of each tick label.  Remember tick labels
	     * can be multi-lined and/or rotated. */
	    Blt_GetTextExtents(&(axisPtr->tickStyle), label, &lw, &lh);
	    if (axisPtr->tickStyle.theta > 0.0) {
		Blt_GetBoundingBox(lw, lh, axisPtr->tickStyle.theta, &lw, &lh, 
		   (XPoint *)NULL);
	    }
	    if (tickWidth < lw) {
		tickWidth = lw;
	    }
	    if (tickHeight < lh) {
		tickHeight = lh;
	    }
	}
	assert(nLabels <= axisPtr->t1Ptr->nTicks);
	
	/* Because the axis cap style is "CapProjecting", there's
	 * an extra 1.5 linewidth to be accounted for at the ends
	 * of each line.  */

	pad = ((axisPtr->lineWidth * 15) / 10);
	
	if (AxisIsHoriz(graphPtr, axisPtr)) {
	    height += tickHeight + pad;
	} else {
	    height += tickWidth + pad;
	}
	if (axisPtr->lineWidth > 0) {
	    /* Distance from axis line to tick label. */
	    dist = ABS(axisPtr->tickLength) + AXIS_TITLE_PAD;
	    height += ROUND(dist);
	}
    }

    if (axisPtr->titleText != NULL) {
	if (isMultiple) {
	    if (height < axisPtr->titleHeight) {
		height = axisPtr->titleHeight;
	    }
	} else {
	    height += axisPtr->titleHeight + AXIS_TITLE_PAD;
	}
    }

    /* Correct for orientation of the axis. */
    if (AxisIsHoriz(graphPtr, axisPtr)) {
	axisPtr->height = height;
    } else {
	axisPtr->width = height;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * GetMarginGeometry --
 *
 *	Examines all the axes in the given margin and determines the 
 *	area required to display them.  
 *
 *	Note: For multiple axes, the titles are displayed in another
 *	      margin. So we must keep track of the widest title.
 *	
 * Results:
 *	Returns the width or height of the margin, depending if it
 *	runs horizontally along the graph or vertically.
 *
 * Side Effects:
 *	The area width and height set in the margin.  Note again that
 *	this may be corrected later (mulitple axes) to adjust for
 *	the longest title in another margin.
 *
 *----------------------------------------------------------------------
 */
static int
GetMarginGeometry(graphPtr, marginPtr)
    Graph *graphPtr;
    Margin *marginPtr;
{
    Blt_ChainLink *linkPtr;
    Axis *axisPtr;
    int width, height;
    int isHoriz;
    int length, count;

    /* Count the number of visible axes. */
    count = 0;
    isHoriz = HORIZMARGIN(marginPtr);
    for (linkPtr = Blt_ChainFirstLink(marginPtr->chainPtr); linkPtr != NULL;
	 linkPtr = Blt_ChainNextLink(linkPtr)) {
	axisPtr = (Axis *)Blt_ChainGetValue(linkPtr);
	if (!axisPtr->hidden) {
	    count++;
	}
    }
    length = width = height = 0;
    for (linkPtr = Blt_ChainFirstLink(marginPtr->chainPtr); linkPtr != NULL;
	 linkPtr = Blt_ChainNextLink(linkPtr)) {
	axisPtr = (Axis *)Blt_ChainGetValue(linkPtr);
	if (axisPtr->hidden) {
	    continue;
	}
	if (graphPtr->flags & GET_AXIS_GEOMETRY) {
	    GetAxisGeometry(graphPtr, axisPtr, (count > 1));
	}
	if (length < axisPtr->titleWidth) {
	    length = axisPtr->titleWidth;
	}
	if (isHoriz) {
	    height += axisPtr->height;
	} else {
	    width += axisPtr->width;
	}
    }
    /* Enforce a minimum size for margins. */
    if (width < 3) {
	width = 3;
    }
    if (height < 3) {
	height = 3;
    }
    marginPtr->nAxes = count;
    marginPtr->length = length;
    marginPtr->width = width;
    marginPtr->height = height;
    return (HORIZMARGIN(marginPtr)) ? marginPtr->height : marginPtr->width;
}

/*
 *----------------------------------------------------------------------
 *
 * ComputeMargins --
 *
 * Results:
 *
 *----------------------------------------------------------------------
 */
static void
ComputeMargins(graphPtr)
    Graph *graphPtr;
{
    int left, right, top, bottom;
    int width, height;
    int insets;

    left = right = top = bottom = 0;
    top += GetMarginGeometry(graphPtr, &(graphPtr->topMargin));
    bottom += GetMarginGeometry(graphPtr, &(graphPtr->bottomMargin));
    left += GetMarginGeometry(graphPtr, &(graphPtr->leftMargin));
    right += GetMarginGeometry(graphPtr, &(graphPtr->rightMargin));

    /* Add the graph title height to the top margin. */
    if (graphPtr->titleText != NULL) {
	top += graphPtr->titleStyle.height;
    }
    insets = 2 * (graphPtr->inset + graphPtr->plotBW);
    width = graphPtr->width - (insets + left + right);
    height = graphPtr->height - (insets + top + bottom);
    if (graphPtr->aspect > 0.0) {
	double ratio;
	int w, h;

	/* Shrink one dimension (width or height) of the plotarea to
	 * fit the requested aspect ratio.  */
	ratio = (double)width / (double)height;
	if (ratio > graphPtr->aspect) {
	    w = (int)(height * graphPtr->aspect);
	    if (w < 1) {
		w = 1;
	    }
	    right += (width - w);
	    width = w;
	} else {
	    h = (int)(width / graphPtr->aspect);
	    if (h < 1) {
		h = 1;
	    }
	    bottom += (height - h);
	    height = h;
	}
    }
    Blt_LayoutLegend(graphPtr, width, height);
    if (!graphPtr->legendPtr->hidden) {
	switch (GetLegendSite(graphPtr)) {
	case LEGEND_RIGHT:
	    right += graphPtr->legendPtr->width + 2;
	    break;
	case LEGEND_LEFT:
	    left += graphPtr->legendPtr->width + 2;
	    break;
	case LEGEND_TOP:
	    top += graphPtr->legendPtr->height + 2;
	    break;
	case LEGEND_BOTTOM:
	    bottom += graphPtr->legendPtr->height + 2;
	    break;
	case LEGEND_XY:
	case LEGEND_PLOT:
	    /* Do nothing. */
	    break;
	}
    }
    /* Make sure there's room for the longest axis titles. */
    if ((graphPtr->leftMargin.nAxes > 1) && 
	(top < graphPtr->leftMargin.length)) {
	top = graphPtr->leftMargin.length;
    }
    if ((graphPtr->bottomMargin.nAxes > 1) && 
	(right < graphPtr->bottomMargin.length)) {
	right = graphPtr->bottomMargin.length;
    }
    if ((graphPtr->rightMargin.nAxes > 1) && 
	(top < graphPtr->rightMargin.length)) {
	top = graphPtr->rightMargin.length;
    }
    if ((graphPtr->topMargin.nAxes > 1) && 
	(right < graphPtr->topMargin.length)) {
	right = graphPtr->topMargin.length;
    }
    graphPtr->topMargin.size = top;
    graphPtr->leftMargin.size = left;
    graphPtr->rightMargin.size = right;
    graphPtr->bottomMargin.size = bottom;

    /* Override calculated values if user specified margins */
    graphPtr->leftMargin.width = (graphPtr->leftMargin.reqSize > 0)
	? graphPtr->leftMargin.reqSize : left;
    graphPtr->rightMargin.width = (graphPtr->rightMargin.reqSize > 0)
	? graphPtr->rightMargin.reqSize : right;
    graphPtr->topMargin.height = (graphPtr->topMargin.reqSize > 0)
	? graphPtr->topMargin.reqSize : top;
    graphPtr->bottomMargin.height = (graphPtr->bottomMargin.reqSize > 0)
	? graphPtr->bottomMargin.reqSize : bottom;
}

/*
 * -----------------------------------------------------------------
 *
 * Blt_LayoutMargins --
 *
 * 	Calculate the layout of the graph.  Based upon the data,
 *	axis limits, X and Y titles, and title height, determine
 *	the cavity left which is the plotting surface.  The first
 *	step get the data and axis limits for calculating the space
 *	needed for the top, bottom, left, and right margins.
 *
 * 	1) The LEFT margin is the area from the left border to the
 *	   Y axis (not including ticks). It composes the border
 *	   width, the width an optional Y axis label and its padding,
 *	   and the tick numeric labels. The Y axis label is rotated
 *	   90 degrees so that the width is the font height.
 *
 * 	2) The RIGHT margin is the area from the end of the graph
 *	   to the right window border. It composes the border width,
 *	   some padding, the font height (this may be dubious. It
 *	   appears to provide a more even border), the max of the
 *	   legend width and 1/2 max X tick number. This last part is
 *	   so that the last tick label is not clipped.
 *
 *           Window Width
 *      ___________________________________________________________
 *      |          |                               |               |
 *      |          |   TOP  height of title        |               |
 *      |          |                               |               |
 *      |          |           x2 title            |               |
 *      |          |                               |               |
 *      |          |        height of x2-axis      |               |
 *      |__________|_______________________________|_______________|  W
 *      |          | -plotpady                     |               |  i
 *      |__________|_______________________________|_______________|  n
 *      |          | top                   right   |               |  d
 *      |          |                               |               |  o
 *      |   LEFT   |                               |     RIGHT     |  w
 *      |          |                               |               |
 *      | y        |     Free area = 104%          |      y2       |  H
 *      |          |     Plotting surface = 100%   |               |  e
 *      | t        |     Tick length = 2 + 2%      |      t        |  i
 *      | i        |                               |      i        |  g
 *      | t        |                               |      t  legend|  h
 *      | l        |                               |      l   width|  t
 *      | e        |                               |      e        |
 *      |    height|                               |height         |
 *      |       of |                               | of            |
 *      |    y-axis|                               |y2-axis        |
 *      |          |                               |               |
 *      |          |origin 0,0                     |               |
 *      |__________|_left___________________bottom___|_______________|
 *      |          |-plotpady                      |               |
 *      |__________|_______________________________|_______________|
 *      |          | (xoffset, yoffset)            |               |
 *      |          |                               |               |
 *      |          |       height of x-axis        |               |
 *      |          |                               |               |
 *      |          |   BOTTOM   x title            |               |
 *      |__________|_______________________________|_______________|
 *
 * 3) The TOP margin is the area from the top window border to the top
 *    of the graph. It composes the border width, twice the height of
 *    the title font (if one is given) and some padding between the
 *    title.
 *
 * 4) The BOTTOM margin is area from the bottom window border to the
 *    X axis (not including ticks). It composes the border width, the height
 *    an optional X axis label and its padding, the height of the font
 *    of the tick labels.
 *
 * The plotting area is between the margins which includes the X and Y axes
 * including the ticks but not the tick numeric labels. The length of
 * the ticks and its padding is 5% of the entire plotting area.  Hence the
 * entire plotting area is scaled as 105% of the width and height of the
 * area.
 *
 * The axis labels, ticks labels, title, and legend may or may not be
 * displayed which must be taken into account.
 *
 *
 * -----------------------------------------------------------------
 */
void
Blt_LayoutMargins(graphPtr)
    Graph *graphPtr;
{
    int width, height;
    int titleY;
    int left, right, top, bottom;

    ComputeMargins(graphPtr);
    left = graphPtr->leftMargin.width + graphPtr->inset + graphPtr->plotBW;
    right = graphPtr->rightMargin.width + graphPtr->inset + graphPtr->plotBW;
    top = graphPtr->topMargin.height + graphPtr->inset + graphPtr->plotBW;
    bottom = graphPtr->bottomMargin.height + graphPtr->inset + graphPtr->plotBW;

    /* Based upon the margins, calculate the space left for the graph. */
    width = graphPtr->width - (left + right);
    height = graphPtr->height - (top + bottom);
    if (width < 1) {
	width = 1;
    }
    if (height < 1) {
	height = 1;
    }
    graphPtr->left = left;
    graphPtr->right = left + width;
    graphPtr->bottom = top + height;
    graphPtr->top = top;

    graphPtr->vOffset = top + graphPtr->padTop;
    graphPtr->vRange = height - PADDING(graphPtr->padY);
    graphPtr->hOffset = left + graphPtr->padLeft;
    graphPtr->hRange = width - PADDING(graphPtr->padX);
    if (graphPtr->vRange < 1) {
	graphPtr->vRange = 1;
    }
    if (graphPtr->hRange < 1) {
	graphPtr->hRange = 1;
    }

    /*
     * Calculate the placement of the graph title so it is centered within the
     * space provided for it in the top margin
     */
    titleY = graphPtr->titleStyle.height;
    graphPtr->titleY = (titleY / 2) + graphPtr->inset;
    graphPtr->titleX = (graphPtr->right + graphPtr->left) / 2;

}

/*
 * ----------------------------------------------------------------------
 *
 * ConfigureAxis --
 *
 *	Configures axis attributes (font, line width, label, etc).
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 * Side Effects:
 *	Axis layout is deferred until the height and width of the
 *	window are known.
 *
 * ----------------------------------------------------------------------
 */

static int
ConfigureAxis(graphPtr, axisPtr)
    Graph *graphPtr;
    Axis *axisPtr;
{
    char errMsg[200];

    /* Check the requested axis limits. Can't allow -min to be greater
     * than -max, or have undefined log scale limits.  */
    if (((axisPtr->flags & AXIS_CONFIG_BOTH) == AXIS_CONFIG_BOTH) &&
	(axisPtr->min >= axisPtr->max)) {
	sprintf(errMsg, "impossible limits (min %g >= max %g) for axis \"%s\"",
	    axisPtr->min, axisPtr->max, axisPtr->name);
	Tcl_AppendResult(graphPtr->interp, errMsg, (char *)NULL);
	/* Bad values, turn on axis auto-scaling */
	axisPtr->flags &= ~AXIS_CONFIG_BOTH;
	return TCL_ERROR;
    }
    if ((axisPtr->logScale) && (axisPtr->flags & AXIS_CONFIG_MIN) &&
	(axisPtr->min <= 0.0)) {
	sprintf(errMsg, "bad logscale limits (min=%g,max=%g) for axis \"%s\"",
	    axisPtr->min, axisPtr->max, axisPtr->name);
	Tcl_AppendResult(graphPtr->interp, errMsg, (char *)NULL);
	/* Bad minimum value, turn on auto-scaling */
	axisPtr->flags &= ~AXIS_CONFIG_MIN;
	return TCL_ERROR;
    }
    axisPtr->tickStyle.theta = FMOD(axisPtr->tickStyle.theta, 360.0);
    if (axisPtr->tickStyle.theta < 0.0) {
	axisPtr->tickStyle.theta += 360.0;
    }
    ResetTextStyles(graphPtr, axisPtr);

    axisPtr->titleWidth = axisPtr->titleHeight = 0;
    if (axisPtr->titleText != NULL) {
	int w, h;

	Blt_GetTextExtents(&(axisPtr->titleStyle), axisPtr->titleText, &w, &h);
	axisPtr->titleWidth = (short int)w;
	axisPtr->titleHeight = (short int)h;
    }
    /*
     * Don't bother to check what configuration options have changed.
     * Almost every option changes the size of the plotting area
     * (except for -foreground), requiring the graph and its contents
     * to be completely redrawn.
     *
     * Recompute the scale and offset of the axis in case -min, -max
     * options have changed.
     */
    graphPtr->flags |= (REDRAW_WORLD | COORDS_WORLD | RESET_AXES);
    axisPtr->flags |= AXIS_CONFIG_DIRTY;
    Blt_EventuallyRedrawGraph(graphPtr);

    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * CreateAxis --
 *
 *	Create and initialize a structure containing information to
 * 	display a graph axis.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 * ----------------------------------------------------------------------
 */
static Axis *
CreateAxis(graphPtr, name, margin)
    Graph *graphPtr;
    char *name;			/* Identifier for axis. */
    int margin;
{
    Axis *axisPtr;
    Tcl_HashEntry *hPtr;
    int isNew;

    hPtr = Tcl_CreateHashEntry(&(graphPtr->axisTable), name, &isNew);
    if (!isNew) {
	axisPtr = (Axis *)Tcl_GetHashValue(hPtr);
	if (!axisPtr->deletePending) {
	    Tcl_AppendResult(graphPtr->interp, "axis \"", name,
		"\" already exists in \"", Tk_PathName(graphPtr->tkwin), "\"",
		(char *)NULL);
	    return NULL;
	}
	axisPtr->deletePending = FALSE;
    } else {
	axisPtr = (Axis *)calloc(1, sizeof(Axis));
	assert(axisPtr);

	axisPtr->name = strdup(name);
	axisPtr->hashPtr = hPtr;
	axisPtr->type = AXIS_TYPE_NONE;
	axisPtr->looseMin = axisPtr->looseMax = TICK_RANGE_TIGHT;
	axisPtr->reqNumMinorTicks = 2;
	axisPtr->scrollUnits = 10;
	axisPtr->showTicks = TRUE;

	if ((graphPtr->type == TYPE_ELEM_BAR) && 
	    ((margin == MARGIN_TOP) || (margin == MARGIN_BOTTOM))) {
	    axisPtr->reqStep = 1.0;
	    axisPtr->reqNumMinorTicks = 0;
	} 
	if ((margin == MARGIN_RIGHT) || (margin == MARGIN_TOP)) {
	    axisPtr->hidden = TRUE;
	}
	Blt_InitTextStyle(&(axisPtr->titleStyle));
	Blt_InitTextStyle(&(axisPtr->limitsStyle));
	Blt_InitTextStyle(&(axisPtr->tickStyle));
	axisPtr->labelList = Blt_ListCreate(TCL_STRING_KEYS);
	axisPtr->lineWidth = 1;
	axisPtr->tickStyle.padX.side1 = axisPtr->tickStyle.padX.side2 = 2;
	Tcl_SetHashValue(hPtr, (ClientData)axisPtr);
    }
    return axisPtr;
}

static int
NameToAxis(graphPtr, name, axisPtrPtr)
    Graph *graphPtr;		/* Graph widget record. */
    char *name;			/* Name of the axis to be searched for. */
    Axis **axisPtrPtr;		/* (out) Pointer to found axis structure. */
{
    Tcl_HashEntry *hPtr;

    hPtr = Tcl_FindHashEntry(&(graphPtr->axisTable), name);
    if (hPtr != NULL) {
	Axis *axisPtr;

	axisPtr = (Axis *)Tcl_GetHashValue(hPtr);
	if (!axisPtr->deletePending) {
	    *axisPtrPtr = axisPtr;
	    return TCL_OK;
	}
    }
    Tcl_AppendResult(graphPtr->interp, "can't find axis \"", name,
	    "\" in \"", Tk_PathName(graphPtr->tkwin), "\"", (char *)NULL);
    *axisPtrPtr = NULL;
    return TCL_ERROR;
}

static int
GetAxis(graphPtr, axisName, flags, axisPtrPtr)
    Graph *graphPtr;
    char *axisName;
    unsigned int flags;
    Axis **axisPtrPtr;
{
    Axis *axisPtr;
    int type;

    if (NameToAxis(graphPtr, axisName, &axisPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    type = (flags & AXIS_TYPE_MASK);
    if (type != 0) {
	if ((axisPtr->refCount == 0) || (axisPtr->type == AXIS_TYPE_NONE)) {
	    /* Set the axis type on the first use of it. */
	    axisPtr->type = type;
	} else if (axisPtr->type != type) {
	    Tcl_AppendResult(graphPtr->interp, "axis \"", axisName,
		"\" is already in use on an opposite axis", (char *)NULL);
	    return TCL_ERROR;
	}
	axisPtr->refCount++;
    }
    *axisPtrPtr = axisPtr;
    return TCL_OK;
}

static void
FreeAxis(graphPtr, axisPtr)
    Graph *graphPtr;
    Axis *axisPtr;
{
    axisPtr->refCount--;
    if ((axisPtr->deletePending) && (axisPtr->refCount == 0)) {
	DestroyAxis(graphPtr, axisPtr);
    }
}


void
Blt_DestroyAxes(graphPtr)
    Graph *graphPtr;
{
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch cursor;
    Axis *axisPtr;
    int i;

    for (hPtr = Tcl_FirstHashEntry(&(graphPtr->axisTable), &cursor);
	hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	axisPtr = (Axis *)Tcl_GetHashValue(hPtr);
	axisPtr->hashPtr = NULL;
	DestroyAxis(graphPtr, axisPtr);
    }
    Tcl_DeleteHashTable(&(graphPtr->axisTable));
    for (i = 0; i < 4; i++) {
	Blt_ChainDestroy(graphPtr->axes[i]);
    }
}

int
Blt_DefaultAxes(graphPtr)
    Graph *graphPtr;
{
    register int i;
    Axis *axisPtr;
    Blt_Chain *chainPtr;
    static char *axisNames[4] = { "x", "y", "x2", "y2" } ;
    int flags;

    flags = GraphType(graphPtr);
    for (i = 0; i < 4; i++) {
	chainPtr = Blt_ChainCreate();
	graphPtr->axes[i] = chainPtr;

	/* Create a default axis for each chain. */
	axisPtr = CreateAxis(graphPtr, axisNames[i], i);
	if (axisPtr == NULL) {
	    return TCL_ERROR;
	}
	axisPtr->refCount = 1;
	axisPtr->type = (i & 1) ? AXIS_TYPE_Y : AXIS_TYPE_X;

	/*
	 * Blt_ConfigureWidgetComponent creates a temporary child window 
	 * by the name of the axis.  It's used so that the Tk routines
	 * that access the X resource database can describe a single 
	 * component and not the entire graph.
	 */
	if (Blt_ConfigureWidgetComponent(graphPtr->interp, graphPtr->tkwin,
		axisPtr->name, "Axis", configSpecs, 0, (char **)NULL,
		(char *)axisPtr, flags) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (ConfigureAxis(graphPtr, axisPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	axisPtr->linkPtr = Blt_ChainAppend(chainPtr, (ClientData)axisPtr);
	axisPtr->chainPtr = chainPtr;
    }
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * CgetOp --
 *
 *	Queries axis attributes (font, line width, label, etc).
 *
 * Results:
 *	Return value is a standard Tcl result.  If querying configuration
 *	values, interp->result will contain the results.
 *
 * ----------------------------------------------------------------------
 */
/* ARGSUSED */
static int
CgetOp(graphPtr, axisPtr, argc, argv)
    Graph *graphPtr;
    Axis *axisPtr;
    int argc;			/* Not used. */
    char *argv[];
{
    return Tk_ConfigureValue(graphPtr->interp, graphPtr->tkwin, configSpecs,
	(char *)axisPtr, argv[0], GraphType(graphPtr));
}

/*
 * ----------------------------------------------------------------------
 *
 * ConfigureOp --
 *
 *	Queries or resets axis attributes (font, line width, label, etc).
 *
 * Results:
 *	Return value is a standard Tcl result.  If querying configuration
 *	values, interp->result will contain the results.
 *
 * Side Effects:
 *	Axis resources are possibly allocated (GC, font). Axis layout is
 *	deferred until the height and width of the window are known.
 *
 * ----------------------------------------------------------------------
 */
static int
ConfigureOp(graphPtr, axisPtr, argc, argv)
    Graph *graphPtr;
    Axis *axisPtr;
    int argc;
    char *argv[];
{
    int flags;

    flags = TK_CONFIG_ARGV_ONLY | GraphType(graphPtr);
    if (argc == 0) {
	return Tk_ConfigureInfo(graphPtr->interp, graphPtr->tkwin, configSpecs,
	    (char *)axisPtr, (char *)NULL, flags);
    } else if (argc == 1) {
	return Tk_ConfigureInfo(graphPtr->interp, graphPtr->tkwin, configSpecs,
	    (char *)axisPtr, argv[0], flags);
    }
    if (Tk_ConfigureWidget(graphPtr->interp, graphPtr->tkwin, configSpecs,
	    argc, argv, (char *)axisPtr, flags) != TCL_OK) {
	return TCL_ERROR;
    }
    if (ConfigureAxis(graphPtr, axisPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (axisPtr->refCount > 0) {
	graphPtr->flags |= REDRAW_BACKING_STORE | REDRAW_MARGINS;
	Blt_EventuallyRedrawGraph(graphPtr);
    }
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * LimitsOp --
 *
 *	This procedure returns a string representing the axis limits
 *	of the graph.  The format of the string is { left top right bottom}.
 *
 * Results:
 *	Always returns TCL_OK.  The interp->result field is
 *	a list of the graph axis limits.
 *
 *--------------------------------------------------------------
 */
/*ARGSUSED*/
static int
LimitsOp(graphPtr, axisPtr, argc, argv)
    Graph *graphPtr;
    Axis *axisPtr;
    int argc;			/* Not used. */
    char **argv;		/* Not used. */

{
    Tcl_Interp *interp = graphPtr->interp;
    double min, max;

    if (graphPtr->flags & RESET_AXES) {
	Blt_ResetAxes(graphPtr);
    }
    if (axisPtr->logScale) {
	min = EXP10(axisPtr->tickRange.min);
	max = EXP10(axisPtr->tickRange.max);
    } else {
	min = axisPtr->tickRange.min;
	max = axisPtr->tickRange.max;
    }
    Tcl_AppendElement(interp, Blt_Dtoa(interp, min));
    Tcl_AppendElement(interp, Blt_Dtoa(interp, max));
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * InvTransformOp --
 *
 *	Maps the given window coordinate into an axis-value.
 *
 * Results:
 *	Returns a standard Tcl result.  interp->result contains
 *	the axis value. If an error occurred, TCL_ERROR is returned
 *	and interp->result will contain an error message.
 *
 * ----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
InvTransformOp(graphPtr, axisPtr, argc, argv)
    Graph *graphPtr;
    Axis *axisPtr;
    int argc;			/* Not used. */
    char **argv;
{
    int x;			/* Integer window coordinate*/
    double y;			/* Real graph coordinate */

    if (graphPtr->flags & RESET_AXES) {
	Blt_ResetAxes(graphPtr);
    }
    if (Tcl_GetInt(graphPtr->interp, argv[0], &x) != TCL_OK) {
	return TCL_ERROR;
    }
    /*
     * Is the axis vertical or horizontal?
     *
     * Check the site where the axis was positioned.  If the axis is
     * virtual, all we have to go on is how it was mapped to an
     * element (using either -mapx or -mapy options).  
     */
    if (AxisIsHoriz(graphPtr, axisPtr)) {
	y = InvHMap(graphPtr, axisPtr, (double)x);
    } else {
	y = InvVMap(graphPtr, axisPtr, (double)x);
    }
    Tcl_AppendElement(graphPtr->interp, Blt_Dtoa(graphPtr->interp, y));
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * TransformOp --
 *
 *	Maps the given axis-value to a window coordinate.
 *
 * Results:
 *	Returns a standard Tcl result.  interp->result contains
 *	the window coordinate. If an error occurred, TCL_ERROR
 *	is returned and interp->result will contain an error
 *	message.
 *
 * ----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
TransformOp(graphPtr, axisPtr, argc, argv)
    Graph *graphPtr;
    Axis *axisPtr;		/* Axis */
    int argc;			/* Not used. */
    char **argv;
{
    double x;

    if (graphPtr->flags & RESET_AXES) {
	Blt_ResetAxes(graphPtr);
    }
    if (Blt_GetCoordinate(graphPtr->interp, argv[0], &x) != TCL_OK) {
	return TCL_ERROR;
    }
    if (AxisIsHoriz(graphPtr, axisPtr)) {
	x = HMap(graphPtr, axisPtr, x);
    } else {
	x = VMap(graphPtr, axisPtr, x);
    }
    Tcl_SetResult(graphPtr->interp, Blt_Itoa((int)x), TCL_VOLATILE);
    return TCL_OK;
}

/*
 *--------------------------------------------------------------
 *
 * UseOp --
 *
 *	Changes the virtual axis used by the logical axis.
 *
 * Results:
 *	A standard Tcl result.  If the named axis doesn't exist
 *	an error message is put in interp->result.
 *
 * .g xaxis use "abc def gah"
 * .g xaxis use [lappend abc [.g axis use]]
 *
 *--------------------------------------------------------------
 */
/*ARGSUSED*/
static int
UseOp(graphPtr, axisPtr, argc, argv)
    Graph *graphPtr;
    Axis *axisPtr;		/* Not used. */
    int argc;
    char **argv;
{
    Blt_Chain *chainPtr;
    int nElem;
    char **elemArr;
    Blt_ChainLink *linkPtr;
    int i;
    int type;
    int margin;

    margin = (int)argv[-1];
    chainPtr = graphPtr->margins[margin].chainPtr;
    if (argc == 0) {
	for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr!= NULL;
	     linkPtr = Blt_ChainNextLink(linkPtr)) {
	    axisPtr = (Axis *)Blt_ChainGetValue(linkPtr);
	    Tcl_AppendElement(graphPtr->interp, axisPtr->name);
	}
	return TCL_OK;
    }
    if ((margin == MARGIN_BOTTOM) || (margin == MARGIN_TOP)) {
	type = (graphPtr->inverted) ? AXIS_TYPE_Y : AXIS_TYPE_X;
    } else {
	type = (graphPtr->inverted) ? AXIS_TYPE_X : AXIS_TYPE_Y;
    }
    if (Tcl_SplitList(graphPtr->interp, argv[0], &nElem, &elemArr) != TCL_OK) {
	return TCL_ERROR;
    }
    for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr!= NULL;
	 linkPtr = Blt_ChainNextLink(linkPtr)) {
	axisPtr = (Axis *)Blt_ChainGetValue(linkPtr);
	axisPtr->linkPtr = NULL;
	/* Clear the type of axes not currently mapped to elements.*/
	if (!(axisPtr->flags & AXIS_MAPS_ELEM)) {
	    axisPtr->type = AXIS_TYPE_NONE; 
	}
    }
    Blt_ChainReset(chainPtr);
    for (i = 0; i < nElem; i++) {
	if (NameToAxis(graphPtr, elemArr[i], &axisPtr) != TCL_OK) {
	    free((char *)elemArr);
	    return TCL_ERROR;
	}
	if (axisPtr->type == AXIS_TYPE_NONE) {
	    axisPtr->type = type; 
	} else if (axisPtr->type != type) {
	    Tcl_AppendResult(graphPtr->interp, "wrong type axis \"", 
		axisPtr->name, "\": can't use ", 
		(axisPtr->type == AXIS_TYPE_Y) ? "Y" : "X", " type axis.",
  	        (char *)NULL);			     
	    free((char *)elemArr);
	    return TCL_ERROR;
	}
	if (axisPtr->linkPtr != NULL) {
	    /* Move the axis from the old margin's "use" list to the new. */
	    Blt_ChainUnlinkLink(axisPtr->chainPtr, axisPtr->linkPtr);
	    Blt_ChainAppendLink(chainPtr, axisPtr->linkPtr);
	} else {
	    axisPtr->linkPtr = Blt_ChainAppend(chainPtr, (ClientData)axisPtr);
	}
	axisPtr->chainPtr = chainPtr;
    }
    graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED | RESET_AXES);
    /* When any axis changes, we need to layout the entire graph.  */
    graphPtr->flags |= (COORDS_WORLD | REDRAW_WORLD);
    Blt_EventuallyRedrawGraph(graphPtr);
    
    free((char *)elemArr);
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * CreateVirtualOp --
 *
 *	Creates a new axis.
 *
 * Results:
 *	Returns a standard Tcl result.
 *
 * ----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
CreateVirtualOp(graphPtr, argc, argv)
    Graph *graphPtr;
    int argc;
    char **argv;
{
    Axis *axisPtr;
    int flags;

    axisPtr = CreateAxis(graphPtr, argv[3], MARGIN_NONE);
    if (axisPtr == NULL) {
	return TCL_ERROR;
    }
    flags = GraphType(graphPtr);
    if (Blt_ConfigureWidgetComponent(graphPtr->interp, graphPtr->tkwin,
	    axisPtr->name, "Axis", configSpecs, argc - 4, argv + 4,
	    (char *)axisPtr, flags) != TCL_OK) {
	goto error;
    }
    if (ConfigureAxis(graphPtr, axisPtr) != TCL_OK) {
	goto error;
    }
    Tcl_SetResult(graphPtr->interp, axisPtr->name, TCL_STATIC);
    return TCL_OK;
  error:
    DestroyAxis(graphPtr, axisPtr);
    return TCL_ERROR;
}

/*
 * ----------------------------------------------------------------------
 *
 * CgetVirtualOp --
 *
 *	Queries axis attributes (font, line width, label, etc).
 *
 * Results:
 *	Return value is a standard Tcl result.  If querying configuration
 *	values, interp->result will contain the results.
 *
 * ----------------------------------------------------------------------
 */
/* ARGSUSED */
static int
CgetVirtualOp(graphPtr, argc, argv)
    Graph *graphPtr;
    int argc;
    char *argv[];
{
    Axis *axisPtr;

    if (NameToAxis(graphPtr, argv[3], &axisPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    return CgetOp(graphPtr, axisPtr, argc - 4, argv + 4);
}

/*
 * ----------------------------------------------------------------------
 *
 * ConfigureVirtualOp --
 *
 *	Queries or resets axis attributes (font, line width, label, etc).
 *
 * Results:
 *	Return value is a standard Tcl result.  If querying configuration
 *	values, interp->result will contain the results.
 *
 * Side Effects:
 *	Axis resources are possibly allocated (GC, font). Axis layout is
 *	deferred until the height and width of the window are known.
 *
 * ----------------------------------------------------------------------
 */
static int
ConfigureVirtualOp(graphPtr, argc, argv)
    Graph *graphPtr;
    int argc;
    char *argv[];
{
    Axis *axisPtr;
    int nNames, nOpts;
    char **options;
    register int i;

    /* Figure out where the option value pairs begin */
    argc -= 3;
    argv += 3;
    for (i = 0; i < argc; i++) {
	if (argv[i][0] == '-') {
	    break;
	}
	if (NameToAxis(graphPtr, argv[i], &axisPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
    }
    nNames = i;			/* Number of pen names specified */
    nOpts = argc - i;		/* Number of options specified */
    options = argv + i;		/* Start of options in argv  */

    for (i = 0; i < nNames; i++) {
	if (NameToAxis(graphPtr, argv[i], &axisPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	if (ConfigureOp(graphPtr, axisPtr, nOpts, options) != TCL_OK) {
	    break;
	}
    }
    if (i < nNames) {
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * DeleteVirtualOp --
 *
 *	Deletes one or more axes.  The actual removal may be deferred
 *	until the axis is no longer used by any element. The axis
 *	can't be referenced by its name any longer and it may be
 *	recreated.
 *
 * Results:
 *	Returns a standard Tcl result.
 *
 * ----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
DeleteVirtualOp(graphPtr, argc, argv)
    Graph *graphPtr;
    int argc;
    char **argv;
{
    register int i;
    Axis *axisPtr;

    for (i = 3; i < argc; i++) {
	if (NameToAxis(graphPtr, argv[i], &axisPtr) != TCL_OK) {
	    return TCL_ERROR;
	}
	axisPtr->deletePending = TRUE;
	if (axisPtr->refCount == 0) {
	    DestroyAxis(graphPtr, axisPtr);
	}
    }
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * InvTransformVirtualOp --
 *
 *	Maps the given window coordinate into an axis-value.
 *
 * Results:
 *	Returns a standard Tcl result.  interp->result contains
 *	the axis value. If an error occurred, TCL_ERROR is returned
 *	and interp->result will contain an error message.
 *
 * ----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
InvTransformVirtualOp(graphPtr, argc, argv)
    Graph *graphPtr;
    int argc;			/* Not used. */
    char **argv;
{
    Axis *axisPtr;

    if (NameToAxis(graphPtr, argv[3], &axisPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    return InvTransformOp(graphPtr, axisPtr, argc - 4, argv + 4);
}

/*
 *--------------------------------------------------------------
 *
 * LimitsVirtualOp --
 *
 *	This procedure returns a string representing the axis limits
 *	of the graph.  The format of the string is { left top right bottom}.
 *
 * Results:
 *	Always returns TCL_OK.  The interp->result field is
 *	a list of the graph axis limits.
 *
 *--------------------------------------------------------------
 */
/*ARGSUSED*/
static int
LimitsVirtualOp(graphPtr, argc, argv)
    Graph *graphPtr;
    int argc;			/* Not used. */
    char **argv;		/* Not used. */

{
    Axis *axisPtr;

    if (NameToAxis(graphPtr, argv[3], &axisPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    return LimitsOp(graphPtr, axisPtr, argc - 4, argv + 4);
}

/*
 * ----------------------------------------------------------------------
 *
 * NamesVirtualOp --
 *
 *	Return a list of the names of all the axes.
 *
 * Results:
 *	Returns a standard Tcl result.
 *
 * ----------------------------------------------------------------------
 */

/*ARGSUSED*/
static int
NamesVirtualOp(graphPtr, argc, argv)
    Graph *graphPtr;
    int argc;			/* Not used. */
    char **argv;		/* Not used. */
{
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch cursor;
    Axis *axisPtr;
    register int i;

    for (hPtr = Tcl_FirstHashEntry(&(graphPtr->axisTable), &cursor);
	hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	axisPtr = (Axis *)Tcl_GetHashValue(hPtr);
	if (axisPtr->deletePending) {
	    continue;
	}
	if (argc == 3) {
	    Tcl_AppendElement(graphPtr->interp, axisPtr->name);
	    continue;
	}
	for (i = 3; i < argc; i++) {
	    if (Tcl_StringMatch(axisPtr->name, argv[i])) {
		Tcl_AppendElement(graphPtr->interp, axisPtr->name);
		break;
	    }
	}
    }
    return TCL_OK;
}

/*
 * ----------------------------------------------------------------------
 *
 * TransformVirtualOp --
 *
 *	Maps the given axis-value to a window coordinate.
 *
 * Results:
 *	Returns a standard Tcl result.  interp->result contains
 *	the window coordinate. If an error occurred, TCL_ERROR
 *	is returned and interp->result will contain an error
 *	message.
 *
 * ----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
TransformVirtualOp(graphPtr, argc, argv)
    Graph *graphPtr;
    int argc;			/* Not used. */
    char **argv;
{
    Axis *axisPtr;

    if (NameToAxis(graphPtr, argv[3], &axisPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    return TransformOp(graphPtr, axisPtr, argc - 4, argv + 4);
}

static int
ViewOp(graphPtr, argc, argv)
    Graph *graphPtr;
    int argc;
    char **argv;
{
    Tcl_Interp *interp = graphPtr->interp;
    double width, worldWidth;
    double axisOffset, scrollUnits;
    double fract;
    Axis *axisPtr;
    double min, max, dataMin, dataMax;

    if (NameToAxis(graphPtr, argv[3], &axisPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    dataMin = axisPtr->dataRange.min;
    dataMax = axisPtr->dataRange.max;
    min = axisPtr->min;
    max = axisPtr->max;
    if (axisPtr->logScale) {
	dataMin = log10(dataMin);
	dataMax = log10(dataMax);
	min = log10(min);
	max = log10(max);
    }
    worldWidth = dataMax - dataMin;
    width = max - min;

    /* Unlike horizontal axes, vertical axis values run opposite of
     * the scrollbar first/last values.  So instead of pushing the
     * axis minimum around, we move the maximum instead. */

    if (AxisIsHoriz(graphPtr, axisPtr) != axisPtr->descending) {
	axisOffset = min - dataMin;
	scrollUnits = (double)axisPtr->scrollUnits / (double)graphPtr->hRange;
    } else {
	axisOffset = dataMax - max;
	scrollUnits = (double)axisPtr->scrollUnits / (double)graphPtr->vRange;
    }
    if (argc == 4) {
	/* Note: Bound the fractions between 0.0 and 1.0 to support
	 * "canvas"-style scrolling. */
	fract = axisOffset / worldWidth;
	Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0)));
	fract = (axisOffset + width) / worldWidth;
	Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0)));
	return TCL_OK;
    }
    fract = axisOffset / worldWidth;
    if (GetAxisScrollInfo(interp, argc - 4, argv + 4, &fract, 
		width / worldWidth, scrollUnits) != TCL_OK) {
	return TCL_ERROR;
    }
    if (AxisIsHoriz(graphPtr, axisPtr) != axisPtr->descending) {
	axisPtr->min = (fract * worldWidth) + dataMin;
	axisPtr->max = axisPtr->min + width;
    } else {
	axisPtr->max = dataMax - (fract * worldWidth);
	axisPtr->min = axisPtr->max - width;
    }
    if (axisPtr->logScale) {
	axisPtr->min = EXP10(axisPtr->min);
	axisPtr->max = EXP10(axisPtr->max);
    }
    graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED | RESET_AXES);
    Blt_EventuallyRedrawGraph(graphPtr);
    return TCL_OK;
}

int
Blt_VirtualAxisOp(graphPtr, interp, argc, argv)
    Graph *graphPtr;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    Blt_OpProc opProc;
    int result;
    static Blt_OpSpec axisOps[] =
    {
	{"cget", 2, (Blt_OpProc)CgetVirtualOp, 5, 5, "axisName option",},
	{"configure", 2, (Blt_OpProc)ConfigureVirtualOp, 4, 0,
	    "axisName ?axisName?... ?option value?...",},
	{"create", 2, (Blt_OpProc)CreateVirtualOp, 4, 0,
	    "axisName ?option value?...",},
	{"delete", 1, (Blt_OpProc)DeleteVirtualOp, 3, 0, "?axisName?...",},
	{"invtransform", 1, (Blt_OpProc)InvTransformVirtualOp, 5, 5,
	    "axisName value",},
	{"limits", 1, (Blt_OpProc)LimitsVirtualOp, 4, 4, "axisName",},
	{"names", 1, (Blt_OpProc)NamesVirtualOp, 3, 0, "?pattern?...",},
	{"transform", 1, (Blt_OpProc)TransformVirtualOp, 5, 5, "axisName value",},
	{"view", 1, (Blt_OpProc)ViewOp, 4, 7,
	    "axisName ?moveto fract? ?scroll number what?",},
    };
    static int nAxisOps = sizeof(axisOps) / sizeof(Blt_OpSpec);

    opProc = Blt_GetOperation(interp, nAxisOps, axisOps, BLT_OPER_ARG2,
	argc, argv, 0);
    if (opProc == NULL) {
	return TCL_ERROR;
    }
    result = (*opProc) (graphPtr, argc, argv);
    return result;
}

int
Blt_AxisOp(graphPtr, margin, argc, argv)
    Graph *graphPtr;
    int margin;
    int argc;
    char **argv;
{
    int result;
    Blt_OpProc opProc;
    Axis *axisPtr;
    static Blt_OpSpec axisOps[] =
    {
	{"cget", 2, (Blt_OpProc)CgetOp, 4, 4, "option",},
	{"configure", 2, (Blt_OpProc)ConfigureOp, 3, 0, "?option value?...",},
	{"invtransform", 1, (Blt_OpProc)InvTransformOp, 4, 4, "value",},
	{"limits", 1, (Blt_OpProc)LimitsOp, 3, 3, "",},
	{"transform", 1, (Blt_OpProc)TransformOp, 4, 4, "value",},
	{"use", 1, (Blt_OpProc)UseOp, 3, 4, "?axisName?",},
    };
    static int nAxisOps = sizeof(axisOps) / sizeof(Blt_OpSpec);

    opProc = Blt_GetOperation(graphPtr->interp, nAxisOps, axisOps,
	BLT_OPER_ARG2, argc, argv, 0);
    if (opProc == NULL) {
	return TCL_ERROR;
    }
    argv[2] = (char *)margin; /* Hack. Slide a reference to the margin in 
			       * the argument list. Needed only for UseOp.
			       */
    axisPtr = Blt_GetFirstAxis(graphPtr->margins[margin].chainPtr);
    result = (*opProc)(graphPtr, axisPtr, argc - 3, argv + 3);
    return result;
}

void
Blt_MapAxes(graphPtr)
    Graph *graphPtr;
{
    Axis *axisPtr;
    Blt_Chain *chainPtr;
    Blt_ChainLink *linkPtr;
    register int margin;
    int offset;
    
    for (margin = 0; margin < 4; margin++) {
	chainPtr = graphPtr->margins[margin].chainPtr;
	offset = 0;
	for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr != NULL;
	     linkPtr = Blt_ChainNextLink(linkPtr)) {
	    axisPtr = (Axis *)Blt_ChainGetValue(linkPtr);
	    if (axisPtr->hidden) {
		continue;
	    }
	    MapAxis(graphPtr, axisPtr, offset, margin);
	    if (AxisIsHoriz(graphPtr, axisPtr)) {
		offset += axisPtr->height;
	    } else {
		offset += axisPtr->width;
	    }
	}
    }
}

void
Blt_DrawAxes(graphPtr, drawable)
    Graph *graphPtr;
    Drawable drawable;
{
    Axis *axisPtr;
    Blt_Chain *chainPtr;
    Blt_ChainLink *linkPtr;
    register int i;

    for (i = 0; i < 4; i++) {
	chainPtr = graphPtr->margins[i].chainPtr;
	for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr != NULL;
	     linkPtr = Blt_ChainNextLink(linkPtr)) {
	    axisPtr = (Axis *)Blt_ChainGetValue(linkPtr);
	    if (!axisPtr->hidden) {
		DrawAxis(graphPtr, drawable, axisPtr);
	    }
	}
    }
}

void
Blt_PrintAxes(graphPtr, printable)
    Graph *graphPtr;
    Printable printable;
{
    Axis *axisPtr;
    Blt_Chain *chainPtr;
    Blt_ChainLink *linkPtr;
    register int i;

    for (i = 0; i < 4; i++) {
	chainPtr = graphPtr->margins[i].chainPtr;
	for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr != NULL;
	     linkPtr = Blt_ChainNextLink(linkPtr)) {
	    axisPtr = (Axis *)Blt_ChainGetValue(linkPtr);
	    if (!axisPtr->hidden) {
		PrintAxis(printable, axisPtr);
	    }
	}
    }
}


/*
 * ----------------------------------------------------------------------
 *
 * Blt_DrawAxisLimits --
 *
 *	Draws the min/max values of the axis in the plotting area. 
 *	The text strings are formatted according to the "sprintf"
 *	format descriptors in the limitsFormats array.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Draws the numeric values of the axis limits into the outer
 *	regions of the plotting area.
 *
 * ----------------------------------------------------------------------
 */
void
Blt_DrawAxisLimits(graphPtr, drawable)
    Graph *graphPtr;
    Drawable drawable;
{
    Axis *axisPtr;
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch cursor;
    Dim2D textDim;
    int horizontal;
    char *minPtr, *maxPtr;
    char *minFormat, *maxFormat;
    char minString[200], maxString[200];
    int vMin, hMin, vMax, hMax;

#define SPACING 8
    vMin = vMax = graphPtr->left + graphPtr->padLeft + 2;
    hMin = hMax = graphPtr->bottom - graphPtr->padBottom - 2;	/* Offsets */

    for (hPtr = Tcl_FirstHashEntry(&(graphPtr->axisTable), &cursor);
	hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	axisPtr = (Axis *)Tcl_GetHashValue(hPtr);

	if (axisPtr->nFormats == 0) {
	    continue;
	}
	horizontal = AxisIsHoriz(graphPtr, axisPtr);
	minPtr = maxPtr = NULL;
	minFormat = maxFormat = axisPtr->limitsFormats[0];
	if (axisPtr->nFormats > 1) {
	    maxFormat = axisPtr->limitsFormats[1];
	}
	if (minFormat[0] != '\0') {
	    minPtr = minString;
	    sprintf(minString, minFormat, axisPtr->tickRange.min);
	}
	if (maxFormat[0] != '\0') {
	    maxPtr = maxString;
	    sprintf(maxString, maxFormat, axisPtr->tickRange.max);
	}
	if (axisPtr->descending) {
	    char *tmp;

	    tmp = minPtr, minPtr = maxPtr, maxPtr = tmp;
	}
	if (maxPtr != NULL) {
	    if (horizontal) {
		axisPtr->limitsStyle.theta = 90.0;
		axisPtr->limitsStyle.anchor = TK_ANCHOR_SE;
		Blt_DrawText2(graphPtr->tkwin, drawable, maxPtr,
		    &(axisPtr->limitsStyle), graphPtr->right, hMax, &textDim);
		hMax -= (textDim.height + SPACING);
	    } else {
		axisPtr->limitsStyle.theta = 0.0;
		axisPtr->limitsStyle.anchor = TK_ANCHOR_NW;
		Blt_DrawText2(graphPtr->tkwin, drawable, maxPtr,
		    &(axisPtr->limitsStyle), vMax, graphPtr->top, &textDim);
		vMax += (textDim.width + SPACING);
	    }
	}
	if (minPtr != NULL) {
	    axisPtr->limitsStyle.anchor = TK_ANCHOR_SW;
	    if (horizontal) {
		axisPtr->limitsStyle.theta = 90.0;
		Blt_DrawText2(graphPtr->tkwin, drawable, minPtr,
		    &(axisPtr->limitsStyle), graphPtr->left, hMin, &textDim);
		hMin -= (textDim.height + SPACING);
	    } else {
		axisPtr->limitsStyle.theta = 0.0;
		Blt_DrawText2(graphPtr->tkwin, drawable, minPtr,
		    &(axisPtr->limitsStyle), vMin, graphPtr->bottom, &textDim);
		vMin += (textDim.width + SPACING);
	    }
	}
    }				/* Loop on axes */
}

void
Blt_PrintAxisLimits(graphPtr, printable)
    Graph *graphPtr;
    Printable printable;
{
    Axis *axisPtr;
    Tcl_HashEntry *hPtr;
    Tcl_HashSearch cursor;
    int vMin, hMin, vMax, hMax;
    char string[200];
    int textWidth, textHeight;
    char *minFmt, *maxFmt;

#define SPACING 8
    vMin = vMax = graphPtr->left + graphPtr->padLeft + 2;
    hMin = hMax = graphPtr->bottom - graphPtr->padBottom - 2;	/* Offsets */
    for (hPtr = Tcl_FirstHashEntry(&(graphPtr->axisTable), &cursor);
	hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
	axisPtr = (Axis *)Tcl_GetHashValue(hPtr);

	if (axisPtr->nFormats == 0) {
	    continue;
	}
	minFmt = maxFmt = axisPtr->limitsFormats[0];
	if (axisPtr->nFormats > 1) {
	    maxFmt = axisPtr->limitsFormats[1];
	}
	if (*maxFmt != '\0') {
	    sprintf(string, maxFmt, axisPtr->tickRange.max);
	    Blt_GetTextExtents(&(axisPtr->tickStyle), string, &textWidth,
		&textHeight);
	    if ((textWidth > 0) && (textHeight > 0)) {
		if (axisPtr->type == AXIS_TYPE_X) {
		    axisPtr->limitsStyle.theta = 90.0;
		    axisPtr->limitsStyle.anchor = TK_ANCHOR_SE;
		    Blt_PrintText(printable, string, &(axisPtr->limitsStyle),
			graphPtr->right, hMax);
		    hMax -= (textWidth + SPACING);
		} else {
		    axisPtr->limitsStyle.theta = 0.0;
		    axisPtr->limitsStyle.anchor = TK_ANCHOR_NW;
		    Blt_PrintText(printable, string, &(axisPtr->limitsStyle),
			vMax, graphPtr->top);
		    vMax += (textWidth + SPACING);
		}
	    }
	}
	if (*minFmt != '\0') {
	    sprintf(string, minFmt, axisPtr->tickRange.min);
	    Blt_GetTextExtents(&(axisPtr->tickStyle), string, &textWidth,
		&textHeight);
	    if ((textWidth > 0) && (textHeight > 0)) {
		axisPtr->limitsStyle.anchor = TK_ANCHOR_SW;
		if (axisPtr->type == AXIS_TYPE_X) {
		    axisPtr->limitsStyle.theta = 90.0;
		    Blt_PrintText(printable, string, &(axisPtr->limitsStyle),
			graphPtr->left, hMin);
		    hMin -= (textWidth + SPACING);
		} else {
		    axisPtr->limitsStyle.theta = 0.0;
		    Blt_PrintText(printable, string, &(axisPtr->limitsStyle),
			vMin, graphPtr->bottom);
		    vMin += (textWidth + SPACING);
		}
	    }
	}
    }
}

Axis *
Blt_GetFirstAxis(chainPtr)
    Blt_Chain *chainPtr;
{
    Blt_ChainLink *linkPtr;

    linkPtr = Blt_ChainFirstLink(chainPtr);
    if (linkPtr == NULL) {
	return NULL;
    }
    return (Axis *)Blt_ChainGetValue(linkPtr);
}

