/*
 * bltGrElem.c --
 *
 *	This module implements a elements in the graph widget
 *	for the Tk toolkit.
 *
 * Copyright 1991-1994 by AT&T Bell Laboratories.
 * 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 AT&T Bell Laboratories any of their entities not be used
 * in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 *
 * AT&T disclaims all warranties with regard to this software, including
 * all implied warranties of merchantability and fitness.  In no event
 * shall AT&T 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 "blt.h"
#include "bltGraph.h"
#include <ctype.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>


/*
 * Sun's bundled and unbundled C compilers can't grok static function
 * typedefs (it can handle extern) like
 *
 * 	static Tk_OptionParseProc parseProc;
 *  	static Tk_OptionPrintProc printProc;
 *
 * Provide forward declarations here:
*/
static int ParseSymbolType _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *value, char *widgRec,
	int offset));
static char *PrintSymbolType _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtr));
static int ParseVector _ANSI_ARGS_((ClientData clientData, Tcl_Interp *interp,
	Tk_Window tkwin, char *value, char *widgRec, int offset));
static char *PrintVector _ANSI_ARGS_((ClientData clientData, Tk_Window tkwin,
	char *widgRec, int offset, Tcl_FreeProc **freeProcPtr));
static int ParseCoordPairs _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *value, char *widgRec,
	int offset));
static char *PrintCoordPairs _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtr));
static int ParseColorList _ANSI_ARGS_((ClientData clientData,
	Tcl_Interp *interp, Tk_Window tkwin, char *value, char *widgRec,
	int offset));
static char *PrintColorList _ANSI_ARGS_((ClientData clientData,
	Tk_Window tkwin, char *widgRec, int offset,
	Tcl_FreeProc **freeProcPtr));
extern int Blt_ParseAxisFlags _ANSI_ARGS_((ClientData, Tcl_Interp *, Tk_Window,
	char *, char *, int));
extern char *Blt_PrintAxisFlags _ANSI_ARGS_((ClientData, Tk_Window, char *, int,
	Tcl_FreeProc **));

#include "bltGrElem.h"

#define DEF_VECTOR_SIZE	32	/* Starting size of a data vector. */
enum DataModes {
    CopyData, AppendData
};
enum DataTypes {
    SingleVector = 1, TwinVectors = 2
};

/*
 * Element attributes: either symbol types or line styles
*/
static char *lineSymbolNames[] =
{
    "Line", "Square", "Circle", "Diamond", "Plus", "Cross", "Splus", "Scross",
};
static int numSymbols = sizeof(lineSymbolNames) / sizeof(char *);

typedef struct {
    SymbolType type;
    Display *display;		/* Display on which to draw symbol */
    Drawable canvas;		/* Window to draw symbol */
    GC lineGC;
    GC borderGC;
    GC fillGC;
    int radius;
    int size;
    XPoint offset[13];

} SymbolInfo;


typedef struct {
    XPoint *pointPtr;		/* Array of points defining the segment */
    int numPoints;		/* Number of points in segment */
} LineInfo;

typedef struct {
    Tcl_Interp *interp;		/* Interpreter of the graph widget.
				 * This is only needed for
				 * Tcl_PrintDouble calls in the custom
				 * configure print routines. */
    ElementClassType type;	/* Type of element is LINE_ELEMENT */
    unsigned int flags;
    Tk_Uid id;			/* Identifier to refer the element.
				 * Used in the "insert", "delete", or
				 * "show", commands. */
    int mapped;			/* If non-zero, element is currently
				 * visible.*/
    Tk_ConfigSpec *configSpecs;	/* Configuration specifications */
    char *label;		/* Label displayed in legend */
    SymbolType symbol;		/* Element symbol type */
    double symbolScale;		/* Scale factor when computing symbol
				 * size */
    int symbolSize;		/* Computed size of symbol in pixels. */
    Vector x, y;		/* Contains array of numeric values */
    unsigned int axisFlags;	/* Indicates which axes to map element's
				 * coordinates onto */

    ElemConfigProc *configProc;
    ElemDestroyProc *destroyProc;
    ElemDisplayProc *displayProc;
    ElemLimitsProc *limitsProc;
    ElemDistanceProc *distProc;
    ElemLayoutProc *layoutProc;
    ElemPrintProc *printProc;
    ElemDrawSymbolsProc *drawSymbolsProc;
    ElemPrintSymbolsProc *printSymbolsProc;
    /*
     * Line specific configurable attributes
     */
    int active;			/* If non-zero, indicates element is
				 * active.  Active indicates which
				 * color/GCs to use when displaying
				 * the element's data points */
    int *activePoints;		/* Array of indices of active data
				 * points. If NULL, all data points
				 * are active */
    int numActivePoints;

    XColor *normalFg;		/* normal color of lines/borders*/
    XColor *normalBg;		/* normal color of dashed lines, fills */
    XColor *activeFg;		/* active color of lines/borders */
    XColor *activeBg;		/* active color of dashed lines, fills */

    int lineWidth;		/* Line width */
    int dashes;			/* Dash on-off list value */
    int noretrace;		/* If non-zero, break connected line
				 * segments where x-coordinate values
				 * are not monotonically increasing. */
    GC normalBorderGC;		/* Symbol border graphics context */
    GC normalFillGC;		/* Symbol fill graphics context */
    GC normalLineGC;		/* Line graphics context */

    GC activeLineGC;
    GC activeFillGC;
    GC activeBorderGC;
    /*
     * Drawing related structures
     */
    XPoint *pointArr;		/* Array of window coordinates
				 * representing the points of the line
				 * (malloc'ed) */
    int numPoints;		/* Number of points in point array */
    LineInfo *segArr;		/* Array of line segment information
				 * (malloc'ed) */
    int numSegments;		/* Number of line segments in point
				 * array */

} LineElement;

static Tk_CustomOption SymbolOption =
{
    ParseSymbolType, PrintSymbolType, (ClientData)0
};

static Tk_CustomOption XVectorOption =
{
    ParseVector, PrintVector, (ClientData)AXIS_X_MASK
};
static Tk_CustomOption YVectorOption =
{
    ParseVector, PrintVector, (ClientData)AXIS_Y_MASK
};

static Tk_CustomOption TwinOption =
{
    ParseCoordPairs, PrintCoordPairs, (ClientData)0
};

static Tk_CustomOption ColorListOption =
{
    ParseColorList, PrintColorList, (ClientData)0
};

static Tk_CustomOption XAxisFlagsOption =
{
    Blt_ParseAxisFlags, Blt_PrintAxisFlags, (ClientData)AXIS_X_MASK
};

static Tk_CustomOption YAxisFlagsOption =
{
    Blt_ParseAxisFlags, Blt_PrintAxisFlags, (ClientData)AXIS_Y_MASK
};

#define DEF_ELEM_BG_COLOR	WHITE
#define DEF_ELEM_FG_COLOR     	"navyblue"
#define DEF_ELEM_ACTIVE_BG_COLOR "pink"
#define DEF_ELEM_ACTIVE_FG_COLOR "red"
#define DEF_ELEM_BG_MONO	WHITE
#define DEF_ELEM_FG_MONO	BLACK
#define DEF_ELEM_ACTIVE_BG_MONO	BLACK
#define DEF_ELEM_ACTIVE_FG_MONO WHITE
#define DEF_ELEM_BORDERWIDTH	"2"
#define DEF_ELEM_X_AXIS		"x"
#define DEF_ELEM_Y_AXIS		"y"

static Tk_ConfigSpec lineConfigSpecs[] =
{
    {TK_CONFIG_COLOR, "-activebackground",
	"lineElemActiveBackground", "ElemBackground",
	DEF_ELEM_ACTIVE_BG_COLOR, Tk_Offset(LineElement, activeBg),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-activebackground",
	"lineElemActiveBackground", "ElemBackground",
	DEF_ELEM_ACTIVE_BG_MONO, Tk_Offset(LineElement, activeBg),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_COLOR, "-activeforeground",
	"lineElemActiveForeground", "ElemForeground",
	DEF_ELEM_ACTIVE_FG_COLOR, Tk_Offset(LineElement, activeFg),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-activeforeground",
	"lineElemActiveForeground", "ElemForeground",
	DEF_ELEM_ACTIVE_FG_MONO, Tk_Offset(LineElement, activeFg),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_COLOR, "-background", "lineElemBackground", "ElemBackground",
	DEF_ELEM_BG_COLOR, Tk_Offset(LineElement, normalBg),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-background", "lineElemBackground", "ElemBackground",
	DEF_ELEM_BG_MONO, Tk_Offset(LineElement, normalBg),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_SYNONYM, "-bg", "lineElemBackground", (char *)NULL,
	(char *)NULL, 0, 0},
    {TK_CONFIG_INT, "-dashes", "lineElemDashes", "ElemDashes",
	(char *)NULL, Tk_Offset(LineElement, dashes), 0},
    {TK_CONFIG_CUSTOM, "-data", "lineElemData", "ElemData",
	(char *)NULL, 0, 0, &TwinOption},
    {TK_CONFIG_SYNONYM, "-fg", "lineElemForeground", (char *)NULL,
	(char *)NULL, 0, TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_SYNONYM, "-fg", "lineElemForeground", (char *)NULL,
	(char *)NULL, 0, 0},
    {TK_CONFIG_COLOR, "-foreground", "lineElemForeground", "ElemForeground",
	DEF_ELEM_FG_COLOR, Tk_Offset(LineElement, normalFg),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-foreground", "lineElemForeground", "ElemForeground",
	DEF_ELEM_FG_MONO, Tk_Offset(LineElement, normalFg),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_PIXELS, "-linewidth", "lineElemLinewidth", "ElemLinewidth",
	(char *)NULL, Tk_Offset(LineElement, lineWidth), 0},
    {TK_CONFIG_CUSTOM, "-mapx", "lineElemMapX", "ElemMapX",
	DEF_ELEM_X_AXIS, Tk_Offset(LineElement, axisFlags),
	TK_CONFIG_DONT_SET_DEFAULT, &XAxisFlagsOption},
    {TK_CONFIG_CUSTOM, "-mapy", "lineElemMapY", "ElemMapY",
	DEF_ELEM_Y_AXIS, Tk_Offset(LineElement, axisFlags),
	TK_CONFIG_DONT_SET_DEFAULT, &YAxisFlagsOption},
    {TK_CONFIG_DOUBLE, "-scale", "lineElemScale", "ElemScale",
	(char *)NULL, Tk_Offset(LineElement, symbolScale), 0},
    {TK_CONFIG_CUSTOM, "-symbol", "lineElemSymbol", "ElemSymbol",
	(char *)NULL, Tk_Offset(LineElement, symbol), 0, &SymbolOption},
    {TK_CONFIG_CUSTOM, "-xdata", "lineElemXdata", "ElemXdata",
	(char *)NULL, 0, 0, &XVectorOption},
    {TK_CONFIG_CUSTOM, "-ydata", "lineElemYdata", "ElemYdata",
	(char *)NULL, 0, 0, &YVectorOption},
    {TK_CONFIG_STRING, "-label", "lineElemLabel", "ElemLabel",
	(char *)NULL, Tk_Offset(LineElement, label), TK_CONFIG_NULL_OK},
    {TK_CONFIG_BOOLEAN, "-noretrace", "lineElemNoretrace", "ElemNoretrace",
	(char *)NULL, Tk_Offset(LineElement, noretrace), 0},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};

typedef struct {
    Tcl_Interp *interp;		/* Interpreter of the graph widget */
    ElementClassType type;	/* Type of element is BAR_ELEMENT */
    unsigned int flags;
    Tk_Uid id;			/* Identifier to refer the element.
				 * Used in the "insert", "delete", or
				 * "show", commands. */
    int mapped;			/* If non-zero, element is currently
				 * visible.*/
    Tk_ConfigSpec *configSpecs;	/* Configuration specifications */
    char *label;		/* Label displayed in legend */
    SymbolType symbol;		/* Element symbol type */
    double symbolScale;		/* Size of symbol as a percentage of
				 * the drawing area. */
    int symbolSize;		/* Computed size of symbol in pixels. */
    Vector x, y;		/* Contains array of numeric values */
    unsigned int axisFlags;	/* Indicates which axes to map
				 * element's coordinates onto */

    ElemConfigProc *configProc;
    ElemDestroyProc *destroyProc;
    ElemDisplayProc *displayProc;
    ElemLimitsProc *limitsProc;
    ElemDistanceProc *distProc;
    ElemLayoutProc *layoutProc;
    ElemPrintProc *printProc;
    ElemDrawSymbolsProc *drawSymbolsProc;
    ElemPrintSymbolsProc *printSymbolsProc;
    /*
     * Bar specific attributes
     */
    Tk_3DBorder border;		/* 3D border and background color */
    int borderWidth;		/* 3D border width of bar */
    int relief;			/* Relief of bar */
    XColor **fgColorArr;	/* Array of colors  */
    int numFgColors;		/* Number of colors in color array */
    GC *gcArr;			/* Array of shared Graphics Context
				 * pointers */
    int numGCs;			/* Number of GC's in array */
    int stacked;		/* If non-zero, the bar y-values
				 * represent a set of stacked bar
				 * segments */
    Pixmap stipple;		/* Bitmap representing stippled
				 * pattern of bar. If None, indicates
				 * a solid fill. */
    int padX;			/* Spacing on either side of bar */
    XRectangle *rectArr;	/* Array of rectangle coordinates
				 * composing the set of a bars
				 * possible */
    int numRects;		/* Number of bars in the set */

} BarElement;

static Tk_ConfigSpec barConfigSpecs[] =
{
    {TK_CONFIG_BORDER, "-background", "barElemBackground", "ElemBackground",
	DEF_ELEM_BG_COLOR, Tk_Offset(BarElement, border),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_BORDER, "-background", "barElemBackground", "ElemBackground",
	DEF_ELEM_BG_COLOR, Tk_Offset(BarElement, border),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_SYNONYM, "-bd", "barElemBorderwidth", (char *)NULL,
	(char *)NULL, 0, 0},
    {TK_CONFIG_SYNONYM, "-bg", "barElemBackground", (char *)NULL,
	(char *)NULL, 0, 0},
    {TK_CONFIG_PIXELS, "-borderwidth", "barElemBorderwidth",
	"ElemBorderwidth",
	DEF_ELEM_BORDERWIDTH, Tk_Offset(BarElement, borderWidth), 0},
    {TK_CONFIG_SYNONYM, "-fg", "barElemForeground", (char *)NULL,
	(char *)NULL, 0, 0},
    {TK_CONFIG_CUSTOM, "-data", "barElemData", "ElemData",
	(char *)NULL, 0, 0, &TwinOption},
    {TK_CONFIG_CUSTOM, "-foreground", "barElemForeground", "ElemForeground",
	DEF_ELEM_FG_COLOR, Tk_Offset(BarElement, fgColorArr),
	TK_CONFIG_COLOR_ONLY, &ColorListOption},
    {TK_CONFIG_CUSTOM, "-foreground", "barElemForeground", "ElemForeground",
	DEF_ELEM_FG_COLOR, Tk_Offset(BarElement, fgColorArr),
	TK_CONFIG_MONO_ONLY, &ColorListOption},
    {TK_CONFIG_STRING, "-label", "barElemLabel", "ElemLabel",
	(char *)NULL, Tk_Offset(BarElement, label), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-mapx", "barElemMapX", "ElemMapX",
	DEF_ELEM_X_AXIS, Tk_Offset(BarElement, axisFlags),
	TK_CONFIG_DONT_SET_DEFAULT, &XAxisFlagsOption},
    {TK_CONFIG_CUSTOM, "-mapy", "barElemMapY", "ElemMapY",
	DEF_ELEM_Y_AXIS, Tk_Offset(BarElement, axisFlags),
	TK_CONFIG_DONT_SET_DEFAULT, &YAxisFlagsOption},
    {TK_CONFIG_RELIEF, "-relief", "barElemRelief", "ElemRelief",
	(char *)NULL, Tk_Offset(BarElement, relief), 0},
    {TK_CONFIG_BOOLEAN, "-stacked", "barElemStacked", "ElemStacked",
	(char *)NULL, Tk_Offset(BarElement, stacked), 0},
    {TK_CONFIG_BITMAP, "-stipple", "barElemStipple", "ElemStipple",
	(char *)NULL, Tk_Offset(BarElement, stipple), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-xdata", "barElemXdata", "ElemXdata",
	(char *)NULL, 0, 0, &XVectorOption},
    {TK_CONFIG_CUSTOM, "-ydata", "barElemYdata", "ElemYdata",
	(char *)NULL, 0, 0, &YVectorOption},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};

/* Forward declarations */
extern int Blt_OptionChanged _ANSI_ARGS_((Tk_ConfigSpec *, int *, int));
extern int strncasecmp _ANSI_ARGS_((CONST char *s1, CONST char *s2, size_t n));

/* ----------------------------------------------------------------------
 * Custom option parse and print procedures
 * ----------------------------------------------------------------------
 */
/*
 *----------------------------------------------------------------------
 *
 * ParseSymbolType --
 *
 *	Convert the string representation of a line style or symbol name
 *	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
ParseSymbolType(clientData, interp, tkwin, value, widgRec, offset)
    ClientData clientData;	/* not used */
    Tcl_Interp *interp;		/* Interpreter to send results back to */
    Tk_Window tkwin;		/* not used */
    char *value;		/* String representing symbol type */
    char *widgRec;		/* Element information record */
    int offset;			/* Offset of symbol type field in record */
{
    register char c;
    int *symbolPtr = (int *)(widgRec + offset);
    register int i;
    int length;

    length = strlen(value);
    c = toupper(*value);
    for (i = 0; i < numSymbols; i++) {
	if ((c == *lineSymbolNames[i]) &&
	    (strncasecmp(value, lineSymbolNames[i], length) == 0)) {
	    *symbolPtr = i;
	    return TCL_OK;
	}
    }
    Tcl_AppendResult(interp, "bad symbol name \"", value, "\"", (char *)NULL);
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * PrintSymbolType --
 *
 *	Convert the symbol value into a string.
 *
 * Results:
 *	The string representing the symbol type or line style is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
PrintSymbolType(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* not used */
    Tk_Window tkwin;		/* not used */
    char *widgRec;		/* Element information record */
    int offset;			/* Offset of symbol type field in record */
    Tcl_FreeProc **freeProcPtr;	/* not used */
{
    int symbol = *(int *)(widgRec + offset);

    if (symbol >= 0 || symbol < numSymbols) {
	return (lineSymbolNames[symbol]);
    }
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * ParseColorList --
 *
 *	Convert a list of color names in to an array of XColor pointers.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 * Side Effects:
 *	An array of color pointers in allocated and filled.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ParseColorList(clientData, interp, tkwin, value, widgRec, offset)
    ClientData clientData;	/* not used */
    Tcl_Interp *interp;		/* Interpreter to send results back to */
    Tk_Window tkwin;		/* not used */
    char *value;		/* String representing symbol type */
    char *widgRec;		/* Element information record */
    int offset;			/* Offset of symbol type field in record */
{
    BarElement *barElemPtr = (BarElement *)widgRec;
    register int i;
    int numColors;
    XColor *colorPtr;
    XColor **colorPtrPtr;
    char **colorNames;
    Colormap colorMap;
    Tk_Uid colorId;
    int lastColor;

    colorPtrPtr = NULL;
    colorNames = NULL;

    lastColor = 0;
    if (Tcl_SplitList(interp, value, &numColors, &colorNames) != TCL_OK) {
	return TCL_ERROR;
    }
    if (numColors < 1) {
	interp->result = "no colors specified";
	goto error;
    }
    colorPtrPtr = (XColor **)malloc(sizeof(XColor *) * numColors);
    if (colorPtrPtr == NULL) {
	goto error;
    }
    colorMap = Tk_Colormap(tkwin);
    for (i = 0; i < numColors; i++) {
	colorId = Tk_GetUid(colorNames[i]);
	colorPtr = Tk_GetColor(interp, tkwin, colorMap, colorId);
	if (colorPtr == NULL) {
	    lastColor = i;
	    goto error;
	}
	colorPtrPtr[i] = colorPtr;
    }
    free((char *)colorNames);

    /*  Release color resources from the old color list */
    for (i = 0; i < barElemPtr->numFgColors; i++) {
	colorPtr = barElemPtr->fgColorArr[i];
	Tk_FreeColor(colorPtr);
    }
    if (barElemPtr->fgColorArr != NULL) {
	free((char *)barElemPtr->fgColorArr);
    }
    barElemPtr->numFgColors = numColors;
    barElemPtr->fgColorArr = colorPtrPtr;

    return TCL_OK;

  error:
    for (i = 0; i < lastColor; i++) {
	Tk_FreeColor(colorPtrPtr[i]);
    }
    if (colorNames != NULL) {
	free((char *)colorNames);
    }
    if (colorPtrPtr != NULL) {
	free((char *)colorPtrPtr);
    }
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * PrintColorList --
 *
 *	Convert the array of color pointers into a list of color names.
 *
 * Results:
 *	The string representing the list of color names is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
PrintColorList(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Type of axis vector to print */
    Tk_Window tkwin;		/* not used */
    char *widgRec;		/* Bar element record */
    int offset;			/* not used */
    Tcl_FreeProc **freeProcPtr;	/* Memory deallocation scheme to use */
{
    BarElement *barElemPtr = (BarElement *)(widgRec);
    register int i;
    Tcl_DString buffer;
    char *result;
    char *colorName;

    Tcl_DStringInit(&buffer);
    for (i = 0; i < barElemPtr->numFgColors; i++) {
	colorName = Tk_NameOfColor(barElemPtr->fgColorArr[i]);
	Tcl_DStringAppendElement(&buffer, colorName);
    }
    result = Tcl_DStringValue(&buffer);
    result = strdup(result);
    *freeProcPtr = TCL_DYNAMIC;
    Tcl_DStringFree(&buffer);
    return (result);
}

/*
 *----------------------------------------------------------------------
 *
 * ConvertExpressions --
 *
 *	Converts strings from an array of numeric expressions into
 *	an array of double precision values.
 *
 * Results:
 *     	Returns TCL_OK if sucessful. Otherwise TCL_ERROR will be
 *	returned and interp->result will be filled with an error message.
 *
 *----------------------------------------------------------------------
 */
static int
ConvertExpressions(interp, exprArr, numExpr, dataArr, start, step)
    Tcl_Interp *interp;		/* Interpreter to report error back to */
    char *exprArr[];		/* Array of expression strings */
    int numExpr;		/* Number of expressions in array */
    double dataArr[];
    int start, step;		/* Starting point and step */
{
    register int i, count;
    double x;

    /*
     * Parse and evaluate the list of numeric expressions into an
     * array of floating point numbers.
     */
    for (count = 0, i = start; i < numExpr; i += step, count++) {
	if (Tcl_ExprDouble(interp, exprArr[i], &x) != TCL_OK) {
	    return TCL_ERROR;
	}
	dataArr[count] = x;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GetDataLimits --
 *
 *	Find the minimum, positive minumum, and maximum values in a
 *	given vector and store the results in the vector structure.
 *
 * Results:
 *     	None.
 *
 * Side Effects:
 *	Minimum, positive minimum, and maximum values are stored in
 *	the vector.
 *
 *----------------------------------------------------------------------
 */
static void
GetDataLimits(vecPtr)
    register Vector *vecPtr;
{
    register int i;

    /*
     * Find the minimum, positive minimum, and maximum values in the
     * array.
     */
    vecPtr->min = vecPtr->max = vecPtr->data[0];
    vecPtr->logMin = Blt_posInfinity;
    for (i = 0; i < vecPtr->length; i++) {
	if ((vecPtr->data[i] > 0.0) && (vecPtr->data[i] < vecPtr->logMin)) {
	    vecPtr->logMin = vecPtr->data[i];
	}
	if (vecPtr->data[i] < vecPtr->min) {
	    vecPtr->min = vecPtr->data[i];
	} else if (vecPtr->data[i] > vecPtr->max) {
	    vecPtr->max = vecPtr->data[i];
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * AppendVector --
 *
 *	Appends or overwrites a given vector with numeric expressions.
 *
 * Results:
 *     	A standard Tcl result.
 *
 * Side Effects:
 *	Memory is allocated for the new vector values.
 *
 *----------------------------------------------------------------------
 */
static int
AppendVector(interp, vecPtr, exprArr, numExpr, start, type, mode)
    Tcl_Interp *interp;		/* Interpreter to report error back to */
    Vector *vecPtr;		/* Vector to be filled */
    char *exprArr[];		/* Array of expression strings */
    int numExpr;		/* Number of expressions in array */
    int start;			/* Starting point */
    enum DataTypes type;	/* Type of data (pairs or single vector) */
    enum DataModes mode;	/* Append or overwrite current data */
{
    unsigned int arraySize;
    unsigned int needed;
    double *dataArr;
    unsigned int offset;
    int step;

    if (numExpr < 1) {
	if (vecPtr->data != NULL) {
	    free((char *)vecPtr->data);
	}
	vecPtr->data = NULL;
	vecPtr->size = 0;
	vecPtr->length = 0;
	return TCL_OK;
    }
    step = (int)type;
    offset = (mode == AppendData) ? vecPtr->length : 0;
    needed = (numExpr / step) + offset;

    /*
     * Keep doubling the size of the array until we fit the number
     * needed
     */
    arraySize = vecPtr->size;
    if (arraySize == 0) {
	arraySize = DEF_VECTOR_SIZE;
    }
    while (needed > arraySize) {
	arraySize += arraySize;
    }

    dataArr = (double *)calloc(arraySize, sizeof(double));
    if (dataArr == NULL) {
	interp->result = "can't allocate data vector";
	return TCL_ERROR;
    }
    if (offset > 0) {		/* Copy previous contents */
	memcpy((char *)dataArr, (char *)vecPtr->data, offset * sizeof(double));
    }
    if (ConvertExpressions(interp, exprArr, numExpr,
	    &(dataArr[offset]), start, step) != TCL_OK) {
	free((char *)dataArr);
	return TCL_ERROR;
    }
    if (vecPtr->data != NULL) {
	free((char *)vecPtr->data);
    }
    vecPtr->data = dataArr;
    vecPtr->length = needed;
    vecPtr->size = arraySize;
    GetDataLimits(vecPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ParseVector --
 *
 *	Given a Tcl list of numeric expression representing the element
 *	values, convert into an array of double precision values. In
 *	addition, the minimum and maximum values are saved.  Since
 *	elastic values are allow (values which translate to the
 *	min/max of the graph), we must try to get the non-elastic
 *	minimum and maximum.
 *
 * Results:
 *	The return value is a standard Tcl result.  The vector is passed
 *	back via the vecPtr.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ParseVector(clientData, interp, tkwin, value, widgRec, offset)
    ClientData clientData;	/* Type of axis vector to fill */
    Tcl_Interp *interp;		/* Interpreter to send results back to */
    Tk_Window tkwin;		/* not used */
    char *value;		/* Tcl list of expressions */
    char *widgRec;		/* Element record */
    int offset;			/* Offset of vector in Element record */
{
    Element *elemPtr = (Element *)(widgRec + offset);
    register Vector *vecPtr;
    int flags = (int)clientData;
    int numExprs;
    char **exprArr = NULL;

    vecPtr = (flags & AXIS_X_MASK) ? &(elemPtr->x) : &(elemPtr->y);

    /* Split the list of expressions and check the values */
    if (Tcl_SplitList(interp, value, &numExprs, &exprArr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (numExprs >= 65535) {
	interp->result = "vector is too large";	/* XDrawLines limit */
	goto error;
    }
    if (AppendVector(interp, vecPtr, exprArr, numExprs, 0, SingleVector,
	    CopyData) != TCL_OK) {
	goto error;
    }
    free((char *)exprArr);
    return TCL_OK;

  error:
    /* Clean up, release allocated storage */
    if (exprArr) {
	free((char *)exprArr);
    }
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * PrintVector --
 *
 *	Convert the vector of floating point values into a Tcl list.
 *
 * Results:
 *	The string representation of the vector is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
PrintVector(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* Type of axis vector to print */
    Tk_Window tkwin;		/* not used */
    char *widgRec;		/* Element record */
    int offset;			/* Offset of vector in Element record */
    Tcl_FreeProc **freeProcPtr;	/* Memory deallocation scheme to use */
{
    Element *elemPtr = (Element *)(widgRec + offset);
    register Vector *vecPtr;
    int flags = (int)clientData;
    register int i;
    char *result;
    Tcl_DString buffer;
    char string[TCL_DOUBLE_SPACE];

    vecPtr = (flags & AXIS_X_MASK) ? &(elemPtr->x) : &(elemPtr->y);
    if (vecPtr->length == 0) {
	return "";
    }
    Tcl_DStringInit(&buffer);
    for (i = 0; i < vecPtr->length; i++) {
	Tcl_PrintDouble(elemPtr->interp, vecPtr->data[i], string);
	Tcl_DStringAppendElement(&buffer, string);
    }
    result = Tcl_DStringValue(&buffer);
    result = strdup(result);
    *freeProcPtr = TCL_DYNAMIC;
    Tcl_DStringFree(&buffer);
    return (result);
}

/*
 *----------------------------------------------------------------------
 *
 * ParseCoordPairs --
 *
 *	This procedure is like ParseVector except that it
 *	interprets the list of numeric expressions as X Y coordinate
 *	pairs.  The minimum and maximum for both the X and Y vectors are
 *	determined.
 *
 * Results:
 *	The return value is a standard Tcl result.  The vectors are passed
 *	back via the widget record (elemPtr).
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ParseCoordPairs(clientData, interp, tkwin, value, widgRec, offset)
    ClientData clientData;	/* not used */
    Tcl_Interp *interp;		/* Interpreter to send results back to */
    Tk_Window tkwin;		/* not used */
    char *value;		/* Tcl list of numeric expressions */
    char *widgRec;		/* Element record */
    int offset;			/* not used */
{
    Element *elemPtr = (Element *)widgRec;
    int numExprs;
    char **exprArr = NULL;

    /* Split the list of numbers and check the values */
    if (Tcl_SplitList(interp, value, &numExprs, &exprArr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (numExprs >= 131070) {
	interp->result = "vector is too large";	/* XDrawLines limit */
	goto error;
    } else if (numExprs & 1) {
	interp->result = "odd number of values in -xydata option";
	goto error;
    }
    if (AppendVector(interp, &(elemPtr->x), exprArr, numExprs, 0,
	    TwinVectors, CopyData) != TCL_OK) {
	goto error;
    }
    if (AppendVector(interp, &(elemPtr->y), exprArr, numExprs, 1,
	    TwinVectors, CopyData) != TCL_OK) {
	goto error;
    }
    free((char *)exprArr);
    return TCL_OK;
  error:
    /* Clean up, release allocated storage */
    if (exprArr) {
	free((char *)exprArr);
    }
    return TCL_ERROR;
}

/*
 *----------------------------------------------------------------------
 *
 * PrintCoordPairs --
 *
 *	Convert pairs of floating point values in the X and Y arrays
 *	into a Tcl list.
 *
 * Results:
 *	The return value is a string (Tcl list).
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
PrintCoordPairs(clientData, tkwin, widgRec, offset, freeProcPtr)
    ClientData clientData;	/* not used */
    Tk_Window tkwin;		/* not used */
    char *widgRec;		/* Element information record */
    int offset;			/* not used */
    Tcl_FreeProc **freeProcPtr;	/* Memory deallocation scheme to use */
{
    register Element *elemPtr = (Element *)widgRec;
    register int i;
    char *result;
    int length;
    char string[TCL_DOUBLE_SPACE];
    Tcl_DString buffer;

    result = "";
    if ((elemPtr->x.length < 1) || (elemPtr->y.length < 1)) {
	return result;
    }
    Tcl_DStringInit(&buffer);
    length = MIN(elemPtr->x.length, elemPtr->y.length);
    for (i = 0; i < length; i++) {
	Tcl_PrintDouble(elemPtr->interp, elemPtr->x.data[i], string);
	Tcl_DStringAppendElement(&buffer, string);
	Tcl_PrintDouble(elemPtr->interp, elemPtr->y.data[i], string);
	Tcl_DStringAppendElement(&buffer, string);
    }
    result = Tcl_DStringValue(&buffer);
    result = strdup(result);
    *freeProcPtr = TCL_DYNAMIC;
    Tcl_DStringFree(&buffer);
    return (result);
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureLine --
 *
 *	Sets up the appropriate configuration parameters in the GC.
 *      It is assumed the parameters have been previously set by
 *	a call to Tk_ConfigureWidget.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then interp->result contains an error message.
 *
 * Side effects:
 *	Configuration information such as line width, line style, color
 *	etc. get set in a new GC.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ConfigureLine(graphPtr, elemPtr)
    Graph *graphPtr;
    Element *elemPtr;
{
    LineElement *lineElemPtr = (LineElement *)elemPtr;
    unsigned long gcMask;
    GC newGC;
    XGCValues gcValues;

    /* Borders of symbols: need foreground only */

    gcValues.foreground = lineElemPtr->normalFg->pixel;
    newGC = Tk_GetGC(graphPtr->tkwin, GCForeground, &gcValues);
    if (lineElemPtr->normalBorderGC != NULL) {
	Tk_FreeGC(graphPtr->display, lineElemPtr->normalBorderGC);
    }
    lineElemPtr->normalBorderGC = newGC;

    gcValues.foreground = lineElemPtr->activeFg->pixel;
    newGC = Tk_GetGC(graphPtr->tkwin, GCForeground, &gcValues);
    if (lineElemPtr->activeBorderGC != NULL) {
	Tk_FreeGC(graphPtr->display, lineElemPtr->activeBorderGC);
    }
    lineElemPtr->activeBorderGC = newGC;

    /* Fills for symbols: need reversed foreground and background */

    gcMask = (GCBackground | GCForeground);
    gcValues.background = lineElemPtr->normalFg->pixel;
    gcValues.foreground = lineElemPtr->normalBg->pixel;
    newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues);
    if (lineElemPtr->normalFillGC != NULL) {
	Tk_FreeGC(graphPtr->display, lineElemPtr->normalFillGC);
    }
    lineElemPtr->normalFillGC = newGC;

    gcValues.background = lineElemPtr->activeFg->pixel;
    gcValues.foreground = lineElemPtr->activeBg->pixel;
    newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues);
    if (lineElemPtr->activeFillGC != NULL) {
	Tk_FreeGC(graphPtr->display, lineElemPtr->activeFillGC);
    }
    lineElemPtr->activeFillGC = newGC;

    /* Lines */

    gcMask |= (GCLineWidth | GCLineStyle | GCCapStyle | GCJoinStyle);
    gcValues.cap_style = CapButt;
    gcValues.join_style = JoinRound;
    gcValues.line_style = LineSolid;
    gcValues.line_width = lineElemPtr->lineWidth;
    gcValues.background = lineElemPtr->normalBg->pixel;
    gcValues.foreground = lineElemPtr->normalFg->pixel;
    if (lineElemPtr->dashes > 0) {
	gcValues.dash_offset = 0;
	gcValues.line_style = LineDoubleDash;
	gcValues.dashes = lineElemPtr->dashes;
	gcMask |= (GCDashList | GCDashOffset);
    }
    newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues);
    if (lineElemPtr->normalLineGC != NULL) {
	Tk_FreeGC(graphPtr->display, lineElemPtr->normalLineGC);
    }
    lineElemPtr->normalLineGC = newGC;

    gcValues.background = lineElemPtr->activeBg->pixel;
    gcValues.foreground = lineElemPtr->activeFg->pixel;
    newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues);
    if (lineElemPtr->activeLineGC != NULL) {
	Tk_FreeGC(graphPtr->display, lineElemPtr->activeLineGC);
    }
    lineElemPtr->activeLineGC = newGC;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GetLineLimits --
 *
 *	Returns the limits of the line element
 *
 * Results:
 *	None.
 *
 *----------------------------------------------------------------------
 */
static int
GetLineLimits(elemPtr, axisPtr, minPtr, maxPtr)
    Element *elemPtr;
    GraphAxis *axisPtr;		/* Axis information */
    double *minPtr, *maxPtr;
{
    LineElement *lineElemPtr = (LineElement *)elemPtr;
    int numPoints;

    *minPtr = Blt_posInfinity, *maxPtr = Blt_negInfinity;
    numPoints = MIN(lineElemPtr->x.length, lineElemPtr->y.length);

    /* Verify the element uses this axis */
    if ((elemPtr->axisFlags & (axisPtr->flags & AXIS_TYPE_MASK)) == 0) {
	return 0;
    }
    if (numPoints > 0) {
	if (axisPtr->flags & AXIS_X_MASK) {
	    *minPtr = (axisPtr->logScale)
		? lineElemPtr->x.logMin : lineElemPtr->x.min;
	    *maxPtr = lineElemPtr->x.max;
	} else {
	    *minPtr = (axisPtr->logScale)
		? lineElemPtr->y.logMin : lineElemPtr->y.min;
	    *maxPtr = lineElemPtr->y.max;
	}
    }
    return (numPoints);
}

/*
 *----------------------------------------------------------------------
 *
 * GetLineDistance --
 *
 *	Find the distance of the data point in the element closest to
 *	the window coordinates point specified.
 *
 * Results:
 *	Returns the distance of the closest point.  In addition,
 *	indexPtr is set to position of coordinate and the list of data
 *	points.  Index is -1 if the point does not fail within the
 *	prescribed range.
 *
 *----------------------------------------------------------------------
 */
static double
GetLineDistance(graphPtr, elemPtr, winX, winY, indexPtr)
    Graph *graphPtr;		/* Graph widget record */
    Element *elemPtr;		/* Element to examine */
    int winX, winY;		/* Window coordinates of point on screen */
    int *indexPtr;		/* Index of closest point in element */
{
    int length;
    LineElement *lineElemPtr = (LineElement *)elemPtr;	/* Line element */
    unsigned int dist, maxDist, minDist;
    int delta;
    unsigned int deltaX, deltaY;
    double realDist;
    register int i;
    int index;

    length = lineElemPtr->numPoints;
    maxDist = graphPtr->halo * graphPtr->halo;
    minDist = maxDist + 1;
    index = -1;
    for (i = 0; i < length; i++) {
	delta = winX - lineElemPtr->pointArr[i].x;
	deltaX = ABS(delta);
	delta = winY - lineElemPtr->pointArr[i].y;
	deltaY = ABS(delta);
	if ((deltaX > graphPtr->halo) || (deltaY > graphPtr->halo)) {
	    continue;
	}
	dist = deltaX * deltaX + deltaY * deltaY;
	if (dist < minDist) {
	    index = i;
	    minDist = dist;
	}
    }
    if (minDist > maxDist) {
	return -1.0;
    }
    realDist = sqrt((double)minDist);
    if (realDist > (double)graphPtr->halo) {
	return -1.0;
    }
    *indexPtr = index;
    return (realDist);
}

/*
 *----------------------------------------------------------------------
 *
 * LayoutLine --
 *
 *	Calculates the actual window coordinates of the line element.
 *	The window coordinates are saved in an allocated point array.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory is (re)allocated for the point array.
 *
 *----------------------------------------------------------------------
 */
static void
LayoutLine(graphPtr, elemPtr)
    Graph *graphPtr;		/* Graph widget record */
    Element *elemPtr;		/* Element component record */
{
    LineElement *lineElemPtr = (LineElement *)elemPtr;
    XPoint *pointArr;
    unsigned int numPoints;
    int x, lastX;
    int total, numSegments;
    register int i;
    LineInfo *segArr;
    int needSegments;

    numPoints = MIN(lineElemPtr->x.length, lineElemPtr->y.length);
    if (numPoints < 1) {
	lineElemPtr->numPoints = lineElemPtr->numSegments = 0;
	return;
    }
    pointArr = (XPoint *)malloc(numPoints * sizeof(XPoint));
    if (pointArr == NULL) {
	return;			/* Can't allocate point array */
    }
    /*
     * We need to determine is line segments exist only if
     *
     * 1) retracing is not allowed, and
     *
     * 2) we need to draw element lines (either because the symbol
     *    type is "line" or the line width is greater than zero,
     *	  which means to add a line in addition to the symbol).
     */
    needSegments = ((lineElemPtr->noretrace) && ((lineElemPtr->lineWidth > 0)
	    || (lineElemPtr->symbol == LINE_SYMBOL)));
    /*
     * Transform and store the coordinates into an array window
     * points.  In addition, make a quick count of line segments.
     */
    lastX = 0;			/* suppress compiler warning */
    total = numSegments = 0;
    for (i = 0; i < numPoints; i++) {
	pointArr[i] = Blt_TransformPt(graphPtr, lineElemPtr->x.data[i],
	    lineElemPtr->y.data[i], lineElemPtr->axisFlags);
	x = pointArr[i].x;
	if ((needSegments) && (total > 0) && (x < lastX)) {
	    numSegments++;
	    total = 0;
	}
	total++;
	lastX = x;
    }
    if (total > 0) {
	numSegments++;
    }
    /* Create an array of line segment information  */
    segArr = (LineInfo *)malloc(sizeof(LineInfo) * numSegments);
    if (segArr == NULL) {
	return;
    }
    if (!needSegments) {
	segArr[0].pointPtr = pointArr;
	segArr[0].numPoints = numPoints;
    } else {
	int count;

	lastX = 0;		/* suppress compiler warning */
	total = count = 0;
	for (i = 0; i < numPoints; i++) {
	    x = pointArr[i].x;
	    if ((total > 0) && (x < lastX)) {
		segArr[count].pointPtr = pointArr + (i - total);
		segArr[count].numPoints = total;
		count++;
		total = 0;
	    }
	    total++;
	    lastX = x;
	}
	if (total > 0) {
	    segArr[count].pointPtr = pointArr + (i - total);
	    segArr[count].numPoints = total;
	    count++;
	}
    }
    if (lineElemPtr->pointArr != NULL) {
	free((char *)lineElemPtr->pointArr);
    }
    lineElemPtr->pointArr = pointArr;
    lineElemPtr->numPoints = numPoints;
    if (lineElemPtr->segArr != NULL) {
	free((char *)lineElemPtr->segArr);
    }
    lineElemPtr->segArr = segArr;
    lineElemPtr->numSegments = numSegments;

    /* Make the symbol size odd so that its center is a single pixel. */
    lineElemPtr->symbolSize |= 0x01;
    lineElemPtr->symbolSize =
	ROUND(graphPtr->avgSymSize * lineElemPtr->symbolScale);
}

/*
 * -----------------------------------------------------------------
 *
 * GetSymbolInfo --
 *
 * 	Initialize a structure containing information about how to
 *	draw the a symbol at a particular size.  For example, we
 *	precalculate the offsets draw the symbol, rather than
 *	computing them at each data point. Some information is
 *	stored in this structure to save passing it in to each call
 *	of DrawLineSymbol.
 *
 * Results:
 *	Returns a pointer to a static symbol information structure.
 *
 * Problems:
 *	Most notable is the round-off errors generated when
 *	calculating the centered position of the symbol.
 *
 * -----------------------------------------------------------------
 */
static SymbolInfo *
GetSymbolInfo(graphPtr, lineElemPtr, size, active)
    Graph *graphPtr;		/* Graph widget record */
    LineElement *lineElemPtr;	/* Line element information */
    int size;			/* Size of element */
    int active;			/* If non-zero, set to draw active element */
{
    static SymbolInfo s;
    int delta;

    s.display = graphPtr->display;
    s.canvas = graphPtr->canvas;
    s.radius = (size / 2);
    s.size = size;
    s.type = lineElemPtr->symbol;

    switch (s.type) {
    case CROSS_SYMBOL:
    case PLUS_SYMBOL:

	delta = (size / 6);

	/*
	 *
	 *          2   3	The plus/cross symbol is a closed polygon
	 *			of 12 points. The diagram to the left
	 *    0,12  1   4    5	represents the positions of the points
	 *           x,y	which are computed below. The extra
	 *     11  10   7    6	(thirteenth) point connects the first and
	 *			last points.
	 *          9   8
	 */

	s.offset[0].x = s.offset[11].x = s.offset[12].x = -s.radius;
	s.offset[2].x = s.offset[1].x = s.offset[10].x = s.offset[9].x =
	    -delta;
	s.offset[3].x = s.offset[4].x = s.offset[7].x = s.offset[8].x =
	    delta;
	s.offset[5].x = s.offset[6].x = s.radius;
	s.offset[2].y = s.offset[3].y = -s.radius;
	s.offset[0].y = s.offset[1].y = s.offset[4].y = s.offset[5].y =
	    s.offset[12].y = -delta;
	s.offset[11].y = s.offset[10].y = s.offset[7].y = s.offset[6].y =
	    delta;
	s.offset[9].y = s.offset[8].y = s.radius;
	if (lineElemPtr->symbol == CROSS_SYMBOL) {
	    register int i;
	    register double dx, dy;

	    /* For the cross symbol, rotate the points by 45 degrees. */
	    for (i = 0; i < 12; i++) {
		dx = (double)(s.offset[i].x) * M_SQRT1_2;
		dy = (double)(s.offset[i].y) * M_SQRT1_2;
		s.offset[i].x = ROUND(dx - dy);
		s.offset[i].y = ROUND(dx + dy);
	    }
	    s.offset[12] = s.offset[0];
	}
	break;

    case DIAMOND_SYMBOL:

	/*
	 *
	 *             		The plus symbol is a closed polygon
	 *	      1		of 4 points. The diagram to the left
	 *                  	represents the positions of the points
	 *       0,4 x,y  2	which are computed below. The extra
	 *     			(fifth) point connects the first and
	 *	      3		last points.
	 *
	 */

	s.offset[0].y = s.offset[4].y = s.offset[2].y = s.offset[1].x =
	    s.offset[3].x = 0;
	s.offset[1].y = s.offset[4].x = s.offset[0].x = -s.radius;
	s.offset[3].y = s.offset[2].x = s.radius;
	break;

    case SPLUS_SYMBOL:
    case SCROSS_SYMBOL:
	s.radius = size / 2;
	s.offset[0].y = s.offset[1].y = s.offset[2].x = s.offset[3].x = 0;
	s.offset[0].x = s.offset[2].y = -s.radius;
	s.offset[1].x = s.offset[3].y = s.radius;
	/* Rotate the 4 points 45 degrees for a cross */
	if (s.type == SCROSS_SYMBOL) {
	    register int i;
	    register double dx, dy;

	    for (i = 0; i < 4; i++) {
		dx = (double)(s.offset[i].x) * M_SQRT1_2;
		dy = (double)(s.offset[i].y) * M_SQRT1_2;
		s.offset[i].x = ROUND(dx - dy);
		s.offset[i].y = ROUND(dx + dy);
	    }
	}
	break;

    case SQUARE_SYMBOL:
	s.size = (int)ceil(size * M_SQRT1_2);
	s.radius = size / 2;
	break;

    case CIRCLE_SYMBOL:
	s.size--;
	break;

    case LINE_SYMBOL:
	break;
    }

    /* Set the correct GCs, depending if the element is active  */
    if (active) {
	s.borderGC = lineElemPtr->activeBorderGC;
	s.lineGC = lineElemPtr->activeLineGC;
	s.fillGC = lineElemPtr->activeFillGC;
    } else {
	s.borderGC = lineElemPtr->normalBorderGC;
	s.lineGC = lineElemPtr->normalLineGC;
	s.fillGC = lineElemPtr->normalFillGC;
    }
    return (&s);
}

/*
 * -----------------------------------------------------------------
 *
 * DrawLineSymbol --
 *
 * 	Draws the symbol (as indicated by the symbol information
 *	structure) centered at the given x,y coordinate.
 *
 * Results:
 *	None.
 *
 * -----------------------------------------------------------------
 */
static void
DrawLineSymbol(x, y, infoPtr)
    int x, y;			/* x,y coordinates of center of symbol */
    SymbolInfo *infoPtr;	/* Information how to draw the symbol */
{
    XPoint points[13];
    XSegment segments[2];
    register int n;

    switch (infoPtr->type) {
    case LINE_SYMBOL:
	/*
	 * Draw an extra line offset by one pixel from the previous to
	 * give a thicker appearance
	 */
	XDrawLine(infoPtr->display, infoPtr->canvas, infoPtr->lineGC,
	    x - infoPtr->radius, y, x + infoPtr->radius, y);
	XDrawLine(infoPtr->display, infoPtr->canvas, infoPtr->lineGC,
	    x - infoPtr->radius, y + 1, x + infoPtr->radius, y + 1);
	break;

    case PLUS_SYMBOL:
    case CROSS_SYMBOL:
	for (n = 0; n < 13; n++) {
	    points[n].x = infoPtr->offset[n].x + x;
	    points[n].y = infoPtr->offset[n].y + y;
	}
	XFillPolygon(infoPtr->display, infoPtr->canvas, infoPtr->fillGC,
	    points, 13, Complex, CoordModeOrigin);
	XDrawLines(infoPtr->display, infoPtr->canvas, infoPtr->borderGC,
	    points, 13, CoordModeOrigin);
	break;

    case SPLUS_SYMBOL:
    case SCROSS_SYMBOL:
	segments[0].x1 = infoPtr->offset[0].x + x;
	segments[0].y1 = infoPtr->offset[0].y + y;
	segments[0].x2 = infoPtr->offset[1].x + x;
	segments[0].y2 = infoPtr->offset[1].y + y;
	segments[1].x1 = infoPtr->offset[2].x + x;
	segments[1].y1 = infoPtr->offset[2].y + y;
	segments[1].x2 = infoPtr->offset[3].x + x;
	segments[1].y2 = infoPtr->offset[3].y + y;
	/* I'm not sure what attributes this symbol type should have.
	 * Right now I using the fill GC instead of line or border GC */
	XDrawSegments(infoPtr->display, infoPtr->canvas, infoPtr->fillGC,
	    segments, 2);
	break;

    case SQUARE_SYMBOL:
	x -= infoPtr->radius, y -= infoPtr->radius;
	XFillRectangle(infoPtr->display, infoPtr->canvas, infoPtr->fillGC,
	    x, y, infoPtr->size, infoPtr->size);
	XDrawRectangle(infoPtr->display, infoPtr->canvas, infoPtr->borderGC,
	    x, y, infoPtr->size, infoPtr->size);
	break;

    case CIRCLE_SYMBOL:
	x -= infoPtr->radius, y -= infoPtr->radius;
	XFillArc(infoPtr->display, infoPtr->canvas, infoPtr->fillGC, x, y,
	    infoPtr->size, infoPtr->size, 0, 23040);
	XDrawArc(infoPtr->display, infoPtr->canvas, infoPtr->borderGC, x, y,
	    infoPtr->size, infoPtr->size, 0, 23040);
	break;

    case DIAMOND_SYMBOL:
	for (n = 0; n < 5; n++) {
	    points[n].x = infoPtr->offset[n].x + x;
	    points[n].y = infoPtr->offset[n].y + y;
	}
	XFillPolygon(infoPtr->display, infoPtr->canvas, infoPtr->fillGC,
	    points, 5, Convex, CoordModeOrigin);
	XDrawLines(infoPtr->display, infoPtr->canvas, infoPtr->borderGC,
	    points, 5, CoordModeOrigin);
	break;
    }
}

/*
 * -----------------------------------------------------------------
 *
 * DrawLineSymbols --
 *
 * 	Draw a symbol centered at each point given in the array of
 *	window coordinates (coordArr) based upon the element symbol
 *	type and size.
 *
 * Results:
 *	None.
 *
 * Problems:
 *	Most notable is the round-off errors generated when
 *	calculating the centered position of the symbol.
 *
 * -----------------------------------------------------------------
 */
static void
DrawLineSymbols(graphPtr, elemPtr, size, coordArr, numCoords)
    Graph *graphPtr;		/* Graph widget record */
    Element *elemPtr;		/* Line element information */
    int size;			/* Size of element */
    XPoint *coordArr;		/* Array of x,y coordinates for line */
    int numCoords;		/* Number of coordinates in array */
{
    LineElement *lineElemPtr = (LineElement *)elemPtr;
    SymbolInfo *infoPtr;
    register int i;
    int active;

    active = (lineElemPtr->active) && (lineElemPtr->numActivePoints == 0);
    infoPtr = GetSymbolInfo(graphPtr, lineElemPtr, size, active);
    for (i = 0; i < numCoords; i++) {
	DrawLineSymbol(coordArr[i].x, coordArr[i].y, infoPtr);
    }
    if (lineElemPtr->numActivePoints > 0) {
	int index;

	infoPtr = GetSymbolInfo(graphPtr, lineElemPtr, size, 1);
	for (i = 0; i < lineElemPtr->numActivePoints; i++) {
	    index = lineElemPtr->activePoints[i];
	    if ((index >= 0) && (index < numCoords)) {
		DrawLineSymbol(coordArr[index].x, coordArr[index].y, infoPtr);
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DisplayLine --
 *
 *	Draws the connected line(s) representing the element. If the
 *	line is made up of non-line symbols and the line width parameter
 *	has been set (linewidth > 0), the element will also be drawn as
 *	a line (with the linewidth requested).  The line may consist of
 *	separate line segments depending if
 *
 *	    a) the preceding x coordinate is less than the current
 *	and
 *	    b) the line -noretrace option is turned on.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	X drawing commands are output.
 *
 *----------------------------------------------------------------------
 */
static void
DisplayLine(graphPtr, elemPtr)
    Graph *graphPtr;
    Element *elemPtr;
{
    LineElement *lineElemPtr = (LineElement *)elemPtr;
    GC lineGC;

    if (lineElemPtr->numPoints < 1) {
	return;
    }
    if ((lineElemPtr->active) && (lineElemPtr->numActivePoints == 0)) {
	lineGC = lineElemPtr->activeLineGC;
    } else {
	lineGC = lineElemPtr->normalLineGC;
    }
    /*
     * Draw lines if symbol is "line" or linewidth is greater than 0
     */
    if ((lineElemPtr->lineWidth > 0) || (lineElemPtr->symbol == LINE_SYMBOL)) {
	register int i;

	for (i = 0; i < lineElemPtr->numSegments; i++) {
	    XDrawLines(graphPtr->display, graphPtr->canvas, lineGC,
		lineElemPtr->segArr[i].pointPtr,
		lineElemPtr->segArr[i].numPoints,
		CoordModeOrigin);
	}
    }
    /* Draw non-line symbols */

    if (lineElemPtr->symbol != LINE_SYMBOL) {
	DrawLineSymbols(graphPtr, (Element *)lineElemPtr,
	    lineElemPtr->symbolSize,
	    lineElemPtr->pointArr, lineElemPtr->numPoints);
    }
}

/*
 * -----------------------------------------------------------------
 *
 * PrintLineSymbols --
 *
 * 	Draw a symbol centered at the given x,y window coordinate
 *	based upon the element symbol type and size.
 *
 * Results:
 *	None.
 *
 * Problems:
 *	Most notable is the round-off errors generated when
 *	calculating the centered position of the symbol.
 * -----------------------------------------------------------------
 */
static void
PrintLineSymbols(graphPtr, elemPtr, size, pointArr, numPoints)
    Graph *graphPtr;
    Element *elemPtr;
    int size;
    XPoint *pointArr;
    int numPoints;
{
    LineElement *lineElemPtr = (LineElement *)elemPtr;
    double symbolSize;
    register int i;
    int x, y;

    symbolSize = (double)size;
    if (elemPtr->symbol == LINE_SYMBOL) {
	Blt_LineWidthToPostScript(graphPtr, lineElemPtr->lineWidth + 2);
	Blt_LineDashesToPostScript(graphPtr, lineElemPtr->dashes);
    } else {
	if ((elemPtr->symbol == SQUARE_SYMBOL) ||
	    (elemPtr->symbol == DIAMOND_SYMBOL)) {
	    /* Adjust square and diamond symbol sizes */
	    symbolSize = ((double)size * M_SQRT1_2) - 1;
	}
	Blt_LineWidthToPostScript(graphPtr, lineElemPtr->lineWidth);
	Blt_LineDashesToPostScript(graphPtr, 0);
    }
    Tcl_AppendResult(graphPtr->interp, "/BgColorProc {\n   ", (char *)NULL);
    Blt_BackgroundToPostScript(graphPtr, lineElemPtr->normalBg);
    Tcl_AppendResult(graphPtr->interp, "} def\n", (char *)NULL);
    if ((elemPtr->symbol == SPLUS_SYMBOL) ||
	(elemPtr->symbol == SCROSS_SYMBOL)) {
	Blt_ForegroundToPostScript(graphPtr, lineElemPtr->normalBg);
    } else {
	Blt_ForegroundToPostScript(graphPtr, lineElemPtr->normalFg);
    }

    for (i = 0; i < numPoints; i++) {
	x = pointArr[i].x, y = pointArr[i].y;
	sprintf(graphPtr->scratchPtr, "%d %d %g %.2s\n", x, y, symbolSize,
	    lineSymbolNames[lineElemPtr->symbol]);
	Tcl_AppendResult(graphPtr->interp, graphPtr->scratchPtr, (char *)NULL);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * PrintLine --
 *
 *	Similar to the DrawLine procedure, prints PostScript related
 *	commands to form the connected line(s) representing the element.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	PostScript pen width, dashes, and color settings are changed.
 *
 *----------------------------------------------------------------------
 */
static void
PrintLine(graphPtr, elemPtr)
    Graph *graphPtr;
    Element *elemPtr;
{
    LineElement *lineElemPtr = (LineElement *)elemPtr;

    if (lineElemPtr->numPoints < 1) {
	return;
    }
    /* Set line and foreground attributes */
    Blt_LineWidthToPostScript(graphPtr, lineElemPtr->lineWidth);
    Blt_ForegroundToPostScript(graphPtr, lineElemPtr->normalFg);
    if (lineElemPtr->dashes > 0) {
	Blt_LineDashesToPostScript(graphPtr, lineElemPtr->dashes);
	Tcl_AppendResult(graphPtr->interp, "/DashesProc {\ngsave\n",
	    (char *)NULL);
	Blt_BackgroundToPostScript(graphPtr, lineElemPtr->normalBg);
	Blt_LineDashesToPostScript(graphPtr, 0);
	Tcl_AppendResult(graphPtr->interp, "stroke grestore\n} def\n",
	    (char *)NULL);
    } else {
	Tcl_AppendResult(graphPtr->interp, "/DashesProc {} def\n",
	    (char *)NULL);
    }

    if ((lineElemPtr->lineWidth > 0) || (lineElemPtr->symbol == LINE_SYMBOL)) {
	register int i;

	for (i = 0; i < lineElemPtr->numSegments; i++) {
	    Blt_PrintLine(graphPtr, lineElemPtr->segArr[i].pointPtr,
		lineElemPtr->segArr[i].numPoints);
	}
    }
    /* Draw non-line symbols */

    if (lineElemPtr->symbol != LINE_SYMBOL) {
	(*lineElemPtr->printSymbolsProc) (graphPtr, (Element *)lineElemPtr,
	    lineElemPtr->symbolSize, lineElemPtr->pointArr,
	    lineElemPtr->numPoints);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyLine --
 *
 *	Release memory and resources allocated for the line element.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the line element is freed up.
 *
 *----------------------------------------------------------------------
 */
static void
DestroyLine(graphPtr, elemPtr)
    Graph *graphPtr;
    Element *elemPtr;
{
    LineElement *lineElemPtr = (LineElement *)elemPtr;

    Tk_FreeOptions(lineElemPtr->configSpecs, (char *)lineElemPtr,
	graphPtr->display, 0);

    if (lineElemPtr->normalBorderGC != NULL) {
	Tk_FreeGC(graphPtr->display, lineElemPtr->normalBorderGC);
    }
    if (lineElemPtr->normalFillGC != NULL) {
	Tk_FreeGC(graphPtr->display, lineElemPtr->normalFillGC);
    }
    if (lineElemPtr->normalLineGC != NULL) {
	Tk_FreeGC(graphPtr->display, lineElemPtr->normalLineGC);
    }
    if (lineElemPtr->activeBorderGC != NULL) {
	Tk_FreeGC(graphPtr->display, lineElemPtr->activeBorderGC);
    }
    if (lineElemPtr->activeFillGC != NULL) {
	Tk_FreeGC(graphPtr->display, lineElemPtr->activeFillGC);
    }
    if (lineElemPtr->activeLineGC != NULL) {
	Tk_FreeGC(graphPtr->display, lineElemPtr->activeLineGC);
    }
    if (lineElemPtr->pointArr != NULL) {
	free((char *)lineElemPtr->pointArr);
    }
    if (lineElemPtr->segArr != NULL) {
	free((char *)lineElemPtr->segArr);
    }
    if (lineElemPtr->x.data != NULL) {
	free((char *)lineElemPtr->x.data);
    }
    if (lineElemPtr->y.data != NULL) {
	free((char *)lineElemPtr->y.data);
    }
    if (lineElemPtr->activePoints != NULL) {
	free((char *)lineElemPtr->activePoints);
    }
    free((char *)lineElemPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * CreateLine --
 *
 *	Allocate memory and initialize methods for the new line element.
 *
 * Results:
 *	The pointer to the newly allocated element structure is returned.
 *
 * Side effects:
 *	Memory is allocated for the line element structure.
 *
 *----------------------------------------------------------------------
 */
static Element *
CreateLine()
{
    register LineElement *lineElemPtr;

    lineElemPtr = (LineElement *)calloc(1, sizeof(LineElement));
    if (lineElemPtr == NULL) {
	return NULL;
    }
    lineElemPtr->configSpecs = lineConfigSpecs;
    lineElemPtr->configProc = ConfigureLine;
    lineElemPtr->destroyProc = DestroyLine;
    lineElemPtr->displayProc = DisplayLine;
    lineElemPtr->limitsProc = GetLineLimits;
    lineElemPtr->distProc = GetLineDistance;
    lineElemPtr->layoutProc = LayoutLine;
    lineElemPtr->printProc = PrintLine;
    lineElemPtr->drawSymbolsProc = DrawLineSymbols;
    lineElemPtr->printSymbolsProc = PrintLineSymbols;
    lineElemPtr->type = LINE_ELEM_TYPE;
    lineElemPtr->symbolScale = 1.0;
    lineElemPtr->symbol = LINE_SYMBOL;
    return ((Element *)lineElemPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureBar --
 *
 *	Sets up the appropriate configuration parameters in the GC.
 *      It is assumed the parameters have been previously set by
 *	a call to Tk_ConfigureWidget.
 *
 * Results:
 *	The return value is a standard Tcl result.  If TCL_ERROR is
 *	returned, then interp->result contains an error message.
 *
 * Side effects:
 *	Configuration information such as bar foreground/background
 *	color and stipple etc. get set in a new GC.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
ConfigureBar(graphPtr, elemPtr)
    Graph *graphPtr;
    register Element *elemPtr;
{
    XGCValues gcValues;
    unsigned long gcMask;
    BarElement *barElemPtr = (BarElement *)elemPtr;
    register int i;
    GC *newArr;

    barElemPtr->symbol = SQUARE_SYMBOL;	/* Use square for bar charts */

    gcValues.foreground = barElemPtr->fgColorArr[0]->pixel;
    gcValues.background = (Tk_3DBorderColor(barElemPtr->border))->pixel;
    gcMask = GCForeground | GCBackground;

    if (barElemPtr->stipple != None) {
	gcValues.stipple = barElemPtr->stipple;
	gcValues.fill_style = FillOpaqueStippled;
	gcMask |= (GCStipple | GCFillStyle);
    }
    newArr = (GC *)malloc(sizeof(GC) * barElemPtr->numFgColors);
    for (i = 0; i < barElemPtr->numFgColors; i++) {
	gcValues.foreground = (barElemPtr->fgColorArr[i])->pixel;
	newArr[i] = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues);
    }
    for (i = 0; i < barElemPtr->numGCs; i++) {
	Tk_FreeGC(graphPtr->display, barElemPtr->gcArr[i]);
    }
    if (barElemPtr->gcArr != NULL) {
	free((char *)barElemPtr->gcArr);
    }
    barElemPtr->gcArr = newArr;
    barElemPtr->numGCs = barElemPtr->numFgColors;
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * GetBarLimits --
 *
 *	Returns the limits of the bar data
 *
 * Results:
 *	None.
 *
 *----------------------------------------------------------------------
 */
static int
GetBarLimits(elemPtr, axisPtr, minPtr, maxPtr)
    Element *elemPtr;
    GraphAxis *axisPtr;		/* Axis information */
    double *minPtr, *maxPtr;
{
    BarElement *barElemPtr = (BarElement *)elemPtr;
    int numPoints;

    *minPtr = Blt_posInfinity, *maxPtr = Blt_negInfinity;
    if ((barElemPtr->x.length < 1) || (barElemPtr->y.length < 1)) {
	return 0;
    }
    if ((elemPtr->axisFlags & (axisPtr->flags & AXIS_TYPE_MASK)) == 0) {
	return 0;
    }
    numPoints = barElemPtr->y.length;
    if (axisPtr->flags & AXIS_X_MASK) {
	*minPtr = (axisPtr->logScale)
	    ? barElemPtr->x.logMin : barElemPtr->x.min;
	*minPtr -= 0.5;
	*maxPtr = barElemPtr->x.max + 0.5;
    } else {
	*minPtr = (axisPtr->logScale)
	    ? barElemPtr->y.logMin : barElemPtr->y.min;
	if (barElemPtr->stacked) {
	    register int i;
	    double sum = 0.0;

	    for (i = 0; i < barElemPtr->y.length; i++) {
		sum += barElemPtr->y.data[i];
	    }
	    *maxPtr = sum;
	} else {
	    *maxPtr = barElemPtr->y.max;
	}
    }
    return (numPoints);
}

/*
 *----------------------------------------------------------------------
 *
 * GetBarDistance --
 *
 *	Find the element data point closest to the window coordinates
 *	point specified.
 *
 * Results:
 *	Returns the index of the closest point.  In addition, distPtr is
 *	set to distance (squared) between the found coordinate and the
 *	given point.  Returns -1 if the point does not fail within the
 *	range prescribed.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static double
GetBarDistance(graphPtr, elemPtr, winX, winY, indexPtr)
    Graph *graphPtr;		/* Graph widget record */
    Element *elemPtr;		/* Line element */
    int winX, winY;		/* Window coordinates of point on screen */
    int *indexPtr;		/* Index of closest point in element */
{
    BarElement *barElemPtr = (BarElement *)elemPtr;	/* Bar element */
    int dist;

    /* Simply check the first x-coordinate value */
    dist = graphPtr->halo + 1;
    if (barElemPtr->numRects > 0) {
	GraphAxis *axis = graphPtr->axisArr[X1];

	dist = winX - Blt_Transform(axis, barElemPtr->x.data[0]);
	dist = ABS(dist);
    }
    if (dist > graphPtr->halo) {
	return -1.0;
    }
    *indexPtr = 0;
    return ((double)dist);
}

/*
 *----------------------------------------------------------------------
 *
 * LayoutBar --
 *
 *	Calculates the actual window coordinates of the bar element.
 *	The window coordinates are saved in the bar element structure.
 *
 * Results:
 *	None.
 *
 * Notes:
 *	A bar can have multiple y values.  In this case, the bar can be
 * 	represented as either a set of contiguous bars (no spacing) or a
 *      single multi-segmented (stacked) bar.
 *
 *	The X axis layout for a barchart may be presented in one of two ways.
 *	If x values are used, the bars are placed at those coordinates.
 *	Otherwise, the range will represent the number of values.
 *
 *----------------------------------------------------------------------
 */
static void
LayoutBar(graphPtr, elemPtr)
    Graph *graphPtr;
    Element *elemPtr;
{
    BarElement *barElemPtr = (BarElement *)elemPtr;
    double sum;			/* Sum of bar y values */
    int curX, curY;		/* Current and last Y positions */
    int lastY;			/* Last y-coordinate used */
    XRectangle *rectArr;
    int avgWidth;		/* Width of each bar in set */
    register int i;
    GraphAxis *X, *Y;

    if ((barElemPtr->x.length < 1) || (barElemPtr->y.length < 1)) {
	return;			/* No bars */
    }
    barElemPtr->numRects = barElemPtr->y.length;

    rectArr = (XRectangle *)malloc(barElemPtr->numRects * sizeof(XRectangle));
    if (rectArr == NULL) {
	barElemPtr->numRects = 0;
	return;
    }
    /* Determine which axes the element is mapped to */
    X = (barElemPtr->axisFlags & AXIS_X1_FLAG)
	? graphPtr->axisArr[X1] : graphPtr->axisArr[X2];
    Y = (barElemPtr->axisFlags & AXIS_Y1_FLAG)
	? graphPtr->axisArr[Y1] : graphPtr->axisArr[Y2];

    curY = lastY = Blt_Transform(Y, 0.0);
    /*
     * Watch out for limiting of values
     */
    avgWidth = Blt_TransformDist(X, graphPtr->barWidth);
    if (avgWidth < 1) {
	avgWidth = 1;
    }
    /* Use only the first data value in the X array for positioning */
    curX = Blt_Transform(X, barElemPtr->x.data[0]) - avgWidth / 2;
    sum = 0.0;
    if (!barElemPtr->stacked) {
	avgWidth /= barElemPtr->y.length;
    }
    for (i = 0; i < barElemPtr->y.length; i++) {
	rectArr[i].width = avgWidth - (barElemPtr->padX * 2);
	rectArr[i].x = curX;
	if (barElemPtr->stacked) {
	    /* Next point is the current sum the y values */
	    sum += barElemPtr->y.data[i];
	    lastY = curY;
	    curY = Blt_Transform(Y, sum);
	} else {
	    /* Adjust the x-coordinate by the next bar width */
	    curY = Blt_Transform(Y, barElemPtr->y.data[i]);
	    curX += avgWidth;
	}
	if (barElemPtr->y.max < 0.0) {
	    /* Rectangle y-origin is last y-coordinate */
	    rectArr[i].y = lastY;
	    rectArr[i].height = (curY - lastY) + 1;
	} else {
	    /* Rectangle y-origin is current y-coordinate */
	    rectArr[i].y = curY;
	    rectArr[i].height = (lastY - curY) + 1;
	}
    }
    barElemPtr->symbolSize =
	ROUND(graphPtr->avgSymSize * barElemPtr->symbolScale);
    if (barElemPtr->rectArr != NULL) {
	free((char *)barElemPtr->rectArr);
    }
    barElemPtr->rectArr = rectArr;
}

/*
 * -----------------------------------------------------------------
 *
 * DrawBarSymbols --
 *
 * 	Draw a symbol centered at the given x,y window coordinate
 *	based upon the element symbol type and size.
 *
 * Results:
 *	None.
 *
 * Problems:
 *	Most notable is the round-off errors generated when
 *	calculating the centered position of the symbol.
 * -----------------------------------------------------------------
 */
static void
DrawBarSymbols(graphPtr, elemPtr, size, pointArr, numPoints)
    Graph *graphPtr;
    Element *elemPtr;
    int size;
    XPoint *pointArr;
    int numPoints;
{
    BarElement *barElemPtr = (BarElement *)elemPtr;
    register int i;
    int x, y, radius;

    radius = (size / 2);
    size--;
    for (i = 0; i < numPoints; i++) {
	x = pointArr[i].x - radius;
	y = pointArr[i].y - radius;
	XFillRectangle(graphPtr->display, graphPtr->canvas,
	    barElemPtr->gcArr[0], x, y, size, size);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DisplayBar --
 *
 *	Draws the rectangle representing the bar element.
 *	If the relief option is set to "raised" or "sunken" and the
 *	bar borderwidth is set (borderwidth > 0), a 3D border is
 *	drawn around the bar.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	X drawing commands are output.
 *
 *----------------------------------------------------------------------
 */
static void
DisplayBar(graphPtr, elemPtr)
    Graph *graphPtr;
    Element *elemPtr;
{
    BarElement *barElemPtr = (BarElement *)elemPtr;
    register int i;
    GC gc;

    for (i = 0; i < barElemPtr->numRects; i++) {
	gc = barElemPtr->gcArr[0];
	if (i < barElemPtr->numGCs) {
	    gc = barElemPtr->gcArr[i];
	}
	XFillRectangle(graphPtr->display, graphPtr->canvas, gc,
	    barElemPtr->rectArr[i].x, barElemPtr->rectArr[i].y,
	    barElemPtr->rectArr[i].width, barElemPtr->rectArr[i].height);
	/* Add a 3D border effect, if required */
	if ((barElemPtr->borderWidth > 0) &&
	    (barElemPtr->relief != TK_RELIEF_FLAT)) {
	    Tk_Draw3DRectangle(graphPtr->display, graphPtr->canvas,
		barElemPtr->border, barElemPtr->rectArr[i].x,
		barElemPtr->rectArr[i].y, barElemPtr->rectArr[i].width,
		barElemPtr->rectArr[i].height, barElemPtr->borderWidth,
		barElemPtr->relief);
	}
    }
}

/*
 * -----------------------------------------------------------------
 *
 * PrintBarSymbols --
 *
 * 	Draw a symbol centered at the given x,y window coordinate
 *	based upon the element symbol type and size.
 *
 * Results:
 *	None.
 *
 * Problems:
 *	Most notable is the round-off errors generated when
 *	calculating the centered position of the symbol.
 * -----------------------------------------------------------------
 */
static void
PrintBarSymbols(graphPtr, elemPtr, size, pointArr, numPoints)
    Graph *graphPtr;
    Element *elemPtr;
    int size;
    XPoint *pointArr;
    int numPoints;
{
    BarElement *barElemPtr = (BarElement *)elemPtr;
    register int i;
    int x, y;
    XColor *normalFg;

    Tcl_AppendResult(graphPtr->interp, "/BgColorProc {\n", (char *)NULL);
    Blt_BackgroundToPostScript(graphPtr, Tk_3DBorderColor(barElemPtr->border));
    Tcl_AppendResult(graphPtr->interp, "} def\n", (char *)NULL);

    if (barElemPtr->stipple != None) {
	unsigned int width, height;

	Tcl_AppendResult(graphPtr->interp, "/StippleProc {\n    gsave\n    ",
	    (char *)NULL);
	Tk_SizeOfBitmap(graphPtr->display, barElemPtr->stipple, &width,
	    &height);
	Blt_StippleToPostScript(graphPtr, barElemPtr->stipple, width, height,
	    1);
	Tcl_AppendResult(graphPtr->interp, "} def\n", (char *)NULL);
    }
    for (i = 0; i < numPoints; i++) {
	normalFg = barElemPtr->fgColorArr[0];
	if (i < barElemPtr->numFgColors) {
	    normalFg = barElemPtr->fgColorArr[i];
	}
	Blt_ForegroundToPostScript(graphPtr, normalFg);
	x = pointArr[i].x, y = pointArr[i].y;
	sprintf(graphPtr->scratchPtr, "%d %d %d %.2s\n", x, y, size,
	    lineSymbolNames[(int)elemPtr->symbol]);
	Tcl_AppendResult(graphPtr->interp, graphPtr->scratchPtr, (char *)NULL);
	if (barElemPtr->stipple != None) {
	    Tcl_AppendResult(graphPtr->interp, "/StippleProc cvx exec\n",
		(char *)NULL);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * PrintBar --
 *
 *	Similar to the DrawBar procedure, prints PostScript related
 *	commands to form rectangle representing the bar element.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	PostScript pen width, dashes, and color settings are changed.
 *
 *----------------------------------------------------------------------
 */
static void
PrintBar(graphPtr, elemPtr)
    Graph *graphPtr;
    Element *elemPtr;
{
    BarElement *barElemPtr = (BarElement *)elemPtr;
    register int i;
    XColor *normalFg;

    for (i = 0; i < barElemPtr->numRects; i++) {
	normalFg = barElemPtr->fgColorArr[0];
	if (i < barElemPtr->numFgColors) {
	    normalFg = barElemPtr->fgColorArr[i];
	}
	if (barElemPtr->stipple != None) {
	    unsigned int width, height;

	    Blt_BackgroundToPostScript(graphPtr,
		Tk_3DBorderColor(barElemPtr->border));
	    Blt_RectangleToPostScript(graphPtr, barElemPtr->rectArr[i].x,
		barElemPtr->rectArr[i].y, barElemPtr->rectArr[i].width,
		barElemPtr->rectArr[i].height);
	    Tk_SizeOfBitmap(graphPtr->display, barElemPtr->stipple,
		&width, &height);
	    Blt_ForegroundToPostScript(graphPtr, normalFg);
	    Blt_StippleToPostScript(graphPtr, barElemPtr->stipple, width,
		height, True);
	} else {
	    Blt_ForegroundToPostScript(graphPtr, normalFg);
	    Blt_RectangleToPostScript(graphPtr, barElemPtr->rectArr[i].x,
		barElemPtr->rectArr[i].y, barElemPtr->rectArr[i].width,
		barElemPtr->rectArr[i].height);
	}
	if ((barElemPtr->borderWidth > 0) &&
	    (barElemPtr->relief != TK_RELIEF_FLAT)) {
	    Blt_Print3DRectangle(graphPtr, barElemPtr->border,
		barElemPtr->rectArr[i].x, barElemPtr->rectArr[i].y,
		(unsigned int)barElemPtr->rectArr[i].width,
		barElemPtr->rectArr[i].height, barElemPtr->borderWidth,
		barElemPtr->relief);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyBar --
 *
 *	Release memory and resources allocated for the bar element.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the bar element is freed up.
 *
 *----------------------------------------------------------------------
 */
static void
DestroyBar(graphPtr, elemPtr)
    Graph *graphPtr;
    Element *elemPtr;
{
    BarElement *barElemPtr = (BarElement *)elemPtr;
    register int i;

    Tk_FreeOptions(barElemPtr->configSpecs, (char *)barElemPtr,
	graphPtr->display, 0);

    for (i = 0; i < barElemPtr->numGCs; i++) {
	Tk_FreeGC(graphPtr->display, barElemPtr->gcArr[i]);
    }
    if (barElemPtr->gcArr != NULL) {
	free((char *)barElemPtr->gcArr);
    }
    for (i = 0; i < barElemPtr->numFgColors; i++) {
	Tk_FreeColor(barElemPtr->fgColorArr[i]);
    }
    if (barElemPtr != NULL) {
	free((char *)barElemPtr->fgColorArr);
    }
    if (barElemPtr->rectArr != NULL) {
	free((char *)barElemPtr->rectArr);
    }
    if (barElemPtr->x.data != NULL) {
	free((char *)barElemPtr->x.data);
    }
    if (barElemPtr->y.data != NULL) {
	free((char *)barElemPtr->y.data);
    }
    free((char *)barElemPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * CreateBar --
 *
 *	Allocate memory and initialize methods for the new bar element.
 *
 * Results:
 *	The pointer to the newly allocated element structure is returned.
 *
 * Side effects:
 *	Memory is allocated for the bar element structure.
 *
 *----------------------------------------------------------------------
 */
static Element *
CreateBar()
{
    register BarElement *barElemPtr;

    barElemPtr = (BarElement *)calloc(1, sizeof(BarElement));
    if (barElemPtr == NULL) {
	return NULL;
    }
    barElemPtr->configSpecs = barConfigSpecs;
    barElemPtr->configProc = ConfigureBar;
    barElemPtr->destroyProc = DestroyBar;
    barElemPtr->displayProc = DisplayBar;
    barElemPtr->limitsProc = GetBarLimits;
    barElemPtr->distProc = GetBarDistance;
    barElemPtr->layoutProc = LayoutBar;
    barElemPtr->printProc = PrintBar;
    barElemPtr->drawSymbolsProc = DrawBarSymbols;
    barElemPtr->printSymbolsProc = PrintBarSymbols;
    barElemPtr->type = BAR_ELEM_TYPE;
    barElemPtr->relief = TK_RELIEF_FLAT;
    barElemPtr->symbolScale = 1.0;
    barElemPtr->stacked = 0;
    barElemPtr->fgColorArr = NULL;
    barElemPtr->numFgColors = 0;
    barElemPtr->gcArr = NULL;
    barElemPtr->numGCs = 0;
    barElemPtr->stipple = None;
    return ((Element *)barElemPtr);
}

/*
 * Generic element routines:
 */
/*
 *----------------------------------------------------------------------
 *
 * CreateElement --
 *
 *	Add a new element to the graph.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 *----------------------------------------------------------------------
 */
static int
CreateElement(graphPtr, argc, argv)
    Graph *graphPtr;
    int argc;
    char **argv;
{
    register Element *elemPtr;
    Tcl_HashEntry *entryPtr;
    Blt_ListEntry *listPtr;
    int dummy;

    if (argc < 4) {
	Tcl_AppendResult(graphPtr->interp, "wrong # args: should be \"",
	    argv[0], " element create name ?options?\"", (char *)NULL);
	return TCL_ERROR;
    }
    entryPtr = Tcl_FindHashEntry(&(graphPtr->elemTable), argv[3]);
    if (entryPtr != NULL) {
	Tcl_AppendResult(graphPtr->interp, "element \"", argv[3],
	    "\" already exists in \"", argv[0], "\"", (char *)NULL);
	return TCL_ERROR;
    }
    elemPtr = NULL;
    if (graphPtr->type == BAR_ELEM_TYPE) {
	elemPtr = CreateBar();
    } else if (graphPtr->type == LINE_ELEM_TYPE) {
	elemPtr = CreateLine();
    }
    if (elemPtr == NULL) {
	Tcl_AppendResult(graphPtr->interp, "can't create element \"",
	    argv[3], "\"", (char *)NULL);
	return TCL_ERROR;
    }
    elemPtr->interp = graphPtr->interp;
    elemPtr->id = Tk_GetUid(argv[3]);
    elemPtr->label = strdup(argv[3]);
    elemPtr->axisFlags = (AXIS_Y1_FLAG | AXIS_X1_FLAG);
    elemPtr->x.size = elemPtr->y.size = 0;
    elemPtr->x.length = elemPtr->y.length = 0;
    if (Tk_ConfigureWidget(graphPtr->interp, graphPtr->tkwin,
	    elemPtr->configSpecs, argc - 4, argv + 4,
	    (char *)elemPtr, 0) != TCL_OK) {
	(*elemPtr->destroyProc) (graphPtr, elemPtr);
	return TCL_ERROR;
    }
    entryPtr = Tcl_CreateHashEntry(&(graphPtr->elemTable),
	(char *)elemPtr->id, &dummy);
    Tcl_SetHashValue(entryPtr, (ClientData)elemPtr);
    (*elemPtr->configProc) (graphPtr, elemPtr);
    listPtr = Blt_CreateListEntry(elemPtr->id);
    Blt_SetListValue(listPtr, (ClientData)elemPtr);
    Blt_LinkListAfter(&(graphPtr->elemList), listPtr,
	(Blt_ListEntry *)NULL);
    elemPtr->mapped = 1;
    elemPtr->flags |= LAYOUT_PENDING;
    Blt_ComputeAxes(graphPtr);
    Blt_EventuallyRedraw(graphPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ConfigureElement --
 *
 *	Sets the element specifications by the given the command line
 *	arguments and calls the element specification configuration
 *	routine. If zero or one command line options are given, only
 *	information about the option(s) is returned in interp->result.
 *      If the element configuration has changed and the element is
 *	currently displayed, the axis limits are updated and recomputed.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 * Side Effects:
 *	Graph will be redrawn to reflect the new display list.
 *
 *----------------------------------------------------------------------
 */
static int
ConfigureElement(graphPtr, argc, argv)
    Graph *graphPtr;
    int argc;
    char *argv[];
{
    Tcl_HashEntry *entryPtr;
    Element *elemPtr;
    int flags = TK_CONFIG_ARGV_ONLY;
    int result;
    static int elemOptions[] =
    {
	Tk_Offset(Element, label),
    };

    if (argc < 4) {
	Tcl_AppendResult(graphPtr->interp, "wrong # args: should be \"",
	    argv[0], " element configure name ?options?\"", (char *)NULL);
	return TCL_ERROR;
    }
    entryPtr = Tcl_FindHashEntry(&(graphPtr->elemTable), argv[3]);
    if (entryPtr == NULL) {
	Tcl_AppendResult(graphPtr->interp, "can't find element \"", argv[3],
	    "\" in \"", argv[0], "\"", (char *)NULL);
	return TCL_ERROR;
    }
    elemPtr = (Element *)Tcl_GetHashValue(entryPtr);
    if (argc == 4) {
	return (Tk_ConfigureInfo(graphPtr->interp, graphPtr->tkwin,
		elemPtr->configSpecs, (char *)elemPtr, (char *)NULL, flags));
    } else if (argc == 5) {
	return (Tk_ConfigureInfo(graphPtr->interp, graphPtr->tkwin,
		elemPtr->configSpecs, (char *)elemPtr, argv[4], flags));
    }
    result = Tk_ConfigureWidget(graphPtr->interp, graphPtr->tkwin,
	elemPtr->configSpecs, argc - 4, argv + 4, (char *)elemPtr, flags);
    if ((*elemPtr->configProc) (graphPtr, elemPtr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (result != TCL_OK) {
	return TCL_ERROR;
    }
    elemPtr->flags |= LAYOUT_PENDING;
    graphPtr->flags |= (LAYOUT_PENDING | REDRAW_ALL);

    /* Layout entire graph if specific element options change */
    if (Blt_OptionChanged(elemPtr->configSpecs, elemOptions, 1)) {
	graphPtr->flags |= LAYOUT_ALL;
    }
    Blt_ComputeAxes(graphPtr);
    Blt_EventuallyRedraw(graphPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * AppendElement --
 *
 *	Given a Tcl list of numeric expression representing the element
 *	values, convert into an array of double precision values. In
 *	addition, the minimum and maximum values are saved.  Since
 *	elastic values are allow (values which translate to the
 *	min/max of the graph), we must try to get the non-elastic
 *	minimum and maximum.
 *
 * Results:
 *	The return value is a standard Tcl result.  The vector is passed
 *	back via the vecPtr.
 *
 *----------------------------------------------------------------------
 */
static int
AppendElement(graphPtr, argc, argv)
    Graph *graphPtr;
    int argc;
    char **argv;
{
    Tcl_Interp *interp = graphPtr->interp;
    register Vector *vecPtr;
    int numExprs;
    char **exprArr = NULL;
    int result = TCL_ERROR;
    unsigned int arraySize;
    register Element *elemPtr;
    Tcl_HashEntry *entryPtr;

    if (argc != 5) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
	    " element append name coords\"", NULL);
	return TCL_ERROR;
    }
    entryPtr = Tcl_FindHashEntry(&(graphPtr->elemTable), argv[3]);
    if (entryPtr == NULL) {
	Tcl_AppendResult(interp, "can't find element \"", argv[3],
	    "\" in \"", argv[0], "\"", (char *)NULL);
	return TCL_ERROR;
    }
    elemPtr = (Element *)Tcl_GetHashValue(entryPtr);

    /* Split the list of expressions and check the values */

    if (Tcl_SplitList(interp, argv[4], &numExprs, &exprArr) != TCL_OK) {
	return TCL_ERROR;
    }
    if (numExprs < 2) {
	interp->result = "too few numeric expressions in coordinate pair list";
	goto error;
    }
    if (numExprs & 1) {
	interp->result = "odd number of expressions in coordinate pair list";
	goto error;
    }
    /*
     * Check both x and y vectors for XDrawLines size limit (64K)
     */
    vecPtr = &(elemPtr->x);
    arraySize = (numExprs / 2) + vecPtr->length;
    if (arraySize >= 65535) {
	interp->result = "x-coordinate vector is too large";
	goto error;
    }
    if (AppendVector(interp, vecPtr, exprArr, numExprs, 0,
	    TwinVectors, AppendData) != TCL_OK) {
	goto error;
    }
    vecPtr = &(elemPtr->y);
    arraySize = (numExprs / 2) + vecPtr->length;
    if (arraySize >= 65535) {
	interp->result = "y-coordinate vector is too large";
	goto error;
    }
    if (AppendVector(interp, vecPtr, exprArr, numExprs, 1,
	    TwinVectors, AppendData) != TCL_OK) {
	goto error;
    }
    result = TCL_OK;
    elemPtr->flags |= LAYOUT_PENDING;	/* Re-generate screen points */
    Blt_ComputeAxes(graphPtr);
    Blt_EventuallyRedraw(graphPtr);
  error:
    if (exprArr != NULL) {
	free((char *)exprArr);
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * DeleteElements --
 *
 *	Delete the named elements from the graph.
 *
 * Results:
 *	TCL_ERROR is returned if any of the named elements can not be found.
 *	Otherwise TCL_OK is returned;
 *
 * Side Effects:
 *	If the element is currently mapped, the plotting area of the
 *	graph is redrawn. Memory and resources allocated by the elements
 *	are released.
 *
 *----------------------------------------------------------------------
 */
static int
DeleteElements(graphPtr, argc, argv)
    Graph *graphPtr;		/* Graph widget */
    int argc;			/* Number of element names */
    char **argv;		/* List of element names */
{
    register Element *elemPtr;
    Tcl_HashEntry *entryPtr;
    Blt_ListEntry *listPtr;
    register int i;
    int count;

    if (argc < 3) {
	Tcl_AppendResult(graphPtr->interp, "wrong # args: should be \"",
	    argv[0], " element delete name ?name...?\"", (char *)NULL);
	return TCL_ERROR;
    }
    count = 0;
    for (i = 3; i < argc; i++) {
	entryPtr = Tcl_FindHashEntry(&(graphPtr->elemTable), argv[i]);
	if (entryPtr == NULL) {
	    Tcl_AppendResult(graphPtr->interp, "can't find element \"",
		argv[i], "\" in \"", argv[0], (char *)NULL);
	    return TCL_ERROR;
	}
	elemPtr = (Element *)Tcl_GetHashValue(entryPtr);
	Tcl_DeleteHashEntry(entryPtr);

	listPtr = Blt_FindListEntry(&(graphPtr->elemList), argv[i]);
	if (listPtr != NULL) {
	    Blt_DeleteListEntry(&(graphPtr->elemList), listPtr);
	    count++;
	}
	/*
	 * Call the element's own destructor to release the memory and
	 * resources allocated for it.
	 */
	(*elemPtr->destroyProc) (graphPtr, elemPtr);
    }
    if (count > 0) {
	Blt_ComputeAxes(graphPtr);
	Blt_EventuallyRedraw(graphPtr);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ElementNames --
 *
 *	Runs through the given list of element entries and builds a
 *	Tcl list of element names.  This procedure is used in the
 *	"names" and "show" commands.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *	interp->result contains the list of element names.
 *
 *----------------------------------------------------------------------
 */
static int
ElementNames(graphPtr, argc, argv)
    Graph *graphPtr;
    int argc;
    char **argv;
{
    register Element *elemPtr;
    register Tcl_HashEntry *entryPtr;
    Tcl_HashSearch cursor;

    if ((argc < 3) || (argc > 4)) {
	Tcl_AppendResult(graphPtr->interp, "wrong # args: should be \"",
	    argv[0], " element names ?pattern?", (char *)NULL);
	return TCL_ERROR;
    }
    for (entryPtr = Tcl_FirstHashEntry(&(graphPtr->elemTable), &cursor);
	entryPtr != NULL; entryPtr = Tcl_NextHashEntry(&cursor)) {
	elemPtr = (Element *)Tcl_GetHashValue(entryPtr);
	if ((argc == 3) || Tcl_StringMatch(elemPtr->id, argv[3])) {
	    Tcl_AppendElement(graphPtr->interp, elemPtr->id);
	}
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ResetDisplayList --
 *
 *	Given a Tcl list of element names, this procedure rebuilds the
 *	display list, ignoring invalid element names. This list describes
 *	not only only which elements to draw, but in what order.  This is
 *	only important for bar and pie charts.
 *
 * Results:
 *	The return value is a standard Tcl result.  Only if the Tcl list
 *	cannot be split, a TCL_ERROR is returned and interp->result contains
 *	an error message.
 *
 * Side effects:
 *	The graph is eventually redrawn using the new display list.
 *
 *----------------------------------------------------------------------
 */
static int
ResetDisplayList(graphPtr, newList)
    Graph *graphPtr;		/* Graph widget record */
    char *newList;		/* Tcl list of element names */
{
    int numNames;		/* Number of names found in Tcl name list */
    char **nameArr;		/* Broken out array of element names */
    Blt_ListEntry *listPtr;
    Tcl_HashEntry *hashPtr;
    Tcl_HashSearch cursor;
    register int count;
    Element *elemPtr;		/* Element information record */

    if (Tcl_SplitList(graphPtr->interp, newList, &numNames,
	    &nameArr) != TCL_OK) {
	Tcl_AppendResult(graphPtr->interp, "can't split name list \"", newList,
	    "\"", (char *)NULL);
	return TCL_ERROR;
    }
    /* Clear the display list and mark all elements unmapped. */
    Blt_ClearList(&(graphPtr->elemList));
    for (hashPtr = Tcl_FirstHashEntry(&(graphPtr->elemTable), &cursor);
	hashPtr != NULL; hashPtr = Tcl_NextHashEntry(&cursor)) {
	elemPtr = (Element *)Tcl_GetHashValue(hashPtr);
	elemPtr->mapped = 0;
    }

    /*
     * Rebuild the display list, checking that each name it exists
     * (we're currently ignoring invalid element names).
     */
    for (count = 0; count < numNames; count++) {
	hashPtr = Tcl_FindHashEntry(&(graphPtr->elemTable), nameArr[count]);
	if (hashPtr != NULL) {
	    elemPtr = (Element *)Tcl_GetHashValue(hashPtr);
	    elemPtr->mapped = 1;
	    listPtr = Blt_CreateListEntry(elemPtr->id);
	    if (listPtr == NULL) {
		free((char *)nameArr);
		return TCL_ERROR;
	    }
	    Blt_SetListValue(listPtr, (ClientData)elemPtr);
	    Blt_LinkListAfter(&(graphPtr->elemList), listPtr,
		(Blt_ListEntry *)NULL);
	}
    }
    free((char *)nameArr);
    graphPtr->flags |= (LAYOUT_PENDING | LAYOUT_ALL);
    Blt_ComputeAxes(graphPtr);
    Blt_EventuallyRedraw(graphPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ShowElements --
 *
 *	Displays or rebuilds the element display list.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *	interp->result contains the list of element names.
 *
 *----------------------------------------------------------------------
 */
static int
ShowElements(graphPtr, argc, argv)
    Graph *graphPtr;
    int argc;
    char **argv;
{
    register Element *elemPtr;
    register Blt_ListEntry *entryPtr;

    if ((argc < 3) || (argc > 4)) {
	Tcl_AppendResult(graphPtr->interp, "wrong # args: should be \"",
	    argv[0], " element show ?nameList?\"", (char *)NULL);
	return TCL_ERROR;
    }
    if (argc == 4) {
	ResetDisplayList(graphPtr, argv[3]);
    }
    for (entryPtr = Blt_FirstListEntry(&(graphPtr->elemList));
	entryPtr != NULL; entryPtr = Blt_NextListEntry(entryPtr)) {
	elemPtr = (Element *)Blt_GetListValue(entryPtr);
	Tcl_AppendElement(graphPtr->interp, (char *)elemPtr->id);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ClosestElement --
 *
 *	Find the element closest to the window coordinates specified.
 *
 * Results:
 *	Returns TCL_OK if no errors occured. The result field of the
 *	interpreter may contain a list containing the element name,
 *	the index of the closest point, and the x and y graph coordinates
 *	of the point is stored.  If an error occurred, returns TCL_ERROR
 *	and an error message is left in interp->result.
 *
 *----------------------------------------------------------------------
 */
static int
ClosestElement(graphPtr, argc, argv)
    Graph *graphPtr;		/* Graph widget */
    int argc;			/* Number of element names */
    char **argv;		/* List of element names */
{
    register Element *elemPtr;
    Element *minPtr;
    int minIndex;
    double minDist;
    int x, y;

    if (argc < 5) {
	Tcl_AppendResult(graphPtr->interp, "wrong # args: should be \"",
	    argv[0], " element closest winX winY ?elements...?\"",
	    (char *)NULL);
	return TCL_ERROR;
    }
    if ((Tk_GetPixels(graphPtr->interp, graphPtr->tkwin,
		argv[3], &x) != TCL_OK) ||
	(Tk_GetPixels(graphPtr->interp, graphPtr->tkwin,
		argv[4], &y) != TCL_OK)) {
	Tcl_AppendResult(graphPtr->interp, ": bad window coordinates",
	    (char *)NULL);
	return TCL_ERROR;
    }
    minIndex = 0;
    minPtr = NULL;
    minDist = Blt_posInfinity;

    if (argc > 5) {
	Tcl_HashEntry *entryPtr;
	double dist;
	int index;
	register int i;

	/*
	 * Search for closest point in set of elements listed. In case
	 * of two or more elements have the same closest point, pick
	 * the first element.
	 */
	for (i = 5; i < argc; i++) {	/* Element name list supplied */
	    entryPtr = Tcl_FindHashEntry(&(graphPtr->elemTable), argv[i]);
	    if (entryPtr == NULL) {
		Tcl_AppendResult(graphPtr->interp, "can't find element \"",
		    argv[i], "\" in \"", argv[0], "\"", (char *)NULL);
		return TCL_ERROR;
	    }
	    elemPtr = (Element *)Tcl_GetHashValue(entryPtr);
	    if (!elemPtr->mapped) {
		Tcl_AppendResult(graphPtr->interp, "element \"", argv[i],
		    "\" isn't mapped", (char *)NULL);
		return TCL_ERROR;
	    }
	    dist = (*elemPtr->distProc) (graphPtr, elemPtr, x, y, &index);
	    if ((dist >= 0.0) && (dist < minDist)) {
		minDist = dist;
		minPtr = elemPtr;
		minIndex = index;
	    }
	}
    } else {
	Blt_ListEntry *entryPtr;
	double dist;
	int index;

	/*
	 * Find the closest point in set of displayed elements,
	 * searching the display list from back to front.  It's
	 * possible that two or more element's may include the same
	 * closest data point.  In these cases, we pick the one drawn
	 * last. BTW. It's ok if an element itself has more than one
	 * point which is the closest.  Active elements are drawn
	 * after the normal elements.
	 */
	for (entryPtr = Blt_LastListEntry(&(graphPtr->elemList));
	    entryPtr != NULL; entryPtr = Blt_PrevListEntry(entryPtr)) {
	    elemPtr = (Element *)Blt_GetListValue(entryPtr);
	    dist = (*elemPtr->distProc) (graphPtr, elemPtr, x, y, &index);
	    if ((dist >= 0.0) && (dist < minDist)) {
		minDist = dist;
		minPtr = elemPtr;
		minIndex = index;
	    }
	}
    }

    if (minDist < Blt_posInfinity) {
	char string[TCL_DOUBLE_SPACE + 1];

	Tcl_AppendElement(graphPtr->interp, minPtr->id);
	sprintf(string, "%d", minIndex);
	Tcl_AppendElement(graphPtr->interp, string);
	Tcl_PrintDouble(graphPtr->interp, minPtr->x.data[minIndex], string);
	Tcl_AppendElement(graphPtr->interp, string);
	Tcl_PrintDouble(graphPtr->interp, minPtr->y.data[minIndex], string);
	Tcl_AppendElement(graphPtr->interp, string);
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ActivateElement --
 *
 *	Find the element closest to the window coordinates specified.
 *
 * Results:
 *	Returns TCL_OK if no errors occured. The result field of the
 *	interpreter may contain a list containing the element name,
 *	the index of the closest point, and the x and y graph coordinates
 *	of the point is stored.  If an error occurred, returns TCL_ERROR
 *	and an error message is left in interp->result.
 *
 *----------------------------------------------------------------------
 */

static int
ActivateElement(graphPtr, argc, argv)
    Graph *graphPtr;		/* Graph widget */
    int argc;			/* Number of element names */
    char **argv;		/* List of element names */
{
    Tcl_Interp *interp = graphPtr->interp;
    LineElement *lineElemPtr;
    Tcl_HashEntry *entryPtr;
    int arraySize;
    int *indexArr;
    char c;

    if (argc < 5) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
	    " element activate name index ?index...?\"", (char *)NULL);
	return TCL_ERROR;
    }
    entryPtr = Tcl_FindHashEntry(&(graphPtr->elemTable), argv[3]);
    if (entryPtr == NULL) {
	Tcl_AppendResult(interp, "can't find element \"", argv[3], "\" in \"",
	    argv[0], "\"", (char *)NULL);
	return TCL_ERROR;
    }
    lineElemPtr = (LineElement *)Tcl_GetHashValue(entryPtr);

    c = argv[4][0];
    arraySize = 0;
    indexArr = NULL;
    if ((argc == 5) && (c == 'a') && (strcmp(argv[4], "all") == 0)) {
	lineElemPtr->active = 1;
    } else if ((argc == 5) && (c == 'n') && (strcmp(argv[4], "none") == 0)) {
	lineElemPtr->active = 0;
    } else {
	register int i;
	int index;

	arraySize = argc - 4;
	indexArr = (int *)malloc(sizeof(int) * arraySize);
	if (indexArr == NULL) {
	    interp->result = "can't allocate index array";
	    return TCL_ERROR;
	}
	for (i = 0; i < arraySize; i++) {
	    if (Tcl_GetInt(graphPtr->interp, argv[i + 4], &index) != TCL_OK) {
		free((char *)indexArr);
		return TCL_ERROR;
	    }
	    indexArr[i] = index;
	}
	lineElemPtr->active = 1;
    }
    lineElemPtr->numActivePoints = arraySize;
    if (lineElemPtr->activePoints != NULL) {
	free((char *)lineElemPtr->activePoints);
    }
    lineElemPtr->activePoints = indexArr;
    Blt_EventuallyRedraw(graphPtr);
    return TCL_OK;
}

/*
 * Global routines:
 */

/*
 *--------------------------------------------------------------
 *
 * Blt_ElementCmd --
 *
 *	This procedure is invoked to process the Tcl command
 *	that corresponds to a widget managed by this module.
 *	See the user documentation for details on what it does.
 *
 * Results:
 *	A standard Tcl result.
 *
 * Side effects:
 *	See the user documentation.
 *
 *--------------------------------------------------------------
 */
/* ARGSUSED */
int
Blt_ElementCmd(graphPtr, argc, argv)
    Graph *graphPtr;		/* Graph widget record */
    int argc;			/* # arguments */
    char **argv;		/* Argument list */
{
    int result = TCL_ERROR;
    char c;
    int length;

    if (argc < 3) {
	Tcl_AppendResult(graphPtr->interp, "wrong # args: should be \"",
	    argv[0], " element option ?args?\"", NULL);
	return TCL_ERROR;
    }
    c = argv[2][0];
    length = strlen(argv[2]);
    if ((graphPtr->type == GRAPH) && (c == 'a') && (length > 1) &&
	(strncmp(argv[2], "activate", length) == 0)) {
	result = ActivateElement(graphPtr, argc, argv);
    } else if ((c == 'a') && (length > 1) &&
	(strncmp(argv[2], "append", length) == 0)) {
	result = AppendElement(graphPtr, argc, argv);
    } else if ((c == 'c') && (length > 1) &&
	(strncmp(argv[2], "closest", length) == 0)) {
	result = ClosestElement(graphPtr, argc, argv);
    } else if ((c == 'c') && (length > 1) &&
	(strncmp(argv[2], "create", length) == 0)) {
	result = CreateElement(graphPtr, argc, argv);
    } else if ((c == 'c') && (length > 1) &&
	(strncmp(argv[2], "configure", length) == 0)) {
	result = ConfigureElement(graphPtr, argc, argv);
    } else if ((c == 'd') && (strncmp(argv[2], "delete", length) == 0)) {
	result = DeleteElements(graphPtr, argc, argv);
    } else if ((c == 'n') && (strncmp(argv[2], "names", length) == 0)) {
	result = ElementNames(graphPtr, argc, argv);
    } else if ((c == 's') && (strncmp(argv[2], "show", length) == 0)) {
	result = ShowElements(graphPtr, argc, argv);
    } else {
	char *options;

	if (graphPtr->type == GRAPH) {
	    options = " activate, append, closest, configure, create, \
delete, names, or show";
	} else {
	    options = " append, closest, configure, create, delete, \
names, or show";
	}
	Tcl_AppendResult(graphPtr->interp, "bad element option \"", argv[2],
	    "\": should be ", options, (char *)NULL);
	return TCL_ERROR;
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * UpdateVector --
 *
 *	Called by C interface routine Blt_GraphElement.
 *
 * Results:
 *	The return value is a standard Tcl result.  The vector is passed
 *	back via the vecPtr.
 *
 *----------------------------------------------------------------------
 */
static int
UpdateVector(graphPtr, vecPtr, start, numValues, valueArr)
    Graph *graphPtr;		/* Graph widget */
    Vector *vecPtr;		/* Element vector */
    int start;			/* Starting value */
    int numValues;		/* Number of elements in array */
    double *valueArr;		/* Array of floating point values */
{
    unsigned int arraySize;
    double *newArr;
    register int offset;
    register int i;
    int needed;

    offset = vecPtr->length;
    needed = (numValues / 2) + offset;

    /*
     * Keep doubling the size of the array until we fit the number
     * needed
     */
    arraySize = vecPtr->size;
    if (arraySize == 0) {
	arraySize = DEF_VECTOR_SIZE;
    }
    while (needed > arraySize) {
	arraySize += arraySize;
    }
    newArr = (double *)malloc(arraySize * sizeof(double));
    if (newArr == NULL) {
	graphPtr->interp->result = "can't allocate data vector";
	return TCL_ERROR;
    }
    if (offset > 0) {
	memcpy((char *)newArr, (char *)vecPtr->data, offset * sizeof(double));
    }
    /*
     * Pick out every other value and stuff it into the extended
     * array.
     */
    for (i = start; i < numValues; i += 2) {
	newArr[offset++] = valueArr[i];
    }
    if (vecPtr->data != NULL) {
	free((char *)vecPtr->data);
    }
    vecPtr->data = newArr;
    vecPtr->length = needed;
    vecPtr->size = arraySize;
    GetDataLimits(vecPtr);
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_GraphElement --
 *
 *	User convenience routine to set data of an individual graph
 *	element.
 *
 * Results:
 *	The return value is a standard Tcl result.
 *
 * Side Effects:
 *      Graph is redrawn with new data values. Axes are recalculated.
 *
 *----------------------------------------------------------------------
 */
/*LINTLIBRARY*/
int
Blt_GraphElement(interp, pathName, elemName, numValues, valueArr)
    Tcl_Interp *interp;		/* Interpreter to send results back to */
    char *pathName;		/* Path name of graph widget */
    char *elemName;		/* Name of element to set */
    int numValues;		/* Number of coordinates in array */
    double *valueArr;		/* Array of XY coordinate values */
{
    Tcl_HashEntry *entryPtr;
    Graph *graphPtr;
    Element *elemPtr;
    Tk_Window mainWin, tkwin;
    Tk_Uid classUid;
    ClientData clientData;

    mainWin = Tk_MainWindow(interp);
    if (mainWin == NULL) {
	return TCL_ERROR;
    }
    tkwin = Tk_NameToWindow(interp, pathName, mainWin);
    if (tkwin == NULL) {
	return TCL_ERROR;
    }
    classUid = Tk_Class(tkwin);
    if (classUid != Tk_GetUid("Blt_graph")) {
	Tcl_AppendResult(interp, "window \"", pathName,
	    "\" is the wrong class \"", classUid, "\"", (char *)NULL);
	return TCL_ERROR;
    }
    if (Blt_FindCmd(interp, pathName, &clientData) != TCL_OK) {
	Tcl_AppendResult(interp, "can't find command \"", pathName,
	    "\"", (char *)NULL);
	return TCL_ERROR;
    }
    if (numValues <= 2) {
	Tcl_AppendResult(interp, "too few values in array", (char *)NULL);
	return TCL_ERROR;
    }
    if (numValues & 1) {
	Tcl_AppendResult(interp, "odd number of values in array", (char *)NULL);
	return TCL_ERROR;
    }
    graphPtr = (Graph *)clientData;
    entryPtr = Tcl_FindHashEntry(&(graphPtr->elemTable), elemName);
    if (entryPtr == NULL) {
	Tcl_AppendResult(interp, "can't find element \"", elemName, "\" in \"",
	    pathName, "\"", (char *)NULL);
	return TCL_ERROR;
    }
    elemPtr = (Element *)Tcl_GetHashValue(entryPtr);

    /* Reset element data values */
    elemPtr->x.length = elemPtr->y.length = 0;
    UpdateVector(graphPtr, &(elemPtr->x), 0, numValues, valueArr);
    UpdateVector(graphPtr, &(elemPtr->y), 1, numValues, valueArr);

    /* Indicate element layout needs to be recalculated. */
    elemPtr->flags |= LAYOUT_PENDING;
    Blt_ComputeAxes(graphPtr);
    Blt_EventuallyRedraw(graphPtr);
    return TCL_OK;
}
