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

#include "bltGrElem.h"

#define COLOR_DEFAULT	(XColor *)1
#define PATTERN_SOLID	((Pixmap)1)

#define PEN_INCREASING  1	/* Draw line segments for only those
				 * data points whose abscissas are
				 * monotonically increasing in
				 * order */
#define PEN_DECREASING  2	/* Lines will be drawn between only
				 * those points whose abscissas are
				 * decreasing in order */

#define PEN_BOTH_DIRECTIONS	(PEN_INCREASING | PEN_DECREASING)
 /* Lines will be drawn between points regardless of the ordering of
  * the abscissas */

#define BROKEN_TRACE(dir,last,next) \
    (((((dir) & PEN_DECREASING) == 0) && ((next) < (last))) || \
     ((((dir) & PEN_INCREASING) == 0) && ((next) > (last))))

#define DRAW_SYMBOL(linePtr) \
	(((linePtr)->symbolCounter % (linePtr)->symbolInterval) == 0)

typedef enum { 
    PEN_SMOOTH_NONE,		/* Line segments */
    PEN_SMOOTH_STEP,		/* Step-and-hold */
    PEN_SMOOTH_NATURAL,		/* Natural cubic spline */
    PEN_SMOOTH_QUADRATIC,	/* Quadratic spline */
    PEN_SMOOTH_CATROM,		/* Catrom parametric spline */
    PEN_SMOOTH_LAST		/* Sentinel */
} Smoothing;

typedef struct {
    char *name;
    Smoothing value;
} SmoothingInfo;

static SmoothingInfo smoothingInfo[] = {
    { "linear",		PEN_SMOOTH_NONE },
    { "step",		PEN_SMOOTH_STEP },
    { "natural",	PEN_SMOOTH_NATURAL },
    { "cubic",		PEN_SMOOTH_NATURAL },
    { "quadratic",	PEN_SMOOTH_QUADRATIC },
    { "catrom",		PEN_SMOOTH_CATROM },
    { (char *)NULL,	PEN_SMOOTH_LAST }
};


typedef struct {
    Point2D *screenPts;		/* Array of transformed coordinates */
    int nScreenPts;		/* Number of coordinates */
    int *dataToStyle;		/* Index of pen styles  */
    int *indices;		/* Maps segments/traces to data points */

} MapInfo;

/*
 * Symbol types for line elements
 */
typedef enum {
    SYMBOL_NONE,
    SYMBOL_SQUARE,
    SYMBOL_CIRCLE,
    SYMBOL_DIAMOND,
    SYMBOL_PLUS,
    SYMBOL_CROSS,
    SYMBOL_SPLUS,
    SYMBOL_SCROSS,
    SYMBOL_TRIANGLE,
    SYMBOL_ARROW,
    SYMBOL_BITMAP
} SymbolType;

typedef struct {
    SymbolType type;		/* Type of symbol to be drawn/printed */

    int size;			/* Requested size of symbol in pixels */

    XColor *outlineColor;	/* Outline color */

    int outlineWidth;		/* Width of the outline */

    GC outlineGC;		/* Outline graphics context */

    XColor *fillColor;		/* Normal fill color */

    GC fillGC;			/* Fill graphics context */

    /* The last two fields are used only for bitmap symbols. */

    Pixmap bitmap;		/* Bitmap to determine foreground/background
				 * pixels of the symbol */

    Pixmap mask;		/* Bitmap representing the transparent
				 * pixels of the symbol */

} Symbol;

typedef struct {
    int start;			/* Index into the X-Y coordinate
				 * arrays indicating where trace
				 * starts. */

    int nScreenPts;		/* Number of points in the continuous
				 * trace */

    Point2D *screenPts;		/* Array of screen coordinates
				 * (malloc-ed) representing the
				 * trace. */

    int *symbolToData;		/* Reverse mapping of screen
				 * coordinate indices back to their
				 * data coordinates */
} Trace;

typedef struct {
    char *name;			/* Name of pen style. If the pen was
				 * statically allocated the name will
				 * be NULL. */

    Blt_Uid classUid;		/* Type of pen */

    char *typeId;		/* String token identifying the type
				 * of pen */

    unsigned int flags;		/* Indicates if the pen element is
				 * active or normal */

    int refCount;		/* Reference count for elements using
				 * this pen. */
    Blt_HashEntry *hashPtr;

    Tk_ConfigSpec *configSpecs;	/* Configuration specifications */

    PenConfigureProc *configProc;
    PenDestroyProc *destroyProc;

    /* Symbol attributes. */
    Symbol symbol;		/* Element symbol type */

    /* Trace attributes. */
    int traceWidth;		/* Width of the line segments. If
				 * lineWidth is 0, no line will be
				 * drawn, only symbols. */

    Blt_Dashes traceDashes;	/* Dash on-off list value */

    XColor *traceColor;		/* Line segment color */

    XColor *traceOffColor;	/* Line segment dash gap color */

    GC traceGC;			/* Line segment graphics context */
    
    /* Error bar attributes. */
    int errorBarShow;		/* Describes which error bars to
				 * display: none, x, y, or * both. */

    int errorBarLineWidth;	/* Width of the error bar segments. */

    int errorBarCapWidth;	/* Width of the cap on error bars. */

    XColor *errorBarColor;	/* Color of the error bar. */

    GC errorBarGC;		/* Error bar graphics context. */

    /* Show value attributes. */
    int valueShow;		/* Indicates whether to display data
				 * value.  Values are x, y, both, or 
				 * none. */
    char *valueFormat;		/* A printf format string. */

    TextStyle valueStyle;	/* Text attributes (color, font,
				 * rotation, etc.) of the value. */

} LinePen;

typedef struct {
    Weight weight;		/* Weight range where this pen is valid. */

    LinePen *penPtr;		/* Pen used to draw symbols, traces, error 
				 * bars, segments, etc. */

    Segment2D *xErrorBars;	/* Point to start of this pen's X-error bar 
				 * segments in the element's array. */
    Segment2D *yErrorBars;	/* Point to start of this pen's Y-error bar 
				 * segments in the element's array. */
    int xErrorBarCnt;		/* # of error bars for this pen. */
    int yErrorBarCnt;		/* # of error bars for this pen. */

    int errorBarCapWidth;	/* Length of the cap ends on each
				 * error bar. */

    int symbolSize;		/* Size of the pen's symbol scaled to the
				 * current graph size. */

    /* Graph specific data. */

    Point2D *symbolPts;		/* Points to start of array for this pen. */

    int nSymbolPts;		/* # of points for this pen. */

    /* The last two fields are used only for stripcharts. */

    Segment2D *strips;		/* Points to start of the line segments
				 * for this pen. */

    int nStrips;		/* # of line segments for this pen. */

} LinePenStyle;

typedef struct {
    char *name;			/* Identifier used to refer the
				 * element. Used in the "insert",
				 * "delete", or "show", operations. */

    Blt_Uid classUid;		/* Type of element */

    Graph *graphPtr;		/* Graph widget of element*/

    unsigned int flags;		/* Indicates if the entire element is
				 * active, or if coordinates need to
				 * be calculated */

    char **tags;

    int hidden;			/* If non-zero, don't display the
				 * element. */

    Blt_HashEntry *hashPtr;

    char *label;		/* Label displayed in legend */

    int labelRelief;		/* Relief of label in legend. */

    Axis2D axes;

    ElemVector x, y, w;		/* Contains array of numeric values */

    ElemVector xError;		/* Relative/symmetric X error values. */
    ElemVector yError;		/* Relative/symmetric Y error values. */
    ElemVector xHigh, xLow;	/* Absolute/asymmetric X-coordinate high/low
				   error values. */
    ElemVector yHigh, yLow;	/* Absolute/asymmetric Y-coordinate high/low
				   error values. */

    int *activeIndices;		/* Array of indices (malloc-ed) that
				 * indicate the data points are active
				 * (drawn with "active" colors). */

    int nActiveIndices;		/* Number of active data points.
				 * Special case: if < 0 then all data
				 * points are drawn active. */

    ElementProcs *procsPtr;
    Tk_ConfigSpec *configSpecs;	/* Configuration specifications */

    Segment2D *xErrorBars;	/* Point to start of this pen's X-error bar 
				 * segments in the element's array. */
    Segment2D *yErrorBars;	/* Point to start of this pen's Y-error bar 
				 * segments in the element's array. */
    int xErrorBarCnt;		/* # of error bars for this pen. */
    int yErrorBarCnt;		/* # of error bars for this pen. */

    int *xErrorToData;		/* Maps individual error bar segments back
				 * to the data point associated with it. */
    int *yErrorToData;		/* Maps individual error bar segments back
				 * to the data point associated with it. */

    int errorBarCapWidth;	/* Length of cap on error bars */

    LinePen *activePenPtr;	/* Pen to draw "active" elements. */
    LinePen *normalPenPtr;	/* Pen to draw elements normally. */

    Blt_Chain *palette;		/* Array of pen styles: pens are associated
				 * with specific ranges of data.*/

    /* Symbol scaling */
    int scaleSymbols;		/* If non-zero, the symbols will scale
				 * in size as the graph is zoomed
				 * in/out.  */

    double xRange, yRange;	/* Initial X-axis and Y-axis ranges:
				 * used to scale the size of element's
				 * symbol. */

    int state;
    /*
     * Line specific configurable attributes
     */
    LinePen builtinPen;

    /* Line smoothing */
    Smoothing reqSmooth;	/* Requested smoothing function to use
				 * for connecting the data points */

    Smoothing smooth;		/* Smoothing function used. */

    double rTolerance;		/* Tolerance to reduce the number of
				 * points displayed. */
    /*
     * Drawing related data structures.
     */

    /* Area-under-curve fill attributes. */
    XColor *fillFgColor;
    XColor *fillBgColor;
    GC fillGC;

    Blt_Tile fillTile;		/* Tile for fill area. */
    Pixmap fillStipple;		/* Stipple for fill area. */

    int nFillPts;
    Point2D *fillPts;		/* Array of points used to draw
				 * polygon to fill area under the
				 * curve */

    /* Symbol points */
    Point2D *symbolPts;		/* Holds the screen coordinates of all
				 * the data points for the element. */
    int nSymbolPts;		/* Number of points */

    int *symbolToData;		/* Contains indices of data points.
				 * It's first used to map pens to the
				 * visible points to sort them by pen
				 * style, and later to find data
				 * points from the index of a visible
				 * point. */

    /* Active symbol points */
    Point2D *activePts;		/* Array of indices representing the
				 * "active" points. */
    int nActivePts;		/* Number of indices in the above array. */

    int *activeToData;		/* Contains indices of data points.
				 * It's first used to map pens to the
				 * visible points to sort them by pen
				 * style, and later to find data
				 * points from the index of a visible
				 * point. */

    int reqMaxSymbols;
    int symbolInterval;
    int symbolCounter;

    /* X-Y graph-specific fields */

    int penDir;			/* Indicates if a change in the pen
				 * direction should be considered a
				 * retrace (line segment is not
				 * drawn). */

    Blt_Chain *traces;	/* List of traces (a trace is a series
				 * of contiguous line segments).  New
				 * traces are generated when either
				 * the next segment changes the pen
				 * direction, or the end point is
				 * clipped by the plotting area. */

    /* Stripchart-specific fields */

    Segment2D *strips;		/* Holds the the line segments of the
				 * element trace. The segments are
				 * grouped by pen style. */
    int nStrips;		/* Number of line segments to be drawn. */
    int *stripToData;		/* Pen to visible line segment mapping. */

} Line;

static Tk_OptionParseProc StringToPattern;
static Tk_OptionPrintProc PatternToString;
static Tk_OptionParseProc StringToSmooth;
static Tk_OptionPrintProc SmoothToString;
extern Tk_OptionParseProc Blt_StringToStyles;
extern Tk_OptionPrintProc Blt_StylesToString;
static Tk_OptionParseProc StringToPenDir;
static Tk_OptionPrintProc PenDirToString;
static Tk_OptionParseProc StringToSymbol;
static Tk_OptionPrintProc SymbolToString;

static Tk_CustomOption patternOption =
{
    StringToPattern, PatternToString, (ClientData)0
};
static Tk_CustomOption smoothOption =
{
    StringToSmooth, SmoothToString, (ClientData)0
};
static Tk_CustomOption stylesOption =
{
    Blt_StringToStyles, Blt_StylesToString, (ClientData)sizeof(LinePenStyle)
};
static Tk_CustomOption penDirOption =
{
    StringToPenDir, PenDirToString, (ClientData)0
};
static Tk_CustomOption symbolOption =
{
    StringToSymbol, SymbolToString, (ClientData)0
};
extern Tk_CustomOption bltColorOption;
extern Tk_CustomOption bltDashesOption;
extern Tk_CustomOption bltDataOption;
extern Tk_CustomOption bltDataPairsOption;
extern Tk_CustomOption bltDistanceOption;
extern Tk_CustomOption bltListOption;
extern Tk_CustomOption bltLinePenOption;
extern Tk_CustomOption bltShadowOption;
extern Tk_CustomOption bltXAxisOption;
extern Tk_CustomOption bltYAxisOption;
extern Tk_CustomOption bltTileOption;
extern Tk_CustomOption bltFillOption;
extern Tk_CustomOption bltStateOption;

#define DEF_LINE_ACTIVE_PEN		"activeLine"
#define DEF_LINE_AXIS_X			"x"
#define DEF_LINE_AXIS_Y			"y"
#define DEF_LINE_DASHES			(char *)NULL
#define DEF_LINE_DATA			(char *)NULL
#define DEF_LINE_FILL_COLOR    		"defcolor"
#define DEF_LINE_FILL_MONO		"defcolor"
#define DEF_LINE_HIDE			"no"
#define DEF_LINE_LABEL			(char *)NULL
#define DEF_LINE_LABEL_RELIEF		"flat"
#define DEF_LINE_MAX_SYMBOLS		"0"
#define DEF_LINE_OFFDASH_COLOR    	(char *)NULL
#define DEF_LINE_OFFDASH_MONO		(char *)NULL
#define DEF_LINE_OUTLINE_COLOR		"defcolor"
#define DEF_LINE_OUTLINE_MONO		"defcolor"
#define DEF_LINE_OUTLINE_WIDTH 		"1"
#define DEF_LINE_PATTERN		(char *)NULL
#define DEF_LINE_PATTERN_BG		"white"
#define DEF_LINE_PATTERN_FG		"black"
#define DEF_LINE_PATTERN_TILE		(char *)NULL
#define DEF_LINE_PEN_COLOR		RGB_NAVYBLUE
#define DEF_LINE_PEN_DIRECTION		"both"
#define DEF_LINE_PEN_MONO		RGB_BLACK
#define DEF_LINE_PEN_WIDTH		"1"
#define DEF_LINE_PIXELS			"0.125i"
#define DEF_LINE_REDUCE			"0.0"
#define DEF_LINE_SCALE_SYMBOLS		"yes"
#define DEF_LINE_SMOOTH			"linear"
#define DEF_LINE_STATE			"normal"
#define DEF_LINE_STIPPLE		(char *)NULL
#define DEF_LINE_STYLES			""
#define DEF_LINE_SYMBOL			"circle"
#define DEF_LINE_TAGS			"all"
#define DEF_LINE_X_DATA			(char *)NULL
#define DEF_LINE_Y_DATA			(char *)NULL

#define DEF_LINE_ERRORBAR_COLOR		"defcolor"
#define DEF_LINE_ERRORBAR_LINE_WIDTH	"1"
#define DEF_LINE_ERRORBAR_CAP_WIDTH	"1"
#define DEF_LINE_SHOW_ERRORBARS		"both"

#define DEF_PEN_ACTIVE_COLOR		RGB_BLUE
#define DEF_PEN_ACTIVE_MONO		RGB_BLACK
#define DEF_PEN_DASHES			(char *)NULL
#define DEF_PEN_FILL_COLOR    		"defcolor"
#define DEF_PEN_FILL_MONO		"defcolor"
#define DEF_PEN_LINE_WIDTH		"1"
#define DEF_PEN_NORMAL_COLOR		RGB_NAVYBLUE
#define DEF_PEN_NORMAL_MONO		RGB_BLACK
#define DEF_PEN_OFFDASH_COLOR    	(char *)NULL
#define DEF_PEN_OFFDASH_MONO		(char *)NULL
#define DEF_PEN_OUTLINE_COLOR		"defcolor"
#define DEF_PEN_OUTLINE_MONO		"defcolor"
#define DEF_PEN_OUTLINE_WIDTH 		"1"
#define DEF_PEN_PIXELS			"0.125i"
#define DEF_PEN_SYMBOL			"circle"
#define DEF_PEN_TYPE			"line"
#define	DEF_PEN_VALUE_ANCHOR		"s"
#define	DEF_PEN_VALUE_COLOR		RGB_BLACK
#define	DEF_PEN_VALUE_FONT		STD_FONT_SMALL
#define	DEF_PEN_VALUE_FORMAT		"%g"
#define	DEF_PEN_VALUE_ROTATE		(char *)NULL
#define	DEF_PEN_VALUE_SHADOW		(char *)NULL
#define DEF_PEN_SHOW_VALUES		"no"

static Tk_ConfigSpec lineElemConfigSpecs[] =
{
    {TK_CONFIG_CUSTOM, "-activepen", "activePen", "ActivePen",
	DEF_LINE_ACTIVE_PEN, Tk_Offset(Line, activePenPtr),
	TK_CONFIG_NULL_OK, &bltLinePenOption},
    {TK_CONFIG_CUSTOM, "-areapattern", "areaPattern", "AreaPattern",
        DEF_LINE_PATTERN, Tk_Offset(Line, fillStipple), 
        TK_CONFIG_NULL_OK, &patternOption},
    {TK_CONFIG_COLOR, "-areaforeground", "areaForeground", "areaForeground",
	DEF_LINE_PATTERN_FG, Tk_Offset(Line, fillFgColor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_COLOR, "-areabackground", "areaBackground", "areaBackground",
	DEF_LINE_PATTERN_BG, Tk_Offset(Line, fillBgColor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-areatile", "areaTile", "AreaTile",
	DEF_LINE_PATTERN_TILE, Tk_Offset(Line, fillTile), 
	TK_CONFIG_NULL_OK, &bltTileOption},
    {TK_CONFIG_CUSTOM, "-bindtags", "bindTags", "BindTags",
	DEF_LINE_TAGS, Tk_Offset(Line, tags),
	TK_CONFIG_NULL_OK, &bltListOption},
    {TK_CONFIG_COLOR, "-color", "color", "Color",
	DEF_LINE_PEN_COLOR, Tk_Offset(Line, builtinPen.traceColor),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-color", "color", "Color",
	DEF_LINE_PEN_MONO, Tk_Offset(Line, builtinPen.traceColor),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_CUSTOM, "-dashes", "dashes", "Dashes",
	DEF_LINE_DASHES, Tk_Offset(Line, builtinPen.traceDashes),
	TK_CONFIG_NULL_OK, &bltDashesOption},
    {TK_CONFIG_CUSTOM, "-data", "data", "Data",
	DEF_LINE_DATA, 0, 0, &bltDataPairsOption},
    {TK_CONFIG_CUSTOM, "-errorbarcolor", "errorBarColor", "ErrorBarColor",
	DEF_LINE_ERRORBAR_COLOR, Tk_Offset(Line, builtinPen.errorBarColor), 
	0, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-errorbarwidth", "errorBarWidth", "ErrorBarWidth",
	DEF_LINE_ERRORBAR_LINE_WIDTH, 
	Tk_Offset(Line, builtinPen.errorBarLineWidth),
        TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-errorbarcap", "errorBarCap", "ErrorBarCap", 
	DEF_LINE_ERRORBAR_CAP_WIDTH, 
	Tk_Offset(Line, builtinPen.errorBarCapWidth),
        TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-fill", "fill", "Fill",
	DEF_LINE_FILL_COLOR, Tk_Offset(Line, builtinPen.symbol.fillColor),
	TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-fill", "fill", "Fill",
	DEF_LINE_FILL_MONO, Tk_Offset(Line, builtinPen.symbol.fillColor),
	TK_CONFIG_NULL_OK | TK_CONFIG_MONO_ONLY, &bltColorOption},
    {TK_CONFIG_BOOLEAN, "-hide", "hide", "Hide",
	DEF_LINE_HIDE, Tk_Offset(Line, hidden), TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_STRING, "-label", "label", "Label",
	(char *)NULL, Tk_Offset(Line, label), TK_CONFIG_NULL_OK},
    {TK_CONFIG_RELIEF, "-labelrelief", "labelRelief", "LabelRelief",
	DEF_LINE_LABEL_RELIEF, Tk_Offset(Line, labelRelief),
	TK_CONFIG_DONT_SET_DEFAULT}, 
    {TK_CONFIG_CUSTOM, "-linewidth", "lineWidth", "LineWidth",
	DEF_LINE_PEN_WIDTH, Tk_Offset(Line, builtinPen.traceWidth),
        TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-mapx", "mapX", "MapX",
        DEF_LINE_AXIS_X, Tk_Offset(Line, axes.x), 0, &bltXAxisOption},
    {TK_CONFIG_CUSTOM, "-mapy", "mapY", "MapY",
	DEF_LINE_AXIS_Y, Tk_Offset(Line, axes.y), 0, &bltYAxisOption},
    {TK_CONFIG_CUSTOM, "-maxsymbols", "maxSymbols", "MaxSymbols",
	DEF_LINE_MAX_SYMBOLS, Tk_Offset(Line, reqMaxSymbols),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-offdash", "offDash", "OffDash",
	DEF_LINE_OFFDASH_COLOR, Tk_Offset(Line, builtinPen.traceOffColor),
	TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-offdash", "offDash", "OffDash",
	DEF_LINE_OFFDASH_MONO, Tk_Offset(Line, builtinPen.traceOffColor),
	TK_CONFIG_NULL_OK | TK_CONFIG_MONO_ONLY, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-outline", "outline", "Outline",
	DEF_LINE_OUTLINE_COLOR, Tk_Offset(Line, builtinPen.symbol.outlineColor),
	TK_CONFIG_COLOR_ONLY, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-outline", "outline", "Outline",
	DEF_LINE_OUTLINE_MONO, Tk_Offset(Line, builtinPen.symbol.outlineColor),
	TK_CONFIG_MONO_ONLY, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-outlinewidth", "outlineWidth", "OutlineWidth",
	DEF_LINE_OUTLINE_WIDTH, Tk_Offset(Line, builtinPen.symbol.outlineWidth),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-pen", "pen", "Pen",
	(char *)NULL, Tk_Offset(Line, normalPenPtr),
	TK_CONFIG_NULL_OK, &bltLinePenOption},
    {TK_CONFIG_CUSTOM, "-pixels", "pixels", "Pixels",
	DEF_LINE_PIXELS, Tk_Offset(Line, builtinPen.symbol.size),
	GRAPH | STRIPCHART, &bltDistanceOption},
    {TK_CONFIG_DOUBLE, "-reduce", "reduce", "Reduce",
	DEF_LINE_REDUCE, Tk_Offset(Line, rTolerance),
	GRAPH | STRIPCHART | TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_BOOLEAN, "-scalesymbols", "scaleSymbols", "ScaleSymbols",
	DEF_LINE_SCALE_SYMBOLS, Tk_Offset(Line, scaleSymbols),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-showerrorbars", "showErrorBars", "ShowErrorBars",
	DEF_LINE_SHOW_ERRORBARS, Tk_Offset(Line, builtinPen.errorBarShow),
	TK_CONFIG_DONT_SET_DEFAULT, &bltFillOption},
    {TK_CONFIG_CUSTOM, "-showvalues", "showValues", "ShowValues",
	DEF_PEN_SHOW_VALUES, Tk_Offset(Line, builtinPen.valueShow),
	TK_CONFIG_DONT_SET_DEFAULT, &bltFillOption},
    {TK_CONFIG_CUSTOM, "-smooth", "smooth", "Smooth",
	DEF_LINE_SMOOTH, Tk_Offset(Line, reqSmooth),
	TK_CONFIG_DONT_SET_DEFAULT, &smoothOption},
    {TK_CONFIG_CUSTOM, "-state", "state", "State",
	DEF_LINE_STATE, Tk_Offset(Line, state), 
	TK_CONFIG_DONT_SET_DEFAULT, &bltStateOption},
    {TK_CONFIG_CUSTOM, "-styles", "styles", "Styles",
	DEF_LINE_STYLES, Tk_Offset(Line, palette), 
        TK_CONFIG_NULL_OK, &stylesOption},
    {TK_CONFIG_CUSTOM, "-symbol", "symbol", "Symbol",
	DEF_LINE_SYMBOL, Tk_Offset(Line, builtinPen.symbol),
	TK_CONFIG_DONT_SET_DEFAULT, &symbolOption},
    {TK_CONFIG_CUSTOM, "-trace", "trace", "Trace",
	DEF_LINE_PEN_DIRECTION, Tk_Offset(Line, penDir),
	TK_CONFIG_DONT_SET_DEFAULT, &penDirOption},
    {TK_CONFIG_ANCHOR, "-valueanchor", "valueAnchor", "ValueAnchor",
	DEF_PEN_VALUE_ANCHOR, 
        Tk_Offset(Line, builtinPen.valueStyle.anchor), 0},
    {TK_CONFIG_COLOR, "-valuecolor", "valueColor", "ValueColor",
	DEF_PEN_VALUE_COLOR, Tk_Offset(Line, builtinPen.valueStyle.color), 0},
    {TK_CONFIG_FONT, "-valuefont", "valueFont", "ValueFont",
	DEF_PEN_VALUE_FONT, Tk_Offset(Line, builtinPen.valueStyle.font), 0},
    {TK_CONFIG_STRING, "-valueformat", "valueFormat", "ValueFormat",
	DEF_PEN_VALUE_FORMAT, Tk_Offset(Line, builtinPen.valueFormat),
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_DOUBLE, "-valuerotate", "valueRotate", "ValueRotate",
	DEF_PEN_VALUE_ROTATE, Tk_Offset(Line, builtinPen.valueStyle.theta), 0},
    {TK_CONFIG_CUSTOM, "-valueshadow", "valueShadow", "ValueShadow",
	DEF_PEN_VALUE_SHADOW, Tk_Offset(Line, builtinPen.valueStyle.shadow),
	0, &bltShadowOption},
    {TK_CONFIG_CUSTOM, "-weights", "weights", "Weights",
	(char *)NULL, Tk_Offset(Line, w), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-x", "xData", "XData",
	(char *)NULL, Tk_Offset(Line, x), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-xdata", "xData", "XData",
	(char *)NULL, Tk_Offset(Line, x), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-xerror", "xError", "XError", 
        (char *)NULL, Tk_Offset(Line, xError), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-xhigh", "xHigh", "XHigh", 
        (char *)NULL, Tk_Offset(Line, xHigh), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-xlow", "xLow", "XLow", 
        (char *)NULL, Tk_Offset(Line, xLow), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-y", "yData", "YData", 
	(char *)NULL, Tk_Offset(Line, y), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-ydata", "yData", "YData",
	(char *)NULL, Tk_Offset(Line, y), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-yerror", "yError", "YError", 
        (char *)NULL, Tk_Offset(Line, yError), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-yhigh", "yHigh", "YHigh", 
        (char *)NULL, Tk_Offset(Line, yHigh), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-ylow", "yLow", "YLow", 
        (char *)NULL, Tk_Offset(Line, yLow), 0, &bltDataOption},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};


static Tk_ConfigSpec stripElemConfigSpecs[] =
{
    {TK_CONFIG_CUSTOM, "-activepen", "activePen", "ActivePen",
	DEF_LINE_ACTIVE_PEN, Tk_Offset(Line, activePenPtr),
	TK_CONFIG_NULL_OK, &bltLinePenOption},
    {TK_CONFIG_CUSTOM, "-bindtags", "bindTags", "BindTags",
	DEF_LINE_TAGS, Tk_Offset(Line, tags),
	TK_CONFIG_NULL_OK, &bltListOption},
    {TK_CONFIG_COLOR, "-color", "color", "Color",
	DEF_LINE_PEN_COLOR, Tk_Offset(Line, builtinPen.traceColor),
	TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-color", "color", "Color",
	DEF_LINE_PEN_MONO, Tk_Offset(Line, builtinPen.traceColor),
	TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_CUSTOM, "-dashes", "dashes", "Dashes",
	DEF_LINE_DASHES, Tk_Offset(Line, builtinPen.traceDashes),
	TK_CONFIG_NULL_OK, &bltDashesOption},
    {TK_CONFIG_CUSTOM, "-data", "data", "Data",
	DEF_LINE_DATA, 0, 0, &bltDataPairsOption},
    {TK_CONFIG_CUSTOM, "-errorbarcolor", "errorBarColor", "ErrorBarColor",
	DEF_LINE_ERRORBAR_COLOR, Tk_Offset(Line, builtinPen.errorBarColor), 
	0, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-errorbarwidth", "errorBarWidth", "ErrorBarWidth",
	DEF_LINE_ERRORBAR_LINE_WIDTH, 
	Tk_Offset(Line, builtinPen.errorBarLineWidth),
        TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-errorbarcap", "errorBarCap", 
	"ErrorBarCap", DEF_LINE_ERRORBAR_CAP_WIDTH, 
	Tk_Offset(Line, builtinPen.errorBarCapWidth),
        TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-fill", "fill", "Fill",
	DEF_LINE_FILL_COLOR, Tk_Offset(Line, builtinPen.symbol.fillColor),
	TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-fill", "fill", "Fill",
	DEF_LINE_FILL_MONO, Tk_Offset(Line, builtinPen.symbol.fillColor),
	TK_CONFIG_NULL_OK | TK_CONFIG_MONO_ONLY, &bltColorOption},
    {TK_CONFIG_BOOLEAN, "-hide", "hide", "Hide",
	DEF_LINE_HIDE, Tk_Offset(Line, hidden), TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_STRING, "-label", "label", "Label",
	(char *)NULL, Tk_Offset(Line, label), TK_CONFIG_NULL_OK},
    {TK_CONFIG_RELIEF, "-labelrelief", "labelRelief", "LabelRelief",
	DEF_LINE_LABEL_RELIEF, Tk_Offset(Line, labelRelief),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-linewidth", "lineWidth", "LineWidth",
        DEF_LINE_PEN_WIDTH, Tk_Offset(Line, builtinPen.traceWidth), 
        TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-mapx", "mapX", "MapX",
	DEF_LINE_AXIS_X, Tk_Offset(Line, axes.x), 0, &bltXAxisOption},
    {TK_CONFIG_CUSTOM, "-mapy", "mapY", "MapY",
	DEF_LINE_AXIS_Y, Tk_Offset(Line, axes.y), 0, &bltYAxisOption},
    {TK_CONFIG_CUSTOM, "-maxsymbols", "maxSymbols", "MaxSymbols",
	DEF_LINE_MAX_SYMBOLS, Tk_Offset(Line, reqMaxSymbols),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-offdash", "offDash", "OffDash",
	DEF_LINE_OFFDASH_COLOR, Tk_Offset(Line, builtinPen.traceOffColor),
	TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-offdash", "offDash", "OffDash",
	DEF_LINE_OFFDASH_MONO, Tk_Offset(Line, builtinPen.traceOffColor),
	TK_CONFIG_NULL_OK | TK_CONFIG_MONO_ONLY, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-outline", "outline", "Outline",
	DEF_LINE_OUTLINE_COLOR, Tk_Offset(Line, builtinPen.symbol.outlineColor),
	TK_CONFIG_COLOR_ONLY, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-outline", "outline", "Outline",
	DEF_LINE_OUTLINE_MONO, Tk_Offset(Line, builtinPen.symbol.outlineColor),
	TK_CONFIG_MONO_ONLY, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-outlinewidth", "outlineWidth", "OutlineWidth",
	DEF_LINE_OUTLINE_WIDTH, Tk_Offset(Line, builtinPen.symbol.outlineWidth),
	TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-pen", "pen", "Pen",
	(char *)NULL, Tk_Offset(Line, normalPenPtr), 
        TK_CONFIG_NULL_OK, &bltLinePenOption},
    {TK_CONFIG_CUSTOM, "-pixels", "pixels", "Pixels",
        DEF_LINE_PIXELS, Tk_Offset(Line, builtinPen.symbol.size), 0,
	&bltDistanceOption},
    {TK_CONFIG_BOOLEAN, "-scalesymbols", "scaleSymbols", "ScaleSymbols",
	DEF_LINE_SCALE_SYMBOLS, Tk_Offset(Line, scaleSymbols),
	TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-showerrorbars", "showErrorBars", "ShowErrorBars",
	DEF_LINE_SHOW_ERRORBARS, Tk_Offset(Line, builtinPen.errorBarShow),
	TK_CONFIG_DONT_SET_DEFAULT, &bltFillOption},
    {TK_CONFIG_CUSTOM, "-showvalues", "showValues", "ShowValues",
	DEF_PEN_SHOW_VALUES, Tk_Offset(Line, builtinPen.valueShow),
	TK_CONFIG_DONT_SET_DEFAULT, &bltFillOption},
    {TK_CONFIG_CUSTOM, "-smooth", "smooth", "Smooth",
	DEF_LINE_SMOOTH, Tk_Offset(Line, reqSmooth),
	TK_CONFIG_DONT_SET_DEFAULT, &smoothOption},
    {TK_CONFIG_CUSTOM, "-styles", "styles", "Styles",
	DEF_LINE_STYLES, Tk_Offset(Line, palette), 
	TK_CONFIG_NULL_OK, &stylesOption},
    {TK_CONFIG_CUSTOM, "-symbol", "symbol", "Symbol",
	DEF_LINE_SYMBOL, Tk_Offset(Line, builtinPen.symbol),
	TK_CONFIG_DONT_SET_DEFAULT, &symbolOption},
    {TK_CONFIG_ANCHOR, "-valueanchor", "valueAnchor", "ValueAnchor",
	DEF_PEN_VALUE_ANCHOR, 
        Tk_Offset(Line, builtinPen.valueStyle.anchor), 0},
    {TK_CONFIG_COLOR, "-valuecolor", "valueColor", "ValueColor",
	DEF_PEN_VALUE_COLOR, Tk_Offset(Line, builtinPen.valueStyle.color), 0},
    {TK_CONFIG_FONT, "-valuefont", "valueFont", "ValueFont",
	DEF_PEN_VALUE_FONT, Tk_Offset(Line, builtinPen.valueStyle.font), 0},
    {TK_CONFIG_STRING, "-valueformat", "valueFormat", "ValueFormat",
	DEF_PEN_VALUE_FORMAT, Tk_Offset(Line, builtinPen.valueFormat),
	TK_CONFIG_NULL_OK},
    {TK_CONFIG_DOUBLE, "-valuerotate", "valueRotate", "ValueRotate",
	DEF_PEN_VALUE_ROTATE, Tk_Offset(Line, builtinPen.valueStyle.theta), 0},
    {TK_CONFIG_CUSTOM, "-valueshadow", "valueShadow", "ValueShadow",
	DEF_PEN_VALUE_SHADOW, Tk_Offset(Line, builtinPen.valueStyle.shadow), 0,
	&bltShadowOption},
    {TK_CONFIG_CUSTOM, "-weights", "weights", "Weights",
	(char *)NULL, Tk_Offset(Line, w), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-x", "xData", "XData",
	(char *)NULL, Tk_Offset(Line, x), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-xdata", "xData", "XData",
	(char *)NULL, Tk_Offset(Line, x), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-y", "yData", "YData",
	(char *)NULL, Tk_Offset(Line, y), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-xerror", "xError", "XError", (char *)NULL, 
	Tk_Offset(Line, xError), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-ydata", "yData", "YData",
	(char *)NULL, Tk_Offset(Line, y), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-yerror", "yError", "YError", (char *)NULL, 
	Tk_Offset(Line, yError), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-xhigh", "xHigh", "XHigh", (char *)NULL, 
	Tk_Offset(Line, xHigh), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-xlow", "xLow", "XLow", (char *)NULL, 
	Tk_Offset(Line, xLow), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-yhigh", "yHigh", "YHigh", (char *)NULL, 
	Tk_Offset(Line, xHigh), 0, &bltDataOption},
    {TK_CONFIG_CUSTOM, "-ylow", "yLow", "YLow", (char *)NULL, 
	Tk_Offset(Line, yLow), 0, &bltDataOption},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};

static Tk_ConfigSpec linePenConfigSpecs[] =
{
    {TK_CONFIG_COLOR, "-color", "color", "Color",
	DEF_PEN_ACTIVE_COLOR, Tk_Offset(LinePen, traceColor),
	TK_CONFIG_COLOR_ONLY | ACTIVE_PEN},
    {TK_CONFIG_COLOR, "-color", "color", "Color",
	DEF_PEN_ACTIVE_MONO, Tk_Offset(LinePen, traceColor),
	TK_CONFIG_MONO_ONLY | ACTIVE_PEN},
    {TK_CONFIG_COLOR, "-color", "color", "Color",
	DEF_PEN_NORMAL_COLOR, Tk_Offset(LinePen, traceColor),
	TK_CONFIG_COLOR_ONLY | NORMAL_PEN},
    {TK_CONFIG_COLOR, "-color", "color", "Color",
	DEF_PEN_NORMAL_MONO, Tk_Offset(LinePen, traceColor),
	TK_CONFIG_MONO_ONLY | NORMAL_PEN},
    {TK_CONFIG_CUSTOM, "-dashes", "dashes", "Dashes",
	DEF_PEN_DASHES, Tk_Offset(LinePen, traceDashes),
	TK_CONFIG_NULL_OK | ALL_PENS, &bltDashesOption},
    {TK_CONFIG_CUSTOM, "-errorbarcolor", "errorBarColor", "ErrorBarColor",
	DEF_LINE_ERRORBAR_COLOR, Tk_Offset(LinePen, errorBarColor), 
	ALL_PENS, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-errorbarwidth", "errorBarWidth", "ErrorBarWidth",
	DEF_LINE_ERRORBAR_LINE_WIDTH, Tk_Offset(LinePen, errorBarLineWidth),
        ALL_PENS | TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-errorbarcap", "errorBarCap", 
	"ErrorBarCap", DEF_LINE_ERRORBAR_CAP_WIDTH, 
	Tk_Offset(LinePen, errorBarCapWidth),
        TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-fill", "fill", "Fill",
	DEF_PEN_FILL_COLOR, Tk_Offset(LinePen, symbol.fillColor),
	TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY | ALL_PENS, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-fill", "fill", "Fill",
	DEF_PEN_FILL_MONO, Tk_Offset(LinePen, symbol.fillColor),
	TK_CONFIG_NULL_OK | TK_CONFIG_MONO_ONLY | ALL_PENS, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-linewidth", "lineWidth", "LineWidth",
	(char *)NULL, Tk_Offset(LinePen, traceWidth), 
	ALL_PENS | TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-offdash", "offDash", "OffDash",
	DEF_PEN_OFFDASH_COLOR, Tk_Offset(LinePen, traceOffColor),
	TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY | ALL_PENS, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-offdash", "offDash", "OffDash",
	DEF_PEN_OFFDASH_MONO, Tk_Offset(LinePen, traceOffColor),
	TK_CONFIG_NULL_OK | TK_CONFIG_MONO_ONLY | ALL_PENS, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-outline", "outline", "Outline",
	DEF_PEN_OUTLINE_COLOR, Tk_Offset(LinePen, symbol.outlineColor),
	TK_CONFIG_COLOR_ONLY | ALL_PENS, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-outline", "outline", "Outline",
	DEF_PEN_OUTLINE_MONO, Tk_Offset(LinePen, symbol.outlineColor),
	TK_CONFIG_MONO_ONLY | ALL_PENS, &bltColorOption},
    {TK_CONFIG_CUSTOM, "-outlinewidth", "outlineWidth", "OutlineWidth",
	DEF_PEN_OUTLINE_WIDTH, Tk_Offset(LinePen, symbol.outlineWidth),
	TK_CONFIG_DONT_SET_DEFAULT | ALL_PENS, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-pixels", "pixels", "Pixels",
	DEF_PEN_PIXELS, Tk_Offset(LinePen, symbol.size),
	ALL_PENS, &bltDistanceOption},
    {TK_CONFIG_CUSTOM, "-showerrorbars", "showErrorBars", "ShowErrorBars",
	DEF_LINE_SHOW_ERRORBARS, Tk_Offset(LinePen, errorBarShow),
	TK_CONFIG_DONT_SET_DEFAULT, &bltFillOption},
    {TK_CONFIG_CUSTOM, "-showvalues", "showValues", "ShowValues",
	DEF_PEN_SHOW_VALUES, Tk_Offset(LinePen, valueShow),
	ALL_PENS | TK_CONFIG_DONT_SET_DEFAULT, &bltFillOption},
    {TK_CONFIG_CUSTOM, "-symbol", "symbol", "Symbol",
	DEF_PEN_SYMBOL, Tk_Offset(LinePen, symbol),
	TK_CONFIG_DONT_SET_DEFAULT | ALL_PENS, &symbolOption},
    {TK_CONFIG_STRING, "-type", (char *)NULL, (char *)NULL,
	DEF_PEN_TYPE, Tk_Offset(Pen, typeId), ALL_PENS | TK_CONFIG_NULL_OK},
    {TK_CONFIG_ANCHOR, "-valueanchor", "valueAnchor", "ValueAnchor",
	DEF_PEN_VALUE_ANCHOR, Tk_Offset(LinePen, valueStyle.anchor), ALL_PENS},
    {TK_CONFIG_COLOR, "-valuecolor", "valueColor", "ValueColor",
	DEF_PEN_VALUE_COLOR, Tk_Offset(LinePen, valueStyle.color), ALL_PENS},
    {TK_CONFIG_FONT, "-valuefont", "valueFont", "ValueFont",
	DEF_PEN_VALUE_FONT, Tk_Offset(LinePen, valueStyle.font), ALL_PENS},
    {TK_CONFIG_STRING, "-valueformat", "valueFormat", "ValueFormat",
	DEF_PEN_VALUE_FORMAT, Tk_Offset(LinePen, valueFormat),
	ALL_PENS | TK_CONFIG_NULL_OK},
    {TK_CONFIG_DOUBLE, "-valuerotate", "valueRotate", "ValueRotate",
	DEF_PEN_VALUE_ROTATE, Tk_Offset(LinePen, valueStyle.theta), ALL_PENS},
    {TK_CONFIG_CUSTOM, "-valueshadow", "valueShadow", "ValueShadow",
	DEF_PEN_VALUE_SHADOW, Tk_Offset(LinePen, valueStyle.shadow),
	ALL_PENS, &bltShadowOption},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};

typedef double (DistanceProc) _ANSI_ARGS_((int x, int y, Point2D *p, 
	Point2D *q, Point2D *t));

/* Forward declarations */
static PenConfigureProc ConfigurePen;
static PenDestroyProc DestroyPen;
static ElementClosestProc ClosestLine;
static ElementConfigProc ConfigureLine;
static ElementDestroyProc DestroyLine;
static ElementDrawProc DrawActiveLine;
static ElementDrawProc DrawNormalLine;
static ElementDrawSymbolProc DrawSymbol;
static ElementExtentsProc GetLineExtents;
static ElementToPostScriptProc ActiveLineToPostScript;
static ElementToPostScriptProc NormalLineToPostScript;
static ElementSymbolToPostScriptProc SymbolToPostScript;
static ElementMapProc MapLine;
static DistanceProc DistanceToY;
static DistanceProc DistanceToX;
static DistanceProc DistanceToLine;
static Blt_TileChangedProc TileChangedProc;

#ifdef _WIN32

static int tkpWinRopModes[] =
{
    R2_BLACK,			/* GXclear */
    R2_MASKPEN,			/* GXand */
    R2_MASKPENNOT,		/* GXandReverse */
    R2_COPYPEN,			/* GXcopy */
    R2_MASKNOTPEN,		/* GXandInverted */
    R2_NOT,			/* GXnoop */
    R2_XORPEN,			/* GXxor */
    R2_MERGEPEN,		/* GXor */
    R2_NOTMERGEPEN,		/* GXnor */
    R2_NOTXORPEN,		/* GXequiv */
    R2_NOT,			/* GXinvert */
    R2_MERGEPENNOT,		/* GXorReverse */
    R2_NOTCOPYPEN,		/* GXcopyInverted */
    R2_MERGENOTPEN,		/* GXorInverted */
    R2_NOTMASKPEN,		/* GXnand */
    R2_WHITE			/* GXset */
};

#endif

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

/*
 * ----------------------------------------------------------------------
 * 	Custom configuration option (parse and print) routines
 * ----------------------------------------------------------------------
 */

static int
StringToBitmap(
    Tcl_Interp *interp,
    Tk_Window tkwin,
    Symbol *symbolPtr,
    char *string)
{
    Pixmap bitmap, mask;
    char **elemArr;
    int nElems;
    int result;

    if (Tcl_SplitList(interp, string, &nElems, &elemArr) != TCL_OK) {
	return TCL_ERROR;
    }

    if (nElems > 2) {
	Tcl_AppendResult(interp, "too many elements in bitmap list \"", string,
		 "\": should be \"bitmap mask\"", (char *)NULL);
	result = TCL_ERROR;
	goto error;
    }
    mask = None;
    bitmap = Tk_GetBitmap(interp, tkwin, Tk_GetUid(elemArr[0]));
    if (bitmap == None) {
	result = TCL_BREAK;
	Tcl_ResetResult(interp);
	goto error;
    }
    if ((nElems > 1) && (elemArr[1][0] != '\0')) {
	mask = Tk_GetBitmap(interp, tkwin, Tk_GetUid(elemArr[1]));
	if (mask == None) {
	    Tk_FreeBitmap(Tk_Display(tkwin), bitmap);
	    result = TCL_ERROR;
	    goto error;
	}
    }
    Blt_Free(elemArr);
    if (symbolPtr->bitmap != None) {
	Tk_FreeBitmap(Tk_Display(tkwin), symbolPtr->bitmap);
    }
    symbolPtr->bitmap = bitmap;
    if (symbolPtr->mask != None) {
	Tk_FreeBitmap(Tk_Display(tkwin), symbolPtr->mask);
    }
    symbolPtr->mask = mask;
    return TCL_OK;
  error:
    Blt_Free(elemArr);
    return result;
}

/*ARGSUSED*/
static char *
PatternToString(
    ClientData clientData,	/* Not used. */
    Tk_Window tkwin,
    char *widgRec,		/* Element information record */
    int offset,			/* Offset of field in record */
    Tcl_FreeProc **freeProcPtr)	/* Not used. */
{
    Pixmap stipple = *(Pixmap *)(widgRec + offset);

    if (stipple == None) {
	return "";
    } 
    if (stipple == PATTERN_SOLID) {
	return "solid";
    } 
    return Tk_NameOfBitmap(Tk_Display(tkwin), stipple);
}

/*ARGSUSED*/
static int
StringToPattern(
    ClientData clientData,	/* Not used. */
    Tcl_Interp *interp,		/* Interpreter to send results back to */
    Tk_Window tkwin,		/* Not used. */
    char *string,		/* String representing field */
    char *widgRec,		/* Element information record */
    int offset)			/* Offset of field in record */
{
    Pixmap *stipplePtr = (Pixmap *)(widgRec + offset);
    Pixmap stipple;

    if ((string == NULL) || (string[0] == '\0')) {
	stipple = None;
    } else if (strcmp(string, "solid") == 0) {
	stipple = PATTERN_SOLID;
    } else {
	stipple = Tk_GetBitmap(interp, tkwin, Tk_GetUid(string));
	if (stipple == None) {
	    return TCL_ERROR;
	}
    }
    if ((*stipplePtr != None) && (*stipplePtr != PATTERN_SOLID)) {
	Tk_FreeBitmap(Tk_Display(tkwin), *stipplePtr);
    }
    *stipplePtr = stipple;
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * NameOfSymbol --
 *
 *	Converts the symbol value into its string representation.
 *
 * Results:
 *	The static string representing the symbol type is returned.
 *
 *----------------------------------------------------------------------
 */
static char *
NameOfSymbol(
    Symbol *symbolPtr)
{
    switch (symbolPtr->type) {
    case SYMBOL_NONE:
	return "none";
    case SYMBOL_SQUARE:
	return "square";
    case SYMBOL_CIRCLE:
	return "circle";
    case SYMBOL_DIAMOND:
	return "diamond";
    case SYMBOL_PLUS:
	return "plus";
    case SYMBOL_CROSS:
	return "cross";
    case SYMBOL_SPLUS:
	return "splus";
    case SYMBOL_SCROSS:
	return "scross";
    case SYMBOL_TRIANGLE:
	return "triangle";
    case SYMBOL_ARROW:
	return "arrow";
    case SYMBOL_BITMAP:
	return "bitmap";
    }
    return NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * StringToSymbol --
 *
 *	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
StringToSymbol(
    ClientData clientData,	/* Not used. */
    Tcl_Interp *interp,		/* Interpreter to send results back to */
    Tk_Window tkwin,		/* Not used. */
    char *string,		/* String representing symbol type */
    char *widgRec,		/* Element information record */
    int offset)			/* Offset of symbol type field in record */
{
    Symbol *symbolPtr = (Symbol *)(widgRec + offset);
    unsigned int length;
    char c;

    c = string[0];
    length = strlen(string);
    if (c == '\0') {
	symbolPtr->type = SYMBOL_NONE;
    } else if ((c == 'n') && (strncmp(string, "none", length) == 0)) {
	symbolPtr->type = SYMBOL_NONE;
    } else if ((c == 'c') && (length > 1) &&
	(strncmp(string, "circle", length) == 0)) {
	symbolPtr->type = SYMBOL_CIRCLE;
    } else if ((c == 's') && (length > 1) &&
	(strncmp(string, "square", length) == 0)) {
	symbolPtr->type = SYMBOL_SQUARE;
    } else if ((c == 'd') && (strncmp(string, "diamond", length) == 0)) {
	symbolPtr->type = SYMBOL_DIAMOND;
    } else if ((c == 'p') && (strncmp(string, "plus", length) == 0)) {
	symbolPtr->type = SYMBOL_PLUS;
    } else if ((c == 'c') && (length > 1) &&
	(strncmp(string, "cross", length) == 0)) {
	symbolPtr->type = SYMBOL_CROSS;
    } else if ((c == 's') && (length > 1) &&
	(strncmp(string, "splus", length) == 0)) {
	symbolPtr->type = SYMBOL_SPLUS;
    } else if ((c == 's') && (length > 1) &&
	(strncmp(string, "scross", length) == 0)) {
	symbolPtr->type = SYMBOL_SCROSS;
    } else if ((c == 't') && (strncmp(string, "triangle", length) == 0)) {
	symbolPtr->type = SYMBOL_TRIANGLE;
    } else if ((c == 'a') && (strncmp(string, "arrow", length) == 0)) {
	symbolPtr->type = SYMBOL_ARROW;
    } else {
	int result;

	result = StringToBitmap(interp, tkwin, symbolPtr, string);
	if (result != TCL_OK) {
	    if (result != TCL_ERROR) {
		Tcl_AppendResult(interp, "bad symbol \"", string, 
"\": should be \"none\", \"circle\", \"square\", \"diamond\", \"plus\", \
\"cross\", \"splus\", \"scross\", \"triangle\", \"arrow\" \
or the name of a bitmap", (char *)NULL);
	    }
	    return TCL_ERROR;
	}
	symbolPtr->type = SYMBOL_BITMAP;
    }
    return TCL_OK;
}

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

    if (symbolPtr->type == SYMBOL_BITMAP) {
	Tcl_DString dString;

	Tcl_DStringInit(&dString);
	Tcl_DStringAppendElement(&dString,
	    Tk_NameOfBitmap(Tk_Display(tkwin), symbolPtr->bitmap));
	Tcl_DStringAppendElement(&dString, (symbolPtr->mask == None) ? "" :
	    Tk_NameOfBitmap(Tk_Display(tkwin), symbolPtr->mask));
	result = Blt_Strdup(Tcl_DStringValue(&dString));
	Tcl_DStringFree(&dString);
	*freeProcPtr = (Tcl_FreeProc *)Blt_Free;
    } else {
	result = NameOfSymbol(symbolPtr);
    }
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * NameOfSmooth --
 *
 *	Converts the smooth value into its string representation.
 *
 * Results:
 *	The static string representing the smooth type is returned.
 *
 *----------------------------------------------------------------------
 */
static char *
NameOfSmooth(
    Smoothing value)
{
    if ((value < 0) || (value >= PEN_SMOOTH_LAST)) {
	return "unknown smooth value";
    }
    return smoothingInfo[value].name;
}

/*
 *----------------------------------------------------------------------
 *
 * StringToSmooth --
 *
 *	Convert the string representation of a line style or smooth name
 *	into its numeric form.
 *
 * Results:
 *	The return value is a standard Tcl result.  The smooth type is
 *	written into the widget record.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static int
StringToSmooth(
    ClientData clientData,	/* Not used. */
    Tcl_Interp *interp,		/* Interpreter to send results back to */
    Tk_Window tkwin,		/* Not used. */
    char *string,		/* String representing smooth type */
    char *widgRec,		/* Element information record */
    int offset)			/* Offset of smooth type field in record */
{
    Smoothing *valuePtr = (Smoothing *)(widgRec + offset);
    register SmoothingInfo *siPtr;

    for (siPtr = smoothingInfo; siPtr->name != NULL; siPtr++) {
	if (strcmp(string, siPtr->name) == 0) {
	    *valuePtr = siPtr->value;
	    return TCL_OK;
	}
    }
    Tcl_AppendResult(interp, "bad smooth value \"", string, "\": should be \
linear, step, natural, or quadratic", (char *)NULL);
    return TCL_ERROR;
}

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

    return NameOfSmooth(smooth);
}

/*
 *----------------------------------------------------------------------
 *
 * StringToPenDir --
 *
 *	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
StringToPenDir(
    ClientData clientData,	/* Not used. */
    Tcl_Interp *interp,		/* Interpreter to send results back to */
    Tk_Window tkwin,		/* Not used. */
    char *string,		/* String representing pen direction */
    char *widgRec,		/* Element information record */
    int offset)			/* Offset of pen direction field in record */
{
    int *penDirPtr = (int *)(widgRec + offset);
    unsigned int length;
    char c;

    c = string[0];
    length = strlen(string);
    if ((c == 'i') && (strncmp(string, "increasing", length) == 0)) {
	*penDirPtr = PEN_INCREASING;
    } else if ((c == 'd') && (strncmp(string, "decreasing", length) == 0)) {
	*penDirPtr = PEN_DECREASING;
    } else if ((c == 'b') && (strncmp(string, "both", length) == 0)) {
	*penDirPtr = PEN_BOTH_DIRECTIONS;
    } else {
	Tcl_AppendResult(interp, "bad trace value \"", string,
	    "\" : should be \"increasing\", \"decreasing\", or \"both\"",
	    (char *)NULL);
	return TCL_ERROR;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * NameOfPenDir --
 *
 *	Convert the pen direction into a string.
 *
 * Results:
 *	The static string representing the pen direction is returned.
 *
 *----------------------------------------------------------------------
 */
static char *
NameOfPenDir(
    int penDir)			/* Direction for pen drawing between points */
{
    switch (penDir) {
    case PEN_INCREASING:
	return "increasing";
    case PEN_DECREASING:
	return "decreasing";
    case PEN_BOTH_DIRECTIONS:
	return "both";
    default:
	return "unknown trace direction";
    }
}

/*
 *----------------------------------------------------------------------
 *
 * PenDirToString --
 *
 *	Convert the pen direction into a string.
 *
 * Results:
 *	The string representing the pen drawing direction is returned.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static char *
PenDirToString(
    ClientData clientData,	/* Not used. */
    Tk_Window tkwin,		/* Not used. */
    char *widgRec,		/* Element information record */
    int offset,			/* Offset of pen direction field in record */
    Tcl_FreeProc **freeProcPtr)	/* Not used. */
{
    int penDir = *(int *)(widgRec + offset);

    return NameOfPenDir(penDir);
}


/*
 * Clear the number of points and segments, in case there are no
 * segments or points
 */
static void
ClearPalette(
    Blt_Chain *palette)
{
    register LinePenStyle *stylePtr;
    Blt_ChainLink *linkPtr;

    for (linkPtr = Blt_ChainFirstLink(palette); linkPtr != NULL;
	 linkPtr = Blt_ChainNextLink(linkPtr)) {
	stylePtr = Blt_ChainGetValue(linkPtr);
	stylePtr->nStrips = stylePtr->nSymbolPts = 0;
	stylePtr->xErrorBarCnt = stylePtr->yErrorBarCnt = 0;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * ConfigurePen --
 *
 *	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
ConfigurePen(
    Graph *graphPtr,
    Pen *penPtr)
{
    LinePen *lpPtr = (LinePen *)penPtr;
    unsigned long gcMask;
    GC newGC;
    XGCValues gcValues;
    XColor *colorPtr;

    Blt_ResetTextStyle(graphPtr->tkwin, &(lpPtr->valueStyle));
    /*
     * Set the outline GC for this pen: GCForeground is outline color.
     * GCBackground is the fill color (only used for bitmap symbols).
     */
    gcMask = (GCLineWidth | GCForeground);
    colorPtr = lpPtr->symbol.outlineColor;
    if (colorPtr == COLOR_DEFAULT) {
	colorPtr = lpPtr->traceColor;
    }
    gcValues.foreground = colorPtr->pixel;
    if (lpPtr->symbol.type == SYMBOL_BITMAP) {
	colorPtr = lpPtr->symbol.fillColor;
	if (colorPtr == COLOR_DEFAULT) {
	    colorPtr = lpPtr->traceColor;
	}
	/*
	 * Set a clip mask if either
	 *	1) no background color was designated or
	 *	2) a masking bitmap was specified.
	 *
	 * These aren't necessarily the bitmaps we'll be using for
	 * clipping. But this makes it unlikely that anyone else will
	 * be sharing this GC when we set the clip origin (at the time
	 * the bitmap is drawn).
	 */
	if (colorPtr != NULL) {
	    gcValues.background = colorPtr->pixel;
	    gcMask |= GCBackground;
	    if (lpPtr->symbol.mask != None) {
		gcValues.clip_mask = lpPtr->symbol.mask;
		gcMask |= GCClipMask;
	    }
	} else {
	    gcValues.clip_mask = lpPtr->symbol.bitmap;
	    gcMask |= GCClipMask;
	}
    }
    gcValues.line_width = LineWidth(lpPtr->symbol.outlineWidth);
    newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues);
    if (lpPtr->symbol.outlineGC != NULL) {
	Tk_FreeGC(graphPtr->display, lpPtr->symbol.outlineGC);
    }
    lpPtr->symbol.outlineGC = newGC;

    /* Fill GC for symbols: GCForeground is fill color */

    gcMask = (GCLineWidth | GCForeground);
    colorPtr = lpPtr->symbol.fillColor;
    if (colorPtr == COLOR_DEFAULT) {
	colorPtr = lpPtr->traceColor;
    }
    newGC = NULL;
    if (colorPtr != NULL) {
	gcValues.foreground = colorPtr->pixel;
	newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues);
    }
    if (lpPtr->symbol.fillGC != NULL) {
	Tk_FreeGC(graphPtr->display, lpPtr->symbol.fillGC);
    }
    lpPtr->symbol.fillGC = newGC;

    /* Line segments */

    gcMask = (GCLineWidth | GCForeground | GCLineStyle | GCCapStyle |
	GCJoinStyle);
    gcValues.cap_style = CapButt;
    gcValues.join_style = JoinRound;
    gcValues.line_style = LineSolid;
    gcValues.line_width = LineWidth(lpPtr->traceWidth);

    colorPtr = lpPtr->traceOffColor;
    if (colorPtr == COLOR_DEFAULT) {
	colorPtr = lpPtr->traceColor;
    }
    if (colorPtr != NULL) {
	gcMask |= GCBackground;
	gcValues.background = colorPtr->pixel;
    }
    gcValues.foreground = lpPtr->traceColor->pixel;
    if (LineIsDashed(lpPtr->traceDashes)) {
	gcValues.line_width = lpPtr->traceWidth;
	gcValues.line_style = 
	    (colorPtr == NULL) ? LineOnOffDash : LineDoubleDash;
    }
    newGC = Blt_GetPrivateGC(graphPtr->tkwin, gcMask, &gcValues);
    if (lpPtr->traceGC != NULL) {
	Blt_FreePrivateGC(graphPtr->display, lpPtr->traceGC);
    }
    if (LineIsDashed(lpPtr->traceDashes)) {
	lpPtr->traceDashes.offset = lpPtr->traceDashes.values[0] / 2;
	Blt_SetDashes(graphPtr->display, newGC, &(lpPtr->traceDashes));
    }
    lpPtr->traceGC = newGC;

    gcMask = (GCLineWidth | GCForeground);
    colorPtr = lpPtr->errorBarColor;
    if (colorPtr == COLOR_DEFAULT) {
	colorPtr = lpPtr->traceColor;
    }
    gcValues.line_width = LineWidth(lpPtr->errorBarLineWidth);
    gcValues.foreground = colorPtr->pixel;
    newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues);
    if (lpPtr->errorBarGC != NULL) {
	Tk_FreeGC(graphPtr->display, lpPtr->errorBarGC);
    }
    lpPtr->errorBarGC = newGC;

    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyPen --
 *
 *	Release memory and resources allocated for the style.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the pen style is freed up.
 *
 *----------------------------------------------------------------------
 */
static void
DestroyPen(
    Graph *graphPtr,
    Pen *penPtr)
{
    LinePen *lpPtr = (LinePen *)penPtr;

    Blt_FreeTextStyle(graphPtr->display, &(lpPtr->valueStyle));
    /* TODO: Do this for all shadows.
    if (lpPtr->valueStyle.shadow.color != NULL) {
        Tk_FreeColor(lpPtr->valueStyle.shadow.color);
    }
    */
    if (lpPtr->symbol.outlineGC != NULL) {
	Tk_FreeGC(graphPtr->display, lpPtr->symbol.outlineGC);
    }
    if (lpPtr->symbol.fillGC != NULL) {
	Tk_FreeGC(graphPtr->display, lpPtr->symbol.fillGC);
    }
    if (lpPtr->errorBarGC != NULL) {
	Tk_FreeGC(graphPtr->display, lpPtr->errorBarGC);
    }
    if (lpPtr->traceGC != NULL) {
	Blt_FreePrivateGC(graphPtr->display, lpPtr->traceGC);
    }
    if (lpPtr->symbol.bitmap != None) {
	Tk_FreeBitmap(graphPtr->display, lpPtr->symbol.bitmap);
	lpPtr->symbol.bitmap = None;
    }
    if (lpPtr->symbol.mask != None) {
	Tk_FreeBitmap(graphPtr->display, lpPtr->symbol.mask);
	lpPtr->symbol.mask = None;
    }
}


static void
InitPen(
    LinePen *penPtr)
{
    Blt_InitTextStyle(&penPtr->valueStyle);
    penPtr->configProc = ConfigurePen;
    penPtr->configSpecs = linePenConfigSpecs;
    penPtr->destroyProc = DestroyPen;
    penPtr->errorBarLineWidth = 1;
    penPtr->errorBarShow = SHOW_BOTH;
    penPtr->flags = NORMAL_PEN;
    penPtr->name = "";
    penPtr->symbol.bitmap = penPtr->symbol.mask = None;
    penPtr->symbol.outlineColor = penPtr->symbol.fillColor = COLOR_DEFAULT;
    penPtr->symbol.outlineWidth = penPtr->traceWidth = 1;
    penPtr->symbol.type = SYMBOL_CIRCLE;
    penPtr->valueShow = SHOW_NONE;
}

Pen *
Blt_LinePen(
    char *penName)
{
    LinePen *penPtr;

    penPtr = Blt_Calloc(1, sizeof(LinePen));
    assert(penPtr);
    InitPen(penPtr);
    penPtr->name = Blt_Strdup(penName);
    if (strcmp(penName, "activeLine") == 0) {
	penPtr->flags = ACTIVE_PEN;
    }
    return (Pen *)penPtr;
}

/*
 * ----------------------------------------------------------------------
 *
 *	In this section, the routines deal with building and filling
 *	the element's data structures with transformed screen
 *	coordinates.  They are triggered from TranformLine which is
 *	called whenever the data or coordinates axes have changed and
 *	new screen coordinates need to be calculated.
 *
 * ----------------------------------------------------------------------
 */

/*
 *----------------------------------------------------------------------
 *
 * ScaleSymbol --
 *
 *	Returns the scaled size for the line element. Scaling depends
 *	upon when the base line ranges for the element were set and
 *	the current range of the graph.
 *
 * Results:
 *	The new size of the symbol, after considering how much the
 *	graph has been scaled, is returned.
 *
 *----------------------------------------------------------------------
 */
static int
ScaleSymbol(
    Element *elemPtr,
    int normalSize)
{
    int maxSize;
    double scale;
    int newSize;

    scale = 1.0;
    if (elemPtr->scaleSymbols) {
	double xRange, yRange;

	xRange = (elemPtr->axes.x->max - elemPtr->axes.x->min);
	yRange = (elemPtr->axes.y->max - elemPtr->axes.y->min);
	if (elemPtr->flags & SCALE_SYMBOL) {
	    /* Save the ranges as a baseline for future scaling. */
	    elemPtr->xRange = xRange;
	    elemPtr->yRange = yRange;
	    elemPtr->flags &= ~SCALE_SYMBOL;
	} else {
	    double xScale, yScale;

	    /* Scale the symbol by the smallest change in the X or Y axes */
	    xScale = elemPtr->xRange / xRange;
	    yScale = elemPtr->yRange / yRange;
	    scale = MIN(xScale, yScale);
	}
    }
    newSize = Round(normalSize * scale);

    /*
     * Don't let the size of symbols go unbounded. Both X and Win32
     * drawing routines assume coordinates to be a signed short int.
     */
    maxSize = (int)MIN(elemPtr->graphPtr->hRange, elemPtr->graphPtr->vRange);
    if (newSize > maxSize) {
	newSize = maxSize;
    }

    /* Make the symbol size odd so that its center is a single pixel. */
    newSize |= 0x01;
    return newSize;
}

/*
 *----------------------------------------------------------------------
 *
 * GetScreenPoints --
 *
 *	Generates a coordinate array of transformed screen coordinates
 *	from the data points.
 *
 * Results:
 *	The transformed screen coordinates are returned.
 *
 * Side effects:
 *	Memory is allocated for the coordinate array.
 *
 *----------------------------------------------------------------------
 */
static void
GetScreenPoints(
    Graph *graphPtr,
    Line *linePtr,
    MapInfo *mapPtr)
{
    double *x, *y;
    register int i, n;
    register int count;
    register Point2D *screenPts;
    register int *indices;

    n = NumberOfPoints(linePtr);
    x = linePtr->x.valueArr;
    y = linePtr->y.valueArr;
    screenPts = Blt_Malloc(sizeof(Point2D) * n);
    assert(screenPts);
    indices = Blt_Malloc(sizeof(int) * n);
    assert(indices);

    count = 0;			/* Count the valid screen coordinates */
    if (graphPtr->inverted) {
	for (i = 0; i < n; i++) {
	    if ((FINITE(x[i])) && (FINITE(y[i]))) {
 		screenPts[count].x = Blt_HMap(graphPtr, linePtr->axes.y, y[i]);
		screenPts[count].y = Blt_VMap(graphPtr, linePtr->axes.x, x[i]);
		indices[count] = i;
		count++;
	    }
	}
    } else {
	for (i = 0; i < n; i++) {
	    if ((FINITE(x[i])) && (FINITE(y[i]))) {
		screenPts[count].x = Blt_HMap(graphPtr, linePtr->axes.x, x[i]);
		screenPts[count].y = Blt_VMap(graphPtr, linePtr->axes.y, y[i]);
		indices[count] = i;
		count++;
	    }
	}
    }
    mapPtr->screenPts = screenPts;
    mapPtr->nScreenPts = count;
    mapPtr->indices = indices;
}

/*
 *----------------------------------------------------------------------
 *
 * ReducePoints --
 *
 *	Generates a coordinate array of transformed screen coordinates
 *	from the data points.
 *
 * Results:
 *	The transformed screen coordinates are returned.
 *
 * Side effects:
 *	Memory is allocated for the coordinate array.
 *
 *----------------------------------------------------------------------
 */
static void
ReducePoints(
    MapInfo *mapPtr,
    double tolerance)
{
    register int i, k, n;
    Point2D *screenPts;
    int *indices, *simple;

    simple	= Blt_Malloc(sizeof(int) * mapPtr->nScreenPts);
    indices	= Blt_Malloc(sizeof(int) * mapPtr->nScreenPts);
    screenPts	= Blt_Malloc(sizeof(Point2D) * mapPtr->nScreenPts);
    n = Blt_SimplifyLine(mapPtr->screenPts, 0, mapPtr->nScreenPts - 1, 
		 tolerance, simple);
    for (i = 0; i < n; i++) {
	k = simple[i];
	screenPts[i] = mapPtr->screenPts[k];
	indices[i] = mapPtr->indices[k];
    }
#ifdef notdef
    if (n < mapPtr->nScreenPts) {
	fprintf(stderr, "reduced from %d to %d\n", mapPtr->nScreenPts, n);
    }
#endif
    Blt_Free(mapPtr->screenPts);
    Blt_Free(mapPtr->indices);
    Blt_Free(simple);
    mapPtr->screenPts = screenPts;
    mapPtr->indices = indices;
    mapPtr->nScreenPts = n;
}

/*
 *----------------------------------------------------------------------
 *
 * GenerateSteps --
 *
 *	Resets the coordinate and pen index arrays adding new points
 *	for step-and-hold type smoothing.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The temporary arrays for screen coordinates and pen indices
 *	are updated.
 *
 *---------------------------------------------------------------------- 
 */
static void
GenerateSteps(
    MapInfo *mapPtr)
{
    int newSize;
    register int i, count;
    Point2D *screenPts;
    int *indices;

    newSize = ((mapPtr->nScreenPts - 1) * 2) + 1;
    screenPts = Blt_Malloc(newSize * sizeof(Point2D));
    assert(screenPts);
    indices = Blt_Malloc(sizeof(int) * newSize);
    assert(indices);

    screenPts[0] = mapPtr->screenPts[0];
    indices[0] = 0;

    count = 1;
    for (i = 1; i < mapPtr->nScreenPts; i++) {
	screenPts[count + 1] = mapPtr->screenPts[i];

	/* Hold last y-coordinate, use new x-coordinate */
	screenPts[count].x = screenPts[count + 1].x;
	screenPts[count].y = screenPts[count - 1].y;

	/* Use the same style for both the hold and the step points */
	indices[count] = indices[count + 1] = mapPtr->indices[i];
	count += 2;
    }
    Blt_Free(mapPtr->screenPts);
    Blt_Free(mapPtr->indices);
    mapPtr->indices = indices;
    mapPtr->screenPts = screenPts;
    mapPtr->nScreenPts = newSize;
}

/*
 *----------------------------------------------------------------------
 *
 * GenerateSpline --
 *
 *	Computes a spline based upon the data points, returning a new
 *	(larger) coordinate array or points.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The temporary arrays for screen coordinates and data indices
 *	are updated based upon spline.
 *
 * FIXME:  Can't interpolate knots along the Y-axis.   Need to break
 *	   up point array into interchangable X and Y vectors earlier.
 *	   Pass extents (left/right or top/bottom) as parameters.
 *
 *----------------------------------------------------------------------
 */
static void
GenerateSpline(
    Graph *graphPtr,
    Line *linePtr,
    MapInfo *mapPtr)
{
    int extra;
    register int i, j, count;
    Point2D *origPts, *intpPts;
    int *indices;
    int nIntpPts, nOrigPts;
    int result;
    int x;

    nOrigPts = mapPtr->nScreenPts;
    origPts = mapPtr->screenPts;
    assert(mapPtr->nScreenPts > 0);
    for (i = 0, j = 1; j < nOrigPts; i++, j++) {
	if (origPts[j].x <= origPts[i].x) {
	    return;		/* Points are not monotonically increasing */
	}
    }
    if (((origPts[0].x > (double)graphPtr->right)) ||
	((origPts[mapPtr->nScreenPts - 1].x < (double)graphPtr->left))) {
	return;			/* All points are clipped */
    }
    /*
     * The spline is computed in screen coordinates instead of data
     * points so that we can select the abscissas of the interpolated
     * points from each pixel horizontally across the plotting area.
     */
    extra = (graphPtr->right - graphPtr->left) + 1;
    if (extra < 1) {
	return;
    }
    nIntpPts = nOrigPts + extra + 1;
    intpPts = Blt_Malloc(nIntpPts * sizeof(Point2D));
    assert(intpPts);

    indices = Blt_Malloc(sizeof(int) * nIntpPts);
    assert(indices);

    /* Populate the x2 array with both the original X-coordinates and
     * extra X-coordinates for each horizontal pixel that the line
     * segment contains. */
    count = 0;
    for (i = 0, j = 1; j < nOrigPts; i++, j++) {

	/* Add the original x-coordinate */
	intpPts[count].x = origPts[i].x;

	/* Include the starting offset of the point in the offset array */
	indices[count] = mapPtr->indices[i];
	count++;

	/* Is any part of the interval (line segment) in the plotting
	 * area?  */
	if ((origPts[j].x >= (double)graphPtr->left) || 
	    (origPts[i].x <= (double)graphPtr->right)) {
	    int last;

	    x = (int)(origPts[i].x + 1.0);

	    /*
	     * Since the line segment may be partially clipped on the
	     * left or right side, the points to interpolate are
	     * always interior to the plotting area.
	     *
	     *           left			    right
	     *      x1----|--------------------------|---x2
	     *
	     * Pick the max of the starting X-coordinate and the
	     * left edge and the min of the last X-coordinate and
	     * the right edge.
	     */
	    x = MAX(x, graphPtr->left);
	    last = (int)MIN(origPts[j].x, graphPtr->right);

	    /* Add the extra x-coordinates to the interval. */
	    while (x < last) {
		indices[count] = mapPtr->indices[i];
		intpPts[count++].x = (double)x;
		x++;
	    }
	}
    }
    nIntpPts = count;
    result = FALSE;
    if (linePtr->smooth == PEN_SMOOTH_NATURAL) {
	result = Blt_NaturalSpline(origPts, nOrigPts, intpPts, nIntpPts);
    } else if (linePtr->smooth == PEN_SMOOTH_QUADRATIC) {
	result = Blt_QuadraticSpline(origPts, nOrigPts, intpPts, nIntpPts);
    }
    if (!result) {
	/* The spline interpolation failed.  We'll fallback to the
	 * current coordinates and do no smoothing (standard line
	 * segments).  */
	linePtr->smooth = PEN_SMOOTH_NONE;
	Blt_Free(intpPts);
	Blt_Free(indices);
    } else {
	Blt_Free(mapPtr->screenPts);
	Blt_Free(mapPtr->indices);
	mapPtr->indices = indices;
	mapPtr->screenPts = intpPts;
	mapPtr->nScreenPts = nIntpPts;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * GenerateParametricSpline --
 *
 *	Computes a spline based upon the data points, returning a new
 *	(larger) coordinate array or points.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	The temporary arrays for screen coordinates and data indices
 *	are updated based upon spline.
 *
 * FIXME:  Can't interpolate knots along the Y-axis.   Need to break
 *	   up point array into interchangable X and Y vectors earlier.
 *	   Pass extents (left/right or top/bottom) as parameters.
 *
 *----------------------------------------------------------------------
 */
static void
GenerateParametricSpline(
    Graph *graphPtr,
    Line *linePtr,
    MapInfo *mapPtr)
{
    Extents2D exts;
    Point2D *origPts, *intpPts;
    Point2D p, q;
    double dist;
    int *indices;
    int nIntpPts, nOrigPts;
    int result;
    register int i, j, count;

    nOrigPts = mapPtr->nScreenPts;
    origPts = mapPtr->screenPts;
    assert(mapPtr->nScreenPts > 0);

    Blt_GraphExtents(graphPtr, &exts);

    /* 
     * Populate the x2 array with both the original X-coordinates and
     * extra X-coordinates for each horizontal pixel that the line
     * segment contains. 
     */
    count = 1;
    for (i = 0, j = 1; j < nOrigPts; i++, j++) {
        p = origPts[i];
        q = origPts[j];
	count++;
        if (Blt_LineRectClip(&exts, &p, &q)) {
	    count += (int)(hypot(q.x - p.x, q.y - p.y) * 0.5);
	}
    }
    nIntpPts = count;
    intpPts = Blt_Malloc(nIntpPts * sizeof(Point2D));
    assert(intpPts);

    indices = Blt_Malloc(sizeof(int) * nIntpPts);
    assert(indices);

    /* 
     * FIXME: This is just plain wrong.  The spline should be computed
     *        and evaluated in separate steps.  This will mean breaking
     *	      up this routine since the catrom coefficients can be
     *	      independently computed for original data point.  This 
     *	      also handles the problem of allocating enough points 
     *	      since evaluation is independent of the number of points 
     *		to be evalualted.  The interpolated 
     *	      line segments should be clipped, not the original segments.
     */
    count = 0;
    for (i = 0, j = 1; j < nOrigPts; i++, j++) {
        p = origPts[i];
        q = origPts[j];

        dist = hypot(q.x - p.x, q.y - p.y);
        /* Add the original x-coordinate */
        intpPts[count].x = (double)i;
        intpPts[count].y = 0.0;

        /* Include the starting offset of the point in the offset array */
        indices[count] = mapPtr->indices[i];
        count++;

        /* Is any part of the interval (line segment) in the plotting
         * area?  */

        if (Blt_LineRectClip(&exts, &p, &q)) {
            double distP, distQ;

            distP = hypot(p.x - origPts[i].x, p.y - origPts[i].y);
            distQ = hypot(q.x - origPts[i].x, q.y - origPts[i].y);
            distP += 2.0;
            while(distP <= distQ) {
                /* Point is indicated by its interval and parameter t. */
                intpPts[count].x = (double)i;
                intpPts[count].y =  distP / dist;
                indices[count] = mapPtr->indices[i];
                count++;
                distP += 2.0;
            }
        }
    }
    intpPts[count].x = (double)i;
    intpPts[count].y = 0.0;
    indices[count] = mapPtr->indices[i];
    count++;
    nIntpPts = count;
    result = FALSE;
    if (linePtr->smooth == PEN_SMOOTH_NATURAL) {
        result = Blt_NaturalParametricSpline(origPts, nOrigPts, &exts, FALSE,
                            intpPts, nIntpPts);
    } else if (linePtr->smooth == PEN_SMOOTH_CATROM) {
        result = Blt_CatromParametricSpline(origPts, nOrigPts, intpPts,
                                            nIntpPts);
    }
    if (!result) {
        /* The spline interpolation failed.  We'll fallback to the
         * current coordinates and do no smoothing (standard line
         * segments).  */
        linePtr->smooth = PEN_SMOOTH_NONE;
        Blt_Free(intpPts);
        Blt_Free(indices);
    } else {
        Blt_Free(mapPtr->screenPts);
        Blt_Free(mapPtr->indices);
        mapPtr->indices = indices;
        mapPtr->screenPts = intpPts;
        mapPtr->nScreenPts = nIntpPts;
    }
}


/*
 *----------------------------------------------------------------------
 *
 * MapSymbols --
 *
 *	Creates two arrays of points and pen indices, filled with
 *	the screen coordinates of the visible
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory is freed and allocated for the index array.
 *
 *----------------------------------------------------------------------
 */
static void
MapSymbols(
    Graph *graphPtr,
    Line *linePtr,
    MapInfo *mapPtr)
{
    Extents2D exts;
    Point2D *symbolPts;
    int *indices;
    register int i, count;

    symbolPts = Blt_Malloc(sizeof(Point2D) * mapPtr->nScreenPts);
    assert(symbolPts);

    indices = Blt_Malloc(sizeof(int) * mapPtr->nScreenPts);
    assert(indices);

    Blt_GraphExtents(graphPtr, &exts);
    count = 0;			/* Count the number of visible points */

    for (i = 0; i < mapPtr->nScreenPts; i++) {
	if (PointInRegion(&exts, mapPtr->screenPts[i].x, 
		  mapPtr->screenPts[i].y)) {
	    symbolPts[count].x = mapPtr->screenPts[i].x;
	    symbolPts[count].y = mapPtr->screenPts[i].y;
	    indices[count] = mapPtr->indices[i];
	    count++;
	}
    }
    linePtr->symbolPts = symbolPts;
    linePtr->nSymbolPts = count;
    linePtr->symbolToData = indices;
}

/*
 *----------------------------------------------------------------------
 *
 * MapActiveSymbols --
 *
 *	Creates an array of points of the active graph coordinates.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory is freed and allocated for the active point array.
 *
 *----------------------------------------------------------------------
 */
static void
MapActiveSymbols(
    Graph *graphPtr,
    Line *linePtr)
{
    Extents2D exts;
    double x, y;
    int count;
    Point2D *activePts;
    register int i;
    int pointIndex;
    int nPoints;
    int *activeToData;

    if (linePtr->activePts != NULL) {
	Blt_Free(linePtr->activePts);
	linePtr->activePts = NULL;
    }
    if (linePtr->activeToData != NULL) {
	Blt_Free(linePtr->activeToData);
	linePtr->activeToData = NULL;
    }
    Blt_GraphExtents(graphPtr, &exts);
    activePts = Blt_Malloc(sizeof(Point2D) * linePtr->nActiveIndices);
    assert(activePts);
    activeToData = Blt_Malloc(sizeof(int) * linePtr->nActiveIndices);
    nPoints = NumberOfPoints(linePtr);
    count = 0;			/* Count the visible active points */
    for (i = 0; i < linePtr->nActiveIndices; i++) {
	pointIndex = linePtr->activeIndices[i];
	if (pointIndex >= nPoints) {
	    continue;		/* Index not available */
	}
	x = linePtr->x.valueArr[pointIndex];
	y = linePtr->y.valueArr[pointIndex];
	activePts[count] = Blt_Map2D(graphPtr, x, y, &(linePtr->axes));
	activeToData[count] = pointIndex;
	if (PointInRegion(&exts, activePts[count].x, activePts[count].y)) {
	    count++;
	}
    }
    if (count > 0) {
	linePtr->activePts = activePts;
	linePtr->activeToData = activeToData;
    } else {
	/* No active points were visible. */
	Blt_Free(activePts);
	Blt_Free(activeToData);	
    }
    linePtr->nActivePts = count;
    linePtr->flags &= ~ACTIVE_PENDING;
}

/*
 *----------------------------------------------------------------------
 *
 * MapStrip --
 *
 *	Creates an array of line segments of the graph coordinates.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory is  allocated for the line segment array.
 *
 *----------------------------------------------------------------------
 */
static void
MapStrip(
    Graph *graphPtr,
    Line *linePtr,
    MapInfo *mapPtr)
{
    Extents2D exts;
    Segment2D *strips;
    int *indices, *indexPtr;
    register Point2D *endPtr, *pointPtr;
    register Segment2D *segPtr;
    register int count;

    indices = Blt_Malloc(sizeof(int) * mapPtr->nScreenPts);
    assert(indices);

    /* 
     * Create array to hold points for line segments (not polyline 
     * coordinates).  So allocate twice the number of points.  
     */
    segPtr = strips = Blt_Malloc(mapPtr->nScreenPts * sizeof(Segment2D));
    assert(strips);

    Blt_GraphExtents(graphPtr, &exts);
    count = 0;			/* Count the number of segments. */
    indexPtr = mapPtr->indices;
    for (pointPtr = mapPtr->screenPts, 
	     endPtr = mapPtr->screenPts + (mapPtr->nScreenPts - 1);
	 pointPtr < endPtr; pointPtr++, indexPtr++) {
	segPtr->p = pointPtr[0];
	segPtr->q = pointPtr[1];
	if (Blt_LineRectClip(&exts, &segPtr->p, &segPtr->q)) {
	    segPtr++;
	    indices[count] = *indexPtr;
	    count++;
	}
    }
    linePtr->stripToData = indices;
    linePtr->nStrips = count;
    linePtr->strips = strips;
}

/*
 *----------------------------------------------------------------------
 *
 * MergePens --
 *
 *	Reorders the both arrays of points and segments to merge pens.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The old arrays are freed and new ones allocated containing
 *	the reordered points and segments.
 *
 *----------------------------------------------------------------------
 */
static void
MergePens(
    Line *linePtr,
    PenStyle **dataToStyle)
{
    LinePenStyle *stylePtr;
    register int i;
    Blt_ChainLink *linkPtr;

    if (Blt_ChainGetLength(linePtr->palette) < 2) {
	linkPtr = Blt_ChainFirstLink(linePtr->palette);
	stylePtr = Blt_ChainGetValue(linkPtr);
	stylePtr->nStrips = linePtr->nStrips;
	stylePtr->strips = linePtr->strips;
	stylePtr->nSymbolPts = linePtr->nSymbolPts;
	stylePtr->symbolPts = linePtr->symbolPts;
	stylePtr->xErrorBarCnt = linePtr->xErrorBarCnt;
	stylePtr->yErrorBarCnt = linePtr->yErrorBarCnt;
	stylePtr->xErrorBars = linePtr->xErrorBars;
	stylePtr->yErrorBars = linePtr->yErrorBars;
	stylePtr->errorBarCapWidth = linePtr->errorBarCapWidth;
	return;
    }

    /* We have more than one style. Group line segments and points of
     * like pen styles.  */

    if (linePtr->nStrips > 0) {
	Segment2D *strips;
	int *stripToData;
	register Segment2D *segPtr;
	register int *indexPtr;
	int dataIndex;

	strips = Blt_Malloc(linePtr->nStrips * sizeof(Segment2D));
	stripToData = Blt_Malloc(linePtr->nStrips * sizeof(int));
	assert(strips && stripToData);
	segPtr = strips, indexPtr = stripToData;
	for (linkPtr = Blt_ChainFirstLink(linePtr->palette); linkPtr != NULL;
	     linkPtr = Blt_ChainNextLink(linkPtr)) {
	    stylePtr = Blt_ChainGetValue(linkPtr);
	    stylePtr->strips = segPtr;
	    for (i = 0; i < linePtr->nStrips; i++) {
		dataIndex = linePtr->stripToData[i];
		if (dataToStyle[dataIndex] == (PenStyle *)stylePtr) {
		    *segPtr++ = linePtr->strips[i];
		    *indexPtr++ = dataIndex;
		}
	    }
	    stylePtr->nStrips = segPtr - stylePtr->strips;
	}
	Blt_Free(linePtr->strips);
	linePtr->strips = strips;
	Blt_Free(linePtr->stripToData);
	linePtr->stripToData = stripToData;
    }
    if (linePtr->nSymbolPts > 0) {
	int *indexPtr;
	register Point2D *symbolPts, *pointPtr;
	register int *symbolToData;
	int dataIndex;

	symbolPts = Blt_Malloc(linePtr->nSymbolPts * sizeof(Point2D));
	symbolToData = Blt_Malloc(linePtr->nSymbolPts * sizeof(int));
	assert(symbolPts && symbolToData);
	pointPtr = symbolPts, indexPtr = symbolToData;
	for (linkPtr = Blt_ChainFirstLink(linePtr->palette); linkPtr != NULL;
	     linkPtr = Blt_ChainNextLink(linkPtr)) {
	    stylePtr = Blt_ChainGetValue(linkPtr);
	    stylePtr->symbolPts = pointPtr;
	    for (i = 0; i < linePtr->nSymbolPts; i++) {
		dataIndex = linePtr->symbolToData[i];
		if (dataToStyle[dataIndex] == (PenStyle *)stylePtr) {
		    *pointPtr++ = linePtr->symbolPts[i];
		    *indexPtr++ = dataIndex;
		}
	    }
	    stylePtr->nSymbolPts = pointPtr - stylePtr->symbolPts;
	}
	Blt_Free(linePtr->symbolPts);
	linePtr->symbolPts = symbolPts;
	Blt_Free(linePtr->symbolToData);
	linePtr->symbolToData = symbolToData;
    }
    if (linePtr->xErrorBarCnt > 0) {
	Segment2D *xErrorBars, *segPtr;
	int *xErrorToData, *indexPtr;
	int dataIndex;

	xErrorBars = Blt_Malloc(linePtr->xErrorBarCnt * sizeof(Segment2D));
	xErrorToData = Blt_Malloc(linePtr->xErrorBarCnt * sizeof(int));
	assert(xErrorBars);
	segPtr = xErrorBars, indexPtr = xErrorToData;
	for (linkPtr = Blt_ChainFirstLink(linePtr->palette); 
	     linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	    stylePtr = Blt_ChainGetValue(linkPtr);
	    stylePtr->xErrorBars = segPtr;
	    for (i = 0; i < linePtr->xErrorBarCnt; i++) {
		dataIndex = linePtr->xErrorToData[i];
		if (dataToStyle[dataIndex] == (PenStyle *)stylePtr) {
		    *segPtr++ = linePtr->xErrorBars[i];
		    *indexPtr++ = dataIndex;
		}
	    }
	    stylePtr->xErrorBarCnt = segPtr - stylePtr->xErrorBars;
	}
	Blt_Free(linePtr->xErrorBars);
	linePtr->xErrorBars = xErrorBars;
	Blt_Free(linePtr->xErrorToData);
	linePtr->xErrorToData = xErrorToData;
    }
    if (linePtr->yErrorBarCnt > 0) {
	Segment2D *errorBars, *segPtr;
	int *errorToData, *indexPtr;
	int dataIndex;

	errorBars = Blt_Malloc(linePtr->yErrorBarCnt * sizeof(Segment2D));
	errorToData = Blt_Malloc(linePtr->yErrorBarCnt * sizeof(int));
	assert(errorBars);
	segPtr = errorBars, indexPtr = errorToData;
	for (linkPtr = Blt_ChainFirstLink(linePtr->palette); 
	     linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
	    stylePtr = Blt_ChainGetValue(linkPtr);
	    stylePtr->yErrorBars = segPtr;
	    for (i = 0; i < linePtr->yErrorBarCnt; i++) {
		dataIndex = linePtr->yErrorToData[i];
		if (dataToStyle[dataIndex] == (PenStyle *)stylePtr) {
		    *segPtr++ = linePtr->yErrorBars[i];
		    *indexPtr++ = dataIndex;
		}
	    }
	    stylePtr->yErrorBarCnt = segPtr - stylePtr->yErrorBars;
	}
	Blt_Free(linePtr->yErrorBars);
	linePtr->yErrorBars = errorBars;
	Blt_Free(linePtr->yErrorToData);
	linePtr->yErrorToData = errorToData;
    }
}

#define CLIP_TOP	(1<<0)
#define CLIP_BOTTOM	(1<<1)
#define CLIP_RIGHT	(1<<2)
#define CLIP_LEFT	(1<<3)

INLINE static int
OutCode(
    Extents2D *extsPtr,
    Point2D *p)
{
    int code;

    code = 0;
    if (p->x > extsPtr->right) {
	code |= CLIP_RIGHT;
    } else if (p->x < extsPtr->left) {
	code |= CLIP_LEFT;
    }
    if (p->y > extsPtr->bottom) {
	code |= CLIP_BOTTOM;
    } else if (p->y < extsPtr->top) {
	code |= CLIP_TOP;
    }
    return code;
}

static int
ClipSegment(
    Extents2D *extsPtr,
    register int code1, int code2,
    register Point2D *p, Point2D *q)
{
    int inside, outside;

    inside = ((code1 | code2) == 0);
    outside = ((code1 & code2) != 0);

    /*
     * In the worst case, we'll clip the line segment against each of
     * the four sides of the bounding rectangle.
     */
    while ((!outside) && (!inside)) {
	if (code1 == 0) {
	    Point2D *tmp;
	    int code;

	    /* Swap pointers and out codes */
	    tmp = p, p = q, q = tmp;
	    code = code1, code1 = code2, code2 = code;
	}
	if (code1 & CLIP_LEFT) {
	    p->y += (q->y - p->y) *
		(extsPtr->left - p->x) / (q->x - p->x);
	    p->x = extsPtr->left;
	} else if (code1 & CLIP_RIGHT) {
	    p->y += (q->y - p->y) *
		(extsPtr->right - p->x) / (q->x - p->x);
	    p->x = extsPtr->right;
	} else if (code1 & CLIP_BOTTOM) {
	    p->x += (q->x - p->x) *
		(extsPtr->bottom - p->y) / (q->y - p->y);
	    p->y = extsPtr->bottom;
	} else if (code1 & CLIP_TOP) {
	    p->x += (q->x - p->x) *
		(extsPtr->top - p->y) / (q->y - p->y);
	    p->y = extsPtr->top;
	}
	code1 = OutCode(extsPtr, p);

	inside = ((code1 | code2) == 0);
	outside = ((code1 & code2) != 0);
    }
    return (!inside);
}

/*
 *----------------------------------------------------------------------
 *
 * SaveTrace --
 *
 *	Creates a new trace and inserts it into the line's
 *	list of traces.
 *
 * Results:
 *	None.
 *
 *----------------------------------------------------------------------
 */
static void
SaveTrace(
    Line *linePtr,
    int start,			/* Starting index of the trace in data point
				 * array.  Used to figure out closest point */
    int length,			/* Number of points forming the trace */
    MapInfo *mapPtr)
{
    Trace *tracePtr;
    Point2D *screenPts;
    int *indices;
    register int i, j;

    tracePtr = Blt_Malloc(sizeof(Trace));
    assert(tracePtr);
    screenPts = Blt_Malloc(sizeof(Point2D) * length);
    assert(screenPts);
    indices = Blt_Malloc(sizeof(int) * length);
    assert(indices);

    /* Copy the screen coordinates of the trace into the point array */

    if (mapPtr->indices != NULL) {
	for (i = 0, j = start; i < length; i++, j++) {
	    screenPts[i].x = mapPtr->screenPts[j].x;
	    screenPts[i].y = mapPtr->screenPts[j].y;
	    indices[i] = mapPtr->indices[j];
	}
    } else {
	for (i = 0, j = start; i < length; i++, j++) {
	    screenPts[i].x = mapPtr->screenPts[j].x;
	    screenPts[i].y = mapPtr->screenPts[j].y;
	    indices[i] = j;
	}
    }
    tracePtr->nScreenPts = length;
    tracePtr->screenPts = screenPts;
    tracePtr->symbolToData = indices;
    tracePtr->start = start;
    if (linePtr->traces == NULL) {
	linePtr->traces = Blt_ChainCreate();
    }
    Blt_ChainAppend(linePtr->traces, tracePtr);
}

/*
 *----------------------------------------------------------------------
 *
 * FreeTraces --
 *
 *	Deletes all the traces for the line.
 *
 * Results:
 *	None.
 *
 *----------------------------------------------------------------------
 */
static void
FreeTraces(
    Line *linePtr)
{
    Blt_ChainLink *linkPtr;
    Trace *tracePtr;

    for (linkPtr = Blt_ChainFirstLink(linePtr->traces); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	tracePtr = Blt_ChainGetValue(linkPtr);
	Blt_Free(tracePtr->symbolToData);
	Blt_Free(tracePtr->screenPts);
	Blt_Free(tracePtr);
    }
    Blt_ChainDestroy(linePtr->traces);
    linePtr->traces = NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * MapTraces --
 *
 *	Creates an array of line segments of the graph coordinates.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory is  allocated for the line segment array.
 *
 *----------------------------------------------------------------------
 */
static void
MapTraces(
    Graph *graphPtr,
    Line *linePtr,
    MapInfo *mapPtr)
{
    int start, count;
    int code1, code2;
    Point2D *p, *q;
    Point2D s;
    Extents2D exts;
    register int i;
    int broken, offscreen;

    Blt_GraphExtents(graphPtr, &exts);
    count = 1;
    code1 = OutCode(&exts, mapPtr->screenPts);
    p = mapPtr->screenPts;
    q = p + 1;
    for (i = 1; i < mapPtr->nScreenPts; i++, p++, q++) {
	code2 = OutCode(&exts, q);
	if (code2 != 0) {
	    /* Save the coordinates of the last point, before clipping */
	    s = *q;
	}
	broken = BROKEN_TRACE(linePtr->penDir, p->x, q->x);
	offscreen = ClipSegment(&exts, code1, code2, p, q);
	if (broken || offscreen) {

	    /*
	     * The last line segment is either totally clipped by the plotting
	     * area or the x-direction is wrong, breaking the trace.  Either
	     * way, save information about the last trace (if one exists),
	     * discarding the current line segment
	     */

	    if (count > 1) {
		start = i - count;
		SaveTrace(linePtr, start, count, mapPtr);
		count = 1;
	    }
	} else {
	    count++;		/* Add the point to the trace. */
	    if (code2 != 0) {

		/*
		 * If the last point is clipped, this means that the trace is
		 * broken after this point.  Restore the original coordinate
		 * (before clipping) after saving the trace.
		 */

		start = i - (count - 1);
		SaveTrace(linePtr, start, count, mapPtr);
		mapPtr->screenPts[i] = s;
		count = 1;
	    }
	}
	code1 = code2;
    }
    if (count > 1) {
	start = i - count;
	SaveTrace(linePtr, start, count, mapPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * MapFillArea --
 *
 *	Creates an array of points that represent a polygon that fills
 *	the area under the element.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Memory is  allocated for the polygon point array.
 *
 *----------------------------------------------------------------------
 */
static void
MapFillArea(
    Graph *graphPtr,
    Line *linePtr,
    MapInfo *mapPtr)
{
    Point2D *origPts, *clipPts;
    Extents2D exts;
    double maxY;
    register int i, n;

    if (linePtr->fillPts != NULL) {
	Blt_Free(linePtr->fillPts);
	linePtr->fillPts = NULL;
	linePtr->nFillPts = 0;
    }
    if (mapPtr->nScreenPts < 3) {
	return;
    }
    n = mapPtr->nScreenPts + 3;
    Blt_GraphExtents(graphPtr, &exts);

    maxY = (double)graphPtr->bottom;
    origPts = Blt_Malloc(sizeof(Point2D) * n);
    for (i = 0; i < mapPtr->nScreenPts; i++) {
	origPts[i].x = mapPtr->screenPts[i].x + 1;
	origPts[i].y = mapPtr->screenPts[i].y;
	if (origPts[i].y > maxY) {
	    maxY = origPts[i].y;
	}
    }	
    /* Add edges to make (if necessary) the polygon fill to the bottom
     * of plotting window */
    origPts[i].x = origPts[i - 1].x;
    origPts[i].y = maxY;
    i++;
    origPts[i].x = origPts[0].x; 
    origPts[i].y = maxY;
    i++;
    origPts[i] = origPts[0];

    clipPts = Blt_Malloc(sizeof(Point2D) * n * 3);
    assert(clipPts);
    n = Blt_PolyRectClip(&exts, origPts, n - 1, clipPts);

    Blt_Free(origPts);
    if (n < 3) {
	Blt_Free(clipPts);
    } else {
	linePtr->fillPts = clipPts;
	linePtr->nFillPts = n;
    }
}

static void
ResetLine(
    Line *linePtr)
{
    FreeTraces(linePtr);
    ClearPalette(linePtr->palette);
    if (linePtr->symbolPts != NULL) {
	Blt_Free(linePtr->symbolPts);
    }
    if (linePtr->symbolToData != NULL) {
	Blt_Free(linePtr->symbolToData);
    }
    if (linePtr->strips != NULL) {
	Blt_Free(linePtr->strips);
    }
    if (linePtr->stripToData != NULL) {
	Blt_Free(linePtr->stripToData);
    }
    if (linePtr->activePts != NULL) {
	Blt_Free(linePtr->activePts);
    }
    if (linePtr->activeToData != NULL) {
	Blt_Free(linePtr->activeToData);
    }
    if (linePtr->xErrorBars != NULL) {
	Blt_Free(linePtr->xErrorBars);
    }
    if (linePtr->xErrorToData != NULL) {
	Blt_Free(linePtr->xErrorToData);
    }
    if (linePtr->yErrorBars != NULL) {
	Blt_Free(linePtr->yErrorBars);
    }
    if (linePtr->yErrorToData != NULL) {
	Blt_Free(linePtr->yErrorToData);
    }
    linePtr->xErrorBars = linePtr->yErrorBars = linePtr->strips = NULL;
    linePtr->symbolPts = linePtr->activePts = NULL;
    linePtr->stripToData = linePtr->symbolToData = linePtr->xErrorToData = 
	linePtr->yErrorToData = linePtr->activeToData = NULL;
    linePtr->nActivePts = linePtr->nSymbolPts = linePtr->nStrips = 
	linePtr->xErrorBarCnt = linePtr->yErrorBarCnt = 0;
}

/*
 *----------------------------------------------------------------------
 *
 * MapLine --
 *
 *	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
MapLine(
    Graph *graphPtr,		/* Graph widget record */
    Element *elemPtr)		/* Element component record */
{
    Line *linePtr = (Line *)elemPtr;
    MapInfo mapInfo;
    int size, nPoints;
    PenStyle **dataToStyle;
    Blt_ChainLink *linkPtr;
    LinePenStyle *stylePtr;
    
    ResetLine(linePtr);
    nPoints = NumberOfPoints(linePtr);
    if (nPoints < 1) {
	return;			/* No data points */
    }
    GetScreenPoints(graphPtr, linePtr, &mapInfo);
    MapSymbols(graphPtr, linePtr, &mapInfo);

    if ((linePtr->flags & ACTIVE_PENDING) && (linePtr->nActiveIndices > 0)) {
	MapActiveSymbols(graphPtr, linePtr);
    }
    /*
     * Map connecting line segments if they are to be displayed.
     */
    if ((nPoints > 1) && ((graphPtr->classUid == bltStripElementUid) ||
	    (linePtr->builtinPen.traceWidth > 0))) {
	linePtr->smooth = linePtr->reqSmooth;

	/*
	 * Do smoothing if necessary.  This can extend the coordinate array,
	 * so both mapInfo.points and mapInfo.nPoints may change.
	 */

	switch (linePtr->smooth) {
	case PEN_SMOOTH_STEP:
	    GenerateSteps(&mapInfo);
	    break;

	case PEN_SMOOTH_NATURAL:
	case PEN_SMOOTH_QUADRATIC:
	    if (mapInfo.nScreenPts < 3) {
		/* Can't interpolate with less than three points. */
		linePtr->smooth = PEN_SMOOTH_NONE;
	    } else {
		GenerateSpline(graphPtr, linePtr, &mapInfo);
	    }
	    break;

	case PEN_SMOOTH_CATROM:
	    if (mapInfo.nScreenPts < 3) {
		/* Can't interpolate with less than three points. */
		linePtr->smooth = PEN_SMOOTH_NONE;
	    } else {
		GenerateParametricSpline(graphPtr, linePtr, &mapInfo);
	    }
	    break;

	default:
	    break;
	}
	if (linePtr->rTolerance > 0.0) {
	    ReducePoints(&mapInfo, linePtr->rTolerance);
	}
	if ((Blt_HasTile(linePtr->fillTile)) || (linePtr->fillStipple != None)) {
	    MapFillArea(graphPtr, linePtr, &mapInfo);
	}
	if (graphPtr->classUid == bltStripElementUid) {
	    MapStrip(graphPtr, linePtr, &mapInfo);
	} else {
	    MapTraces(graphPtr, linePtr, &mapInfo);
	}
    }
    Blt_Free(mapInfo.screenPts);
    Blt_Free(mapInfo.indices);

    /* Set the symbol size of all the pen styles. */
    for (linkPtr = Blt_ChainFirstLink(linePtr->palette); linkPtr != NULL;
	 linkPtr = Blt_ChainNextLink(linkPtr)) {
	stylePtr = Blt_ChainGetValue(linkPtr);
	size = ScaleSymbol(elemPtr, stylePtr->penPtr->symbol.size);
	stylePtr->symbolSize = size;
	stylePtr->errorBarCapWidth = (stylePtr->penPtr->errorBarCapWidth > 0) 
	    ? stylePtr->penPtr->errorBarCapWidth : (int)(size * 0.6666666);
	stylePtr->errorBarCapWidth /= 2;
    }
    dataToStyle = Blt_StyleMap((Element *)linePtr);
    if (((linePtr->yHigh.nValues > 0) && (linePtr->yLow.nValues > 0)) ||
	((linePtr->xHigh.nValues > 0) && (linePtr->xLow.nValues > 0)) ||
	(linePtr->xError.nValues > 0) || (linePtr->yError.nValues > 0)) {
	Blt_MapErrorBars(graphPtr, (Element *)linePtr, dataToStyle);
    }
    MergePens(linePtr, dataToStyle);
    Blt_Free(dataToStyle);
}

static double
DistanceToLine(
    int x, int y,			/* Sample X-Y coordinate. */
    Point2D *p, Point2D *q,		/* End points of the line segment. */
    Point2D *t)			/* (out) Point on line segment. */
{
    double right, left, top, bottom;

    *t = Blt_GetProjection(x, y, p, q);
    if (p->x > q->x) {
	right = p->x, left = q->x;
    } else {
	left = p->x, right = q->x;
    }
    if (p->y > q->y) {
	bottom = p->y, top = q->y;
    } else {
	top = p->y, bottom = q->y;
    }
    if (t->x > right) {
	t->x = right;
    } else if (t->x < left) {
	t->x = left;
    }
    if (t->y > bottom) {
	t->y = bottom;
    } else if (t->y < top) {
	t->y = top;
    }
    return hypot((t->x - x), (t->y - y));
}

static double
DistanceToX(
    int x, int y,			    /* Search X-Y coordinate. */
    Point2D *p, Point2D *q,		/* End points of the line segment. */
    Point2D *t)			        /* (out) Point on line segment. */
{
    double dx, dy;
    double dist;

    if (p->x > q->x) {
	if ((x > p->x) || (x < q->x)) {
	    return DBL_MAX; /* X-coordinate outside line segment. */
	}
    } else {
	if ((x > q->x) || (x < p->x)) {
	    return DBL_MAX; /* X-coordinate outside line segment. */
	}
    }
    dx = p->x - q->x;
    dy = p->y - q->y;
    t->x = (double)x;
    if (FABS(dx) < DBL_EPSILON) {
	double d1, d2;
	/* Same X-coordinate indicates a vertical line.  Pick the
	 * closest end point. */
	d1 = p->y - y;
	d2 = q->y - y;
	if (FABS(d1) < FABS(d2)) {
	    t->y = p->y, dist = d1;
	} else {
	    t->y = q->y, dist = d2;
	}
    } else if (FABS(dy) < DBL_EPSILON) {
	/* Horizontal line. */
	t->y = p->y, dist = p->y - y;
    } else {
	double m, b;
		
	m = dy / dx;
	b = p->y - (m * p->x);
	t->y = (x * m) + b;
	dist = y - t->y;
    }
   return FABS(dist);
}

static double
DistanceToY(
    int x, int y,			    /* Search X-Y coordinate. */
    Point2D *p, Point2D *q,		/* End points of the line segment. */
    Point2D *t)			        /* (out) Point on line segment. */
{
    double dx, dy;
    double dist;

    if (p->y > q->y) {
	if ((y > p->y) || (y < q->y)) {
	    return DBL_MAX;
	}
    } else {
	if ((y > q->y) || (y < p->y)) {
	    return DBL_MAX;
	}
    }
    dx = p->x - q->x;
    dy = p->y - q->y;
    t->y = y;
    if (FABS(dy) < DBL_EPSILON) {
	double d1, d2;

	/* Save Y-coordinate indicates an horizontal line. Pick the
	 * closest end point. */
	d1 = p->x - x;
	d2 = q->x - x;
	if (FABS(d1) < FABS(d2)) {
	    t->x = p->x, dist = d1;
	} else {
	    t->x = q->x, dist = d2;
	}
    } else if (FABS(dx) < DBL_EPSILON) {
	/* Vertical line. */
	t->x = p->x, dist = p->x - x;
    } else {
	double m, b;
	
	m = dy / dx;
	b = p->y - (m * p->x);
	t->x = (y - b) / m;
	dist = x - t->x;
    }
    return FABS(dist);
}

/*
 *----------------------------------------------------------------------
 *
 * ClosestTrace --
 *
 *	Find the line segment closest to the given window coordinate
 *	in the element.
 *
 * Results:
 *	If a new minimum distance is found, the information regarding
 *	it is returned via searchPtr.
 *
 *----------------------------------------------------------------------
 */
static int
ClosestTrace(
    Graph *graphPtr,		/* Graph widget record */
    Line *linePtr,		/* Line element record */
    ClosestSearch *searchPtr,	/* Info about closest point in element */
    DistanceProc *distProc)
{
    Blt_ChainLink *linkPtr;
    Point2D closest, b;
    Trace *tracePtr;
    double dist, minDist;
    register Point2D *pointPtr, *endPtr;
    int i;

    i = -1;			/* Suppress compiler warning. */
    minDist = searchPtr->dist;
    for (linkPtr = Blt_ChainFirstLink(linePtr->traces); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	tracePtr = Blt_ChainGetValue(linkPtr);
	for (pointPtr = tracePtr->screenPts, 
		 endPtr = tracePtr->screenPts + (tracePtr->nScreenPts - 1);
	     pointPtr < endPtr; pointPtr++) {
	    dist = (*distProc)(searchPtr->x, searchPtr->y, pointPtr, 
		pointPtr + 1, &b);
	    if (dist < minDist) {
		closest = b;
		i = tracePtr->symbolToData[pointPtr - tracePtr->screenPts];
		minDist = dist;
	    }
	}
    }
    if (minDist < searchPtr->dist) {
	searchPtr->dist = minDist;
	searchPtr->elemPtr = (Element *)linePtr;
	searchPtr->index = i;
	searchPtr->point = Blt_InvMap2D(graphPtr, closest.x, closest.y,
	    &(linePtr->axes));
	return TRUE;
    }
    return FALSE;
}

/*
 *----------------------------------------------------------------------
 *
 * ClosestStrip --
 *
 *	Find the line segment closest to the given window coordinate
 *	in the element.
 *
 * Results:
 *	If a new minimum distance is found, the information regarding
 *	it is returned via searchPtr.
 *
 *----------------------------------------------------------------------
 */
static int
ClosestStrip(
    Graph *graphPtr,		/* Graph widget record */
    Line *linePtr,		/* Line element record */
    ClosestSearch *searchPtr,	/* Info about closest point in element */
    DistanceProc *distProc)
{
    Point2D closest, b;
    double dist, minDist;
    int count;
    int i;
    register Segment2D *s;

    i = 0;
    minDist = searchPtr->dist;
    s = linePtr->strips;
    for (count = 0; count < linePtr->nStrips; count++, s++) {
	dist = (*distProc)(searchPtr->x, searchPtr->y, &(s->p), &(s->q), &b);
	if (dist < minDist) {
	    closest = b;
	    i = linePtr->stripToData[count];
	    minDist = dist;
	}
    }
    if (minDist < searchPtr->dist) {
	searchPtr->dist = minDist;
	searchPtr->elemPtr = (Element *)linePtr;
	searchPtr->index = i;
	searchPtr->point = Blt_InvMap2D(graphPtr, closest.x, closest.y,
	    &(linePtr->axes));
	return TRUE;
    }
    return FALSE;
}

/*
 *----------------------------------------------------------------------
 *
 * ClosestPoint --
 *
 *	Find the element whose data point is closest to the given screen
 *	coordinate.
 *
 * Results:
 *	If a new minimum distance is found, the information regarding
 *	it is returned via searchPtr.
 *
 *----------------------------------------------------------------------
 */
static void
ClosestPoint(
    Line *linePtr,		/* Line element that we are looking at */
    ClosestSearch *searchPtr)	/* Assorted information related to searching
				 * for the closest point */
{
    double dist, minDist;
    double dx, dy;
    int count, i;
    register Point2D *pointPtr;

    minDist = searchPtr->dist;
    i = 0;

    /*
     * Instead of testing each data point in graph coordinates, look at
     * the array of mapped screen coordinates. The advantages are
     *   1) only examine points that are visible (unclipped), and
     *   2) the computed distance is already in screen coordinates.
     */
    pointPtr = linePtr->symbolPts;
    for (count = 0; count < linePtr->nSymbolPts; count++, pointPtr++) {
	dx = (double)(searchPtr->x - pointPtr->x);
	dy = (double)(searchPtr->y - pointPtr->y);
	if (searchPtr->along == SEARCH_BOTH) {
	    dist = hypot(dx, dy);
	} else if (searchPtr->along == SEARCH_X) {
	    dist = dx;
	} else if (searchPtr->along == SEARCH_Y) {
	    dist = dy;
	} else {
	    /* This can't happen */
	    continue;
	}
	if (dist < minDist) {
	    i = linePtr->symbolToData[count];
	    minDist = dist;
	}
    }
    if (minDist < searchPtr->dist) {
	searchPtr->elemPtr = (Element *)linePtr;
	searchPtr->dist = minDist;
	searchPtr->index = i;
	searchPtr->point.x = linePtr->x.valueArr[i];
	searchPtr->point.y = linePtr->y.valueArr[i];
    }
}

/*
 *----------------------------------------------------------------------
 *
 * GetLineExtents --
 *
 *	Retrieves the range of the line element
 *
 * Results:
 *	Returns the number of data points in the element.
 *
 *----------------------------------------------------------------------
 */
static void
GetLineExtents(
    Element *elemPtr,
    Extents2D *extsPtr)
{
    int nPoints;

    extsPtr->top = extsPtr->left = DBL_MAX;
    extsPtr->bottom = extsPtr->right = -DBL_MAX;

    nPoints = NumberOfPoints(elemPtr);
    if (nPoints < 1) {
	return;
    } 
    extsPtr->right = elemPtr->x.max;
    if ((elemPtr->x.min <= 0.0) && (elemPtr->axes.x->logScale)) {
	extsPtr->left = Blt_FindElemVectorMinimum(&elemPtr->x, DBL_MIN);
    } else {
	extsPtr->left = elemPtr->x.min;
    }
    extsPtr->bottom = elemPtr->y.max;
    if ((elemPtr->y.min <= 0.0) && (elemPtr->axes.y->logScale)) {
	extsPtr->top = Blt_FindElemVectorMinimum(&elemPtr->y, DBL_MIN);
    } else {
	extsPtr->top = elemPtr->y.min;
    }

    /* Correct the data limits for error bars */

    if (elemPtr->xError.nValues > 0) {
	register int i;
	double x;
	
	nPoints = MIN(elemPtr->xError.nValues, nPoints);
	for (i = 0; i < nPoints; i++) {
	    x = elemPtr->x.valueArr[i] + elemPtr->xError.valueArr[i];
	    if (x > extsPtr->right) {
		extsPtr->right = x;
	    }
	    x = elemPtr->x.valueArr[i] - elemPtr->xError.valueArr[i];
	    if (elemPtr->axes.x->logScale) {
		if (x < 0.0) {
		    x = -x;	/* Mirror negative values, instead
				 * of ignoring them. */
		}
		if ((x > DBL_MIN) && (x < extsPtr->left)) {
		    extsPtr->left = x;
		}
	    } else if (x < extsPtr->left) {
		extsPtr->left = x;
	    }
	}		     
    } else {
	if ((elemPtr->xHigh.nValues > 0) && 
	    (elemPtr->xHigh.max > extsPtr->right)) {
	    extsPtr->right = elemPtr->xHigh.max;
	}
	if (elemPtr->xLow.nValues > 0) {
	    double left;
	    
	    if ((elemPtr->xLow.min <= 0.0) && 
		(elemPtr->axes.x->logScale)) {
		left = Blt_FindElemVectorMinimum(&elemPtr->xLow, DBL_MIN);
	    } else {
		left = elemPtr->xLow.min;
	    }
	    if (left < extsPtr->left) {
		extsPtr->left = left;
	    }
	}
    }
    
    if (elemPtr->yError.nValues > 0) {
	register int i;
	double y;
	
	nPoints = MIN(elemPtr->yError.nValues, nPoints);
	for (i = 0; i < nPoints; i++) {
	    y = elemPtr->y.valueArr[i] + elemPtr->yError.valueArr[i];
	    if (y > extsPtr->bottom) {
		extsPtr->bottom = y;
	    }
	    y = elemPtr->y.valueArr[i] - elemPtr->yError.valueArr[i];
	    if (elemPtr->axes.y->logScale) {
		if (y < 0.0) {
		    y = -y;	/* Mirror negative values, instead
				 * of ignoring them. */
		}
		if ((y > DBL_MIN) && (y < extsPtr->left)) {
		    extsPtr->top = y;
		}
	    } else if (y < extsPtr->top) {
		extsPtr->top = y;
	    }
	}		     
    } else {
	if ((elemPtr->yHigh.nValues > 0) && 
	    (elemPtr->yHigh.max > extsPtr->bottom)) {
	    extsPtr->bottom = elemPtr->yHigh.max;
	}
	if (elemPtr->yLow.nValues > 0) {
	    double top;
	    
	    if ((elemPtr->yLow.min <= 0.0) && 
		(elemPtr->axes.y->logScale)) {
		top = Blt_FindElemVectorMinimum(&elemPtr->yLow, DBL_MIN);
	    } else {
		top = elemPtr->yLow.min;
	    }
	    if (top < extsPtr->top) {
		extsPtr->top = top;
	    }
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TileChangedProc
 *
 *	Rebuilds the designated GC with the new tile pixmap.
 *
 * Results:
 *	None.
 *
 *----------------------------------------------------------------------
 */
/*ARGSUSED*/
static void
TileChangedProc(
    ClientData clientData,
    Blt_Tile tile)		/* Not used. */
{
    Line *linePtr = clientData;
    Graph *graphPtr;

    graphPtr = linePtr->graphPtr;
    if (graphPtr->tkwin != NULL) {
	graphPtr->flags |= REDRAW_WORLD;
	Blt_EventuallyRedrawGraph(graphPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * 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(
    Graph *graphPtr,
    Element *elemPtr)
{
    Line *linePtr = (Line *)elemPtr;
    unsigned long gcMask;
    XGCValues gcValues;
    GC newGC;
    Blt_ChainLink *linkPtr;

    if (ConfigurePen(graphPtr, (Pen *)&(linePtr->builtinPen)) != TCL_OK) {
	return TCL_ERROR;
    }
    /*
     * Point to the static normal/active pens if no external pens have
     * been selected.
     */
    if (linePtr->normalPenPtr == NULL) {
	linePtr->normalPenPtr = &(linePtr->builtinPen);
    }
    linkPtr = Blt_ChainFirstLink(linePtr->palette);
    if (linkPtr != NULL) {
	LinePenStyle *stylePtr;

	stylePtr = Blt_ChainGetValue(linkPtr);
	stylePtr->penPtr = linePtr->normalPenPtr;
    }
    if (linePtr->fillTile != NULL) {
	Blt_SetTileChangedProc(linePtr->fillTile, TileChangedProc, linePtr);
    }
    /*
     * Set the outline GC for this pen: GCForeground is outline color.
     * GCBackground is the fill color (only used for bitmap symbols).
     */
    gcMask = 0;
    if (linePtr->fillFgColor != NULL) {
	gcMask |= GCForeground;
	gcValues.foreground = linePtr->fillFgColor->pixel;
    }
    if (linePtr->fillBgColor != NULL) {
	gcMask |= GCBackground;
	gcValues.background = linePtr->fillBgColor->pixel;
    }
    if ((linePtr->fillStipple != None) && 
	(linePtr->fillStipple != PATTERN_SOLID)) {
	gcMask |= (GCStipple | GCFillStyle);
	gcValues.stipple = linePtr->fillStipple;
	gcValues.fill_style = (linePtr->fillBgColor == NULL) 
	    ? FillStippled : FillOpaqueStippled;
    }
    newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues);
    if (linePtr->fillGC != NULL) {
	Tk_FreeGC(graphPtr->display, linePtr->fillGC);
    }
    linePtr->fillGC = newGC;

    if (Blt_ConfigModified(linePtr->configSpecs, graphPtr->interp, "-scalesymbols", 
	(char *)NULL)) {
	linePtr->flags |= (MAP_ITEM | SCALE_SYMBOL);
    }
    if (Blt_ConfigModified(linePtr->configSpecs, graphPtr->interp, "-pixels", "-trace", "-*data",
	 "-smooth", "-map*", "-label", "-hide", "-x", "-y", "-areapattern",
			   (char *)NULL)) {
	linePtr->flags |= MAP_ITEM;
    }
    return TCL_OK;
}

/*
 *----------------------------------------------------------------------
 *
 * ClosestLine --
 *
 *	Find the closest point or line segment (if interpolated) to
 *	the given window coordinate in the line element.
 *
 * Results:
 *	Returns the distance of the closest point among other
 *	information.
 *
 *----------------------------------------------------------------------
 */
static void
ClosestLine(
    Graph *graphPtr,		/* Graph widget record */
    Element *elemPtr,		/* Element to examine */
    ClosestSearch *searchPtr)	/* Info about closest point in element */
{
    Line *linePtr = (Line *)elemPtr;
    int mode;

    mode = searchPtr->mode;
    if (mode == SEARCH_AUTO) {
	LinePen *penPtr = linePtr->normalPenPtr;

	mode = SEARCH_POINTS;
	if ((NumberOfPoints(linePtr) > 1) && (penPtr->traceWidth > 0)) {
	    mode = SEARCH_TRACES;
	}
    }
    if (mode == SEARCH_POINTS) {
	ClosestPoint(linePtr, searchPtr);
    } else {
	DistanceProc *distProc;
	int found;

	if (searchPtr->along == SEARCH_X) {
	    distProc = DistanceToX;
	} else if (searchPtr->along == SEARCH_Y) {
	    distProc = DistanceToY;
	} else {
	    distProc = DistanceToLine;
	}
	if (elemPtr->classUid == bltStripElementUid) {
	    found = ClosestStrip(graphPtr, linePtr, searchPtr, distProc);
	} else {
	    found = ClosestTrace(graphPtr, linePtr, searchPtr, distProc);
	}
	if ((!found) && (searchPtr->along != SEARCH_BOTH)) {
	    ClosestPoint(linePtr, searchPtr);
	}
    }
}

/*
 * XDrawLines() points: XMaxRequestSize(dpy) - 3
 * XFillPolygon() points:  XMaxRequestSize(dpy) - 4
 * XDrawSegments() segments:  (XMaxRequestSize(dpy) - 3) / 2
 * XDrawRectangles() rectangles:  (XMaxRequestSize(dpy) - 3) / 2
 * XFillRectangles() rectangles:  (XMaxRequestSize(dpy) - 3) / 2
 * XDrawArcs() or XFillArcs() arcs:  (XMaxRequestSize(dpy) - 3) / 3
 */

#define MAX_DRAWLINES(d)	Blt_MaxRequestSize(d, sizeof(XPoint))
#define MAX_DRAWPOLYGON(d)	Blt_MaxRequestSize(d, sizeof(XPoint))
#define MAX_DRAWSEGMENTS(d)	Blt_MaxRequestSize(d, sizeof(XSegment))
#define MAX_DRAWRECTANGLES(d)	Blt_MaxRequestSize(d, sizeof(XRectangle))
#define MAX_DRAWARCS(d)		Blt_MaxRequestSize(d, sizeof(XArc))

#ifdef _WIN32

static void
DrawCircles(
    Display *display,
    Drawable drawable,
    Line *linePtr,
    LinePen *penPtr,
    int nSymbolPts,
    Point2D *symbolPts,
    int radius)
{
    HBRUSH brush, oldBrush;
    HPEN pen, oldPen;
    HDC dc;
    TkWinDCState state;
    register Point2D *pointPtr, *endPtr;

    if (drawable == None) {
	return;			/* Huh? */
    }
    if ((penPtr->symbol.fillGC == NULL) && 
	(penPtr->symbol.outlineWidth == 0)) {
	return;
    }
    dc = TkWinGetDrawableDC(display, drawable, &state);
    /* SetROP2(dc, tkpWinRopModes[penPtr->symbol.fillGC->function]); */
    if (penPtr->symbol.fillGC != NULL) {
	brush = CreateSolidBrush(penPtr->symbol.fillGC->foreground);
    } else {
	brush = GetStockBrush(NULL_BRUSH);
    }
    if (penPtr->symbol.outlineWidth > 0) {
	pen = Blt_GCToPen(dc, penPtr->symbol.outlineGC);
    } else {
	pen = GetStockPen(NULL_PEN);
    }
    oldPen = SelectPen(dc, pen);
    oldBrush = SelectBrush(dc, brush);
    for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
	 pointPtr < endPtr; pointPtr++) {
	Ellipse(dc, (int)pointPtr->x - radius, (int)pointPtr->y - radius,
	    (int)pointPtr->x + radius + 1, (int)pointPtr->y + radius + 1);
    }
    DeleteBrush(SelectBrush(dc, oldBrush));
    DeletePen(SelectPen(dc, oldPen));
    TkWinReleaseDrawableDC(drawable, dc, &state);
}

#else

static void
DrawCircles(display, drawable, linePtr, penPtr, nSymbolPts, symbolPts, radius)
    Display *display;
    Drawable drawable;
    Line *linePtr;
    LinePen *penPtr;
    int nSymbolPts;
    Point2D *symbolPts;
    int radius;
{
    register int i;
    XArc *arcArr;		/* Array of arcs (circle) */
    register XArc *arcPtr;
    int reqSize, nArcs;
    int s;
    int count;
    register Point2D *pointPtr, *endPtr;

    s = radius + radius;
    arcArr = Blt_Malloc(nSymbolPts * sizeof(XArc));
    arcPtr = arcArr;

    if (linePtr->symbolInterval > 0) {
	count = 0;
	for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
	     pointPtr < endPtr; pointPtr++) {
	    if (DRAW_SYMBOL(linePtr)) {
		arcPtr->x = (short int)pointPtr->x - radius;
		arcPtr->y = (short int)pointPtr->y - radius;
		arcPtr->width = arcPtr->height = (unsigned short)s;
		arcPtr->angle1 = 0;
		arcPtr->angle2 = 23040;
		arcPtr++, count++;
	    }
	    linePtr->symbolCounter++;
	}
    } else {
	count = nSymbolPts;
	for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
	     pointPtr < endPtr; pointPtr++) {
	    arcPtr->x = (short int)pointPtr->x - radius;
	    arcPtr->y = (short int)pointPtr->y - radius;
	    arcPtr->width = arcPtr->height = (unsigned short)s;
	    arcPtr->angle1 = 0;
	    arcPtr->angle2 = 23040;
	    arcPtr++;
	}
    }
    reqSize = MAX_DRAWARCS(display);
    for (i = 0; i < count; i += reqSize) {
	nArcs = ((i + reqSize) > count) ? (count - i) : reqSize;
	if (penPtr->symbol.fillGC != NULL) {
	    XFillArcs(display, drawable, penPtr->symbol.fillGC, arcArr + i, 
		      nArcs);
	}
	if (penPtr->symbol.outlineWidth > 0) {
	    XDrawArcs(display, drawable, penPtr->symbol.outlineGC, arcArr + i, 
		      nArcs);
	}
    }
    Blt_Free(arcArr);
}

#endif

static void
DrawSquares(
    Display *display,
    Drawable drawable,
    Line *linePtr,
    LinePen *penPtr,
    int nSymbolPts,
    register Point2D *symbolPts,
    int r)
{
    XRectangle *rectArr;
    register Point2D *pointPtr, *endPtr;
    register XRectangle *rectPtr;
    int reqSize, nRects;
    int s;
    register int i;
    int count;

    s = r + r;
    rectArr = Blt_Malloc(nSymbolPts * sizeof(XRectangle));
    rectPtr = rectArr;

    if (linePtr->symbolInterval > 0) {
	count = 0;
	for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
	     pointPtr < endPtr; pointPtr++) {
	    if (DRAW_SYMBOL(linePtr)) {
		rectPtr->x = (short int)(pointPtr->x - r);
		rectPtr->y = (short int)(pointPtr->y - r);
		rectPtr->width = rectPtr->height = (unsigned short)s;
		rectPtr++, count++;
	    }
	    linePtr->symbolCounter++;
	}
    } else {
	count = nSymbolPts;
	for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
	     pointPtr < endPtr; pointPtr++) {
	    rectPtr->x = (short int)(pointPtr->x - r);
	    rectPtr->y = (short int)(pointPtr->y - r);
	    rectPtr->width = rectPtr->height = (unsigned short)s;
	    rectPtr++;
	}
    }
    reqSize = MAX_DRAWRECTANGLES(display);
    for (i = 0; i < count; i += reqSize) {
	nRects = ((i + reqSize) > count) ? (count - i) : reqSize;
	if (penPtr->symbol.fillGC != NULL) {
	    XFillRectangles(display, drawable, penPtr->symbol.fillGC, 
			    rectArr + i, nRects);
	}
	if (penPtr->symbol.outlineWidth > 0) {
	    XDrawRectangles(display, drawable, penPtr->symbol.outlineGC, 
			    rectArr + i, nRects);
	}
    }
    Blt_Free(rectArr);
}

/*
 * -----------------------------------------------------------------
 *
 * DrawSymbols --
 *
 * 	Draw the symbols centered at the each given x,y coordinate
 *	in the array of points.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Draws a symbol at each coordinate given.  If active,
 *	only those coordinates which are currently active are
 *	drawn.
 *
 * -----------------------------------------------------------------
 */
static void
DrawSymbols(
    Graph *graphPtr,		/* Graph widget record */
    Drawable drawable,		/* Pixmap or window to draw into */
    Line *linePtr,
    LinePen *penPtr,
    int size,			/* Size of element */
    int nSymbolPts,		/* Number of coordinates in array */
    Point2D *symbolPts)		/* Array of x,y coordinates for line */
{
    XPoint pattern[13];		/* Template for polygon symbols */
    int r1, r2;
    register int i, n;
    int count;
    register Point2D *pointPtr, *endPtr;
#define SQRT_PI		1.77245385090552
#define S_RATIO		0.886226925452758

    if (size < 3) {
	if (penPtr->symbol.fillGC != NULL) {
	    XPoint *points;
	    
	    points = Blt_Malloc(nSymbolPts * sizeof(XPoint));
	    count = 0;
	    for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
		 pointPtr < endPtr; pointPtr++) {
		points[count].x = (short int)pointPtr->x;
		points[count].y = (short int)pointPtr->y;
		count++;
	    }
	    XDrawPoints(graphPtr->display, drawable, penPtr->symbol.fillGC, 
			points, nSymbolPts, CoordModeOrigin);
	    Blt_Free(points);
	}
	return;
    }
    r1 = (int)ceil(size * 0.5);
    r2 = (int)ceil(size * S_RATIO * 0.5);

    switch (penPtr->symbol.type) {
    case SYMBOL_NONE:
	break;

    case SYMBOL_SQUARE:
	DrawSquares(graphPtr->display, drawable, linePtr, penPtr, nSymbolPts,
	    symbolPts, r2);
	break;

    case SYMBOL_CIRCLE:
	DrawCircles(graphPtr->display, drawable, linePtr, penPtr, nSymbolPts,
	    symbolPts, r1);
	break;

    case SYMBOL_SPLUS:
    case SYMBOL_SCROSS:
	{
	    XSegment *segArr;	/* Array of line segments (splus, scross) */
	    register XSegment *segPtr;
	    int reqSize, nSegs, chunk;

	    if (penPtr->symbol.type == SYMBOL_SCROSS) {
		r2 = Round(r2 * M_SQRT1_2);
		pattern[3].y = pattern[2].x = pattern[0].x = pattern[0].y = -r2;
		pattern[3].x = pattern[2].y = pattern[1].y = pattern[1].x = r2;
	    } else {
		pattern[0].y = pattern[1].y = pattern[2].x = pattern[3].x = 0;
		pattern[0].x = pattern[2].y = -r2;
		pattern[1].x = pattern[3].y = r2;
	    }
	    segArr = Blt_Malloc(nSymbolPts * 2 * sizeof(XSegment));
	    segPtr = segArr;
	    if (linePtr->symbolInterval > 0) {
		count = 0;
		for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
		     pointPtr < endPtr; pointPtr++) {
		    if (DRAW_SYMBOL(linePtr)) {
			segPtr->x1 = pattern[0].x + (short int)pointPtr->x;
			segPtr->y1 = pattern[0].y + (short int)pointPtr->y;
			segPtr->x2 = pattern[1].x + (short int)pointPtr->x;
			segPtr->y2 = pattern[1].y + (short int)pointPtr->y;
			segPtr++;
			segPtr->x1 = pattern[2].x + (short int)pointPtr->x;
			segPtr->y1 = pattern[2].y + (short int)pointPtr->y;
			segPtr->x2 = pattern[3].x + (short int)pointPtr->x;
			segPtr->y2 = pattern[3].y + (short int)pointPtr->y;
			segPtr++;
			count++;
		    }
		    linePtr->symbolCounter++;
		}
	    } else {
		count = nSymbolPts;
		for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
		     pointPtr < endPtr; pointPtr++) {
		    segPtr->x1 = pattern[0].x + (short int)pointPtr->x;
		    segPtr->y1 = pattern[0].y + (short int)pointPtr->y;
		    segPtr->x2 = pattern[1].x + (short int)pointPtr->x;
		    segPtr->y2 = pattern[1].y + (short int)pointPtr->y;
		    segPtr++;
		    segPtr->x1 = pattern[2].x + (short int)pointPtr->x;
		    segPtr->y1 = pattern[2].y + (short int)pointPtr->y;
		    segPtr->x2 = pattern[3].x + (short int)pointPtr->x;
		    segPtr->y2 = pattern[3].y + (short int)pointPtr->y;
		    segPtr++;
		}
	    }
	    nSegs = count * 2;
	    /* Always draw skinny symbols regardless of the outline width */
	    reqSize = MAX_DRAWSEGMENTS(graphPtr->display);
	    for (i = 0; i < nSegs; i += reqSize) {
		chunk = ((i + reqSize) > nSegs) ? (nSegs - i) : reqSize;
		XDrawSegments(graphPtr->display, drawable, 
			penPtr->symbol.outlineGC, segArr + i, chunk);
	    }
	    Blt_Free(segArr);
	}
	break;

    case SYMBOL_PLUS:
    case SYMBOL_CROSS:
	{
	    XPoint *polygon;
	    register XPoint *p;
	    int d;		/* Small delta for cross/plus thickness */

	    d = (r2 / 3);

	    /*
	     *
	     *          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
	     */

	    pattern[0].x = pattern[11].x = pattern[12].x = -r2;
	    pattern[2].x = pattern[1].x = pattern[10].x = pattern[9].x = -d;
	    pattern[3].x = pattern[4].x = pattern[7].x = pattern[8].x = d;
	    pattern[5].x = pattern[6].x = r2;
	    pattern[2].y = pattern[3].y = -r2;
	    pattern[0].y = pattern[1].y = pattern[4].y = pattern[5].y =
		pattern[12].y = -d;
	    pattern[11].y = pattern[10].y = pattern[7].y = pattern[6].y = d;
	    pattern[9].y = pattern[8].y = r2;

	    if (penPtr->symbol.type == SYMBOL_CROSS) {
		double dx, dy;

		/* For the cross symbol, rotate the points by 45 degrees. */
		for (n = 0; n < 12; n++) {
		    dx = (double)pattern[n].x * M_SQRT1_2;
		    dy = (double)pattern[n].y * M_SQRT1_2;
		    pattern[n].x = Round(dx - dy);
		    pattern[n].y = Round(dx + dy);
		}
		pattern[12] = pattern[0];
	    }
	    polygon = Blt_Malloc(nSymbolPts * 13 * sizeof(XPoint));
	    p = polygon;
	    if (linePtr->symbolInterval > 0) {
		count = 0;
		for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
		     pointPtr < endPtr; pointPtr++) {
		    if (DRAW_SYMBOL(linePtr)) {
			for (n = 0; n < 13; n++) {
			    p->x = pattern[n].x + (short int)pointPtr->x;
			    p->y = pattern[n].y + (short int)pointPtr->y;
			    p++;
			}
			count++;
		    }
		    linePtr->symbolCounter++;
		}
	    } else {
		count = nSymbolPts;
		for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
		     pointPtr < endPtr; pointPtr++) {
		    for (n = 0; n < 13; n++) {
			p->x = pattern[n].x + (short int)pointPtr->x;
			p->y = pattern[n].y + (short int)pointPtr->y;
			p++;
		    }
		}
	    }
	    if (penPtr->symbol.fillGC != NULL) {
		for (p = polygon, i = 0; i < count; i++, p += 13) {
		    XFillPolygon(graphPtr->display, drawable, 
			penPtr->symbol.fillGC, p, 13, Complex, CoordModeOrigin);
		}
	    }
	    if (penPtr->symbol.outlineWidth > 0) {
		for (p = polygon, i = 0; i < count; i++, p += 13) {
		    XDrawLines(graphPtr->display, drawable, 
			penPtr->symbol.outlineGC, p, 13, CoordModeOrigin);
		}
	    }
	    Blt_Free(polygon);
	}
	break;

    case SYMBOL_DIAMOND:
	{
	    XPoint *polygon;
	    register XPoint *p;

	    /*
	     *
	     *                      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.
	     *
	     */
	    pattern[1].y = pattern[0].x = -r1;
	    pattern[2].y = pattern[3].x = pattern[0].y = pattern[1].x = 0;
	    pattern[3].y = pattern[2].x = r1;
	    pattern[4] = pattern[0];

	    polygon = Blt_Malloc(nSymbolPts * 5 * sizeof(XPoint));
	    p = polygon;
	    if (linePtr->symbolInterval > 0) {
		count = 0;
		for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
		     pointPtr < endPtr; pointPtr++) {
		    if (DRAW_SYMBOL(linePtr)) {
			for (n = 0; n < 5; n++, p++) {
			    p->x = pattern[n].x + (short int)pointPtr->x;
			    p->y = pattern[n].y + (short int)pointPtr->y;
			}
			count++;
		    }
		    linePtr->symbolCounter++;
		}
	    } else {
		count = nSymbolPts;
		for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
		     pointPtr < endPtr; pointPtr++) {
		    for (n = 0; n < 5; n++, p++) {
			p->x = pattern[n].x + (short int)pointPtr->x;
			p->y = pattern[n].y + (short int)pointPtr->y;
		    }
		}
	    }
	    if (penPtr->symbol.fillGC != NULL) {
		for (p = polygon, i = 0; i < count; i++, p += 5) {
		    XFillPolygon(graphPtr->display, drawable, 
			 penPtr->symbol.fillGC, p, 5, Convex, CoordModeOrigin);

		}
	    }
	    if (penPtr->symbol.outlineWidth > 0) {
		for (p = polygon, i = 0; i < count; i++, p += 5) {
		    XDrawLines(graphPtr->display, drawable, 
		       penPtr->symbol.outlineGC, p, 5, CoordModeOrigin);
		}
	    }
	    Blt_Free(polygon);
	}
	break;

    case SYMBOL_TRIANGLE:
    case SYMBOL_ARROW:
	{
	    XPoint *polygon;
	    register XPoint *p;
	    double b;
	    int b2, h1, h2;
#define H_RATIO		1.1663402261671607
#define B_RATIO		1.3467736870885982
#define TAN30		0.57735026918962573
#define COS30		0.86602540378443871

	    b = Round(size * B_RATIO * 0.7);
	    b2 = Round(b * 0.5);
	    h2 = Round(TAN30 * b2);
	    h1 = Round(b2 / COS30);
	    /*
	     *
	     *                      The triangle symbol is a closed polygon
	     *           0,3         of 3 points. The diagram to the left
	     *                      represents the positions of the points
	     *           x,y        which are computed below. The extra
	     *                      (fourth) point connects the first and
	     *      2           1   last points.
	     *
	     */

	    if (penPtr->symbol.type == SYMBOL_ARROW) {
		pattern[3].x = pattern[0].x = 0;
		pattern[3].y = pattern[0].y = h1;
		pattern[1].x = b2;
		pattern[2].y = pattern[1].y = -h2;
		pattern[2].x = -b2;
	    } else {
		pattern[3].x = pattern[0].x = 0;
		pattern[3].y = pattern[0].y = -h1;
		pattern[1].x = b2;
		pattern[2].y = pattern[1].y = h2;
		pattern[2].x = -b2;
	    }
	    polygon = Blt_Malloc(nSymbolPts * 4 * sizeof(XPoint));
	    p = polygon;
	    if (linePtr->symbolInterval > 0) {
		count = 0;
		for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
		     pointPtr < endPtr; pointPtr++) {
		    if (DRAW_SYMBOL(linePtr)) {
			for (n = 0; n < 4; n++) {
			    p->x = pattern[n].x + (short int)pointPtr->x;
			    p->y = pattern[n].y + (short int)pointPtr->y;
			    p++;
			}
			count++;
		    }
		    linePtr->symbolCounter++;
		}
	    } else {
		count = nSymbolPts;
		for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
		     pointPtr < endPtr; pointPtr++) {
		    for (n = 0; n < 4; n++) {
			p->x = pattern[n].x + (short int)pointPtr->x;
			p->y = pattern[n].y + (short int)pointPtr->y;
			p++;
		    }
		}
	    }
	    if (penPtr->symbol.fillGC != NULL) {
		for (p = polygon, i = 0; i < count; i++, p += 4) {
		    XFillPolygon(graphPtr->display, drawable, 
			penPtr->symbol.fillGC, p, 4, Convex, CoordModeOrigin);
		}
	    }
	    if (penPtr->symbol.outlineWidth > 0) {
		for (p = polygon, i = 0; i < count; i++, p += 4) {
		    XDrawLines(graphPtr->display, drawable, 
			penPtr->symbol.outlineGC, p, 4, CoordModeOrigin);
		}
	    }
	    Blt_Free(polygon);
	}
	break;
    case SYMBOL_BITMAP:
	{
	    Pixmap bitmap, mask;
	    int width, height, bmWidth, bmHeight;
	    double scale, sx, sy;
	    int dx, dy;
	    register int x, y;

	    Tk_SizeOfBitmap(graphPtr->display, penPtr->symbol.bitmap,
		&width, &height);
	    mask = None;

	    /*
	     * Compute the size of the scaled bitmap.  Stretch the
	     * bitmap to fit a nxn bounding box.
	     */
	    sx = (double)size / (double)width;
	    sy = (double)size / (double)height;
	    scale = MIN(sx, sy);
	    bmWidth = (int)(width * scale);
	    bmHeight = (int)(height * scale);

	    XSetClipMask(graphPtr->display, penPtr->symbol.outlineGC, None);
	    if (penPtr->symbol.mask != None) {
		mask = Blt_ScaleBitmap(graphPtr->tkwin, penPtr->symbol.mask,
		    width, height, bmWidth, bmHeight);
		XSetClipMask(graphPtr->display, penPtr->symbol.outlineGC, 
			     mask);
	    }
	    bitmap = Blt_ScaleBitmap(graphPtr->tkwin, penPtr->symbol.bitmap,
		width, height, bmWidth, bmHeight);
	    if (penPtr->symbol.fillGC == NULL) {
		XSetClipMask(graphPtr->display, penPtr->symbol.outlineGC, 
			     bitmap);
	    }
	    dx = bmWidth / 2;
	    dy = bmHeight / 2;
	    if (linePtr->symbolInterval > 0) {
		for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
		     pointPtr < endPtr; pointPtr++) {
		    if (DRAW_SYMBOL(linePtr)) {
			x = (int)pointPtr->x - dx;
			y = (int)pointPtr->y - dy;
			if ((penPtr->symbol.fillGC == NULL) || (mask != None)) {
			    XSetClipOrigin(graphPtr->display,
				penPtr->symbol.outlineGC, x, y);
			}
			XCopyPlane(graphPtr->display, bitmap, drawable,
			    penPtr->symbol.outlineGC, 0, 0, bmWidth, bmHeight, 
				   x, y, 1);
		    }
		    linePtr->symbolCounter++;
		}
	    } else {
		for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
		     pointPtr < endPtr; pointPtr++) {
		    x = (int)pointPtr->x - dx;
		    y = (int)pointPtr->y - dy;
		    if ((penPtr->symbol.fillGC == NULL) || (mask != None)) {
			XSetClipOrigin(graphPtr->display, 
				       penPtr->symbol.outlineGC, x, y);
		    }
		    XCopyPlane(graphPtr->display, bitmap, drawable,
			penPtr->symbol.outlineGC, 0, 0, bmWidth, bmHeight, 
			       x, y, 1);
		}
	    }
	    Tk_FreePixmap(graphPtr->display, bitmap);
	    if (mask != None) {
		Tk_FreePixmap(graphPtr->display, mask);
	    }
	}
	break;
    }
}

/*
 * -----------------------------------------------------------------
 *
 * DrawSymbol --
 *
 * 	Draw the symbol centered at the each given x,y coordinate.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Draws a symbol at the coordinate given.
 *
 * -----------------------------------------------------------------
 */
static void
DrawSymbol(
    Graph *graphPtr,		/* Graph widget record */
    Drawable drawable,		/* Pixmap or window to draw into */
    Element *elemPtr,		/* Line element information */
    int x, int y,			/* Center position of symbol */
    int size)			/* Size of symbol. */
{
    Line *linePtr = (Line *)elemPtr;
    LinePen *penPtr = linePtr->normalPenPtr;

    if (penPtr->traceWidth > 0) {
	/*
	 * Draw an extra line offset by one pixel from the previous to
	 * give a thicker appearance.  This is only for the legend
	 * entry.  This routine is never called for drawing the actual
	 * line segments.
	 */
	XDrawLine(graphPtr->display, drawable, penPtr->traceGC, x - size, 
		y, x + size, y);
	XDrawLine(graphPtr->display, drawable, penPtr->traceGC, x - size,
		y + 1, x + size, y + 1);
    }
    if (penPtr->symbol.type != SYMBOL_NONE) {
	Point2D point;

	point.x = x, point.y = y;
	DrawSymbols(graphPtr, drawable, linePtr, linePtr->normalPenPtr, size,
	    1, &point);
    }
}

#ifdef _WIN32

static void
DrawTraces(
    Graph *graphPtr,
    Drawable drawable,		/* Pixmap or window to draw into */
    Line *linePtr,
    LinePen *penPtr)
{
    Blt_ChainLink *linkPtr;
    HBRUSH brush, oldBrush;
    HDC dc;
    HPEN pen, oldPen;
    POINT *points;
    TkWinDCState state;
    Trace *tracePtr;
    int j;
    int nPoints, remaining;
    register POINT *p;
    register int count;

    /*  
     * Depending if the line is wide (> 1 pixel), arbitrarily break
     * the line in sections of 100 points.  This bit of weirdness has
     * to do with wide geometric pens.  The longer the polyline, the
     * slower it draws.  The trade off is that we lose dash and cap
     * uniformity for unbearably slow polyline draws.
     */
    if (penPtr->traceGC->line_width > 1) {
	nPoints = 100;
    } else {
	nPoints = Blt_MaxRequestSize(graphPtr->display, sizeof(POINT)) - 1;
    }
    points = Blt_Malloc((nPoints + 1) * sizeof(POINT));

    dc = TkWinGetDrawableDC(graphPtr->display, drawable, &state);

    pen = Blt_GCToPen(dc, penPtr->traceGC);
    oldPen = SelectPen(dc, pen);
    brush = CreateSolidBrush(penPtr->traceGC->foreground);
    oldBrush = SelectBrush(dc, brush);
    SetROP2(dc, tkpWinRopModes[penPtr->traceGC->function]);

    for (linkPtr = Blt_ChainFirstLink(linePtr->traces); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	tracePtr = Blt_ChainGetValue(linkPtr);

	/*
	 * If the trace has to be split into separate XDrawLines
	 * calls, then the end point of the current trace is also the
	 * starting point of the new split.
	 */

	/* Step 1. Convert and draw the first section of the trace.
	 *	   It may contain the entire trace. */

	for (p = points, count = 0; count < MIN(nPoints, tracePtr->nScreenPts); 
	     count++, p++) {
	    p->x = (int)tracePtr->screenPts[count].x;
	    p->y = (int)tracePtr->screenPts[count].y;
	}
	Polyline(dc, points, count);

	/* Step 2. Next handle any full-size chunks left. */

	while ((count + nPoints) < tracePtr->nScreenPts) {
	    /* Start with the last point of the previous trace. */
	    points[0].x = points[nPoints - 1].x;
	    points[0].y = points[nPoints - 1].y;

	    for (p = points + 1, j = 0; j < nPoints; j++, count++, p++) {
		p->x = (int)tracePtr->screenPts[count].x;
		p->y = (int)tracePtr->screenPts[count].y;
	    }
	    Polyline(dc, points, nPoints + 1);
	}
	
	/* Step 3. Convert and draw the remaining points. */

	remaining = tracePtr->nScreenPts - count;
	if (remaining > 0) {
	    /* Start with the last point of the previous trace. */
	    points[0].x = points[nPoints - 1].x;
	    points[0].y = points[nPoints - 1].y;

	    for (p = points + 1; count < tracePtr->nScreenPts; count++, p++) {
		p->x = (int)tracePtr->screenPts[count].x;
		p->y = (int)tracePtr->screenPts[count].y;
	    }	    
	    Polyline(dc, points, remaining + 1);
	}
    }
    Blt_Free(points);
    DeletePen(SelectPen(dc, oldPen));
    DeleteBrush(SelectBrush(dc, oldBrush));
    TkWinReleaseDrawableDC(drawable, dc, &state);
}

#else

static void
DrawTraces(graphPtr, drawable, linePtr, penPtr)
    Graph *graphPtr;
    Drawable drawable;		/* Pixmap or window to draw into */
    Line *linePtr;
    LinePen *penPtr;
{
    Blt_ChainLink *linkPtr;
    Trace *tracePtr;
    XPoint *points;
    int j;
    int nPoints, remaining;
    register XPoint *p;
    register int count;

    nPoints = Blt_MaxRequestSize(graphPtr->display, sizeof(XPoint)) - 1;
    points = Blt_Malloc((nPoints + 1) * sizeof(XPoint));
	    
    for (linkPtr = Blt_ChainFirstLink(linePtr->traces); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	int n;

	tracePtr = Blt_ChainGetValue(linkPtr);

	/*
	 * If the trace has to be split into separate XDrawLines
	 * calls, then the end point of the current trace is also the
	 * starting point of the new split.
	 */
	/* Step 1. Convert and draw the first section of the trace.
	 *	   It may contain the entire trace. */

	n = MIN(nPoints, tracePtr->nScreenPts); 
	for (p = points, count = 0; count < n; count++, p++) {
	    p->x = (short int)tracePtr->screenPts[count].x;
	    p->y = (short int)tracePtr->screenPts[count].y;
	}
	XDrawLines(graphPtr->display, drawable, penPtr->traceGC, points, 
	   count, CoordModeOrigin);

	/* Step 2. Next handle any full-size chunks left. */

	while ((count + nPoints) < tracePtr->nScreenPts) {
	    /* Start with the last point of the previous trace. */
	    points[0].x = points[nPoints - 1].x;
	    points[0].y = points[nPoints - 1].y;
	    
	    for (p = points + 1, j = 0; j < nPoints; j++, count++, p++) {
		p->x = (short int)tracePtr->screenPts[count].x;
		p->y = (short int)tracePtr->screenPts[count].y;
	    }
	    XDrawLines(graphPtr->display, drawable, penPtr->traceGC, points, 
		       nPoints + 1, CoordModeOrigin);
	}
	
	/* Step 3. Convert and draw the remaining points. */

	remaining = tracePtr->nScreenPts - count;
	if (remaining > 0) {
	    /* Start with the last point of the previous trace. */
	    points[0].x = points[nPoints - 1].x;
	    points[0].y = points[nPoints - 1].y;
	    for (p = points + 1; count < tracePtr->nScreenPts; count++, p++) {
		p->x = (short int)tracePtr->screenPts[count].x;
		p->y = (short int)tracePtr->screenPts[count].y;
	    }	    
	    XDrawLines(graphPtr->display, drawable, penPtr->traceGC, points, 
		remaining + 1, CoordModeOrigin);
	}
    }
    Blt_Free(points);
}
#endif /* _WIN32 */

static void
DrawValues(
    Graph *graphPtr,
    Drawable drawable,
    Line *linePtr,
    LinePen *penPtr,
    int nSymbolPts,
    Point2D *symbolPts,
    int *pointToData)
{
    Point2D *pointPtr, *endPtr;
    int count;
    char string[TCL_DOUBLE_SPACE * 2 + 2];
    char *fmt;
    double x, y;
    
    fmt = penPtr->valueFormat;
    if (fmt == NULL) {
	fmt = "%g";
    }
    count = 0;
    for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
	 pointPtr < endPtr; pointPtr++) {
	x = linePtr->x.valueArr[pointToData[count]];
	y = linePtr->y.valueArr[pointToData[count]];
	count++;
	if (penPtr->valueShow == SHOW_X) {
	    sprintf(string, fmt, x); 
	} else if (penPtr->valueShow == SHOW_Y) {
	    sprintf(string, fmt, y); 
	} else if (penPtr->valueShow == SHOW_BOTH) {
	    sprintf(string, fmt, x);
	    strcat(string, ",");
	    sprintf(string + strlen(string), fmt, y);
	}
	Blt_DrawText(graphPtr->tkwin, drawable, string, &(penPtr->valueStyle), 
		(int)pointPtr->x, (int)pointPtr->y);
    } 
}


/*
 *----------------------------------------------------------------------
 *
 * DrawActiveLine --
 *
 *	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.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	X drawing commands are output.
 *
 *----------------------------------------------------------------------
 */
static void
DrawActiveLine(
    Graph *graphPtr,		/* Graph widget record */
    Drawable drawable,		/* Pixmap or window to draw into */
    Element *elemPtr)		/* Element to be drawn */
{
    Line *linePtr = (Line *)elemPtr;
    LinePen *penPtr = linePtr->activePenPtr;
    int symbolSize;

    if (penPtr == NULL) {
	return;
    }
    symbolSize = ScaleSymbol(elemPtr, linePtr->activePenPtr->symbol.size);

    /* 
     * nActiveIndices 
     *	  > 0		Some points are active.  Uses activeArr.
     *	  < 0		All points are active.
     *    == 0		No points are active.
     */
    if (linePtr->nActiveIndices > 0) {
	if (linePtr->flags & ACTIVE_PENDING) {
	    MapActiveSymbols(graphPtr, linePtr);
	}
	if (penPtr->symbol.type != SYMBOL_NONE) {
	    DrawSymbols(graphPtr, drawable, linePtr, penPtr, symbolSize,
		linePtr->nActivePts, linePtr->activePts);
	}
	if (penPtr->valueShow != SHOW_NONE) {
	    DrawValues(graphPtr, drawable, linePtr, penPtr, 
		       linePtr->nActivePts, linePtr->activePts, 
		       linePtr->activeToData);
	}
    } else if (linePtr->nActiveIndices < 0) { 
	if (penPtr->traceWidth > 0) {
	    if (linePtr->nStrips > 0) {
		Blt_Draw2DSegments(graphPtr->display, drawable, 
			penPtr->traceGC, linePtr->strips, linePtr->nStrips);
	    } else if (Blt_ChainGetLength(linePtr->traces) > 0) {
		DrawTraces(graphPtr, drawable, linePtr, penPtr);
	    }
	}
	if (penPtr->symbol.type != SYMBOL_NONE) {
	    DrawSymbols(graphPtr, drawable, linePtr, penPtr, symbolSize,
		linePtr->nSymbolPts, linePtr->symbolPts);
	}
	if (penPtr->valueShow != SHOW_NONE) {
	    DrawValues(graphPtr, drawable, linePtr, penPtr,
		       linePtr->nSymbolPts, linePtr->symbolPts, 
		       linePtr->symbolToData);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DrawNormalLine --
 *
 *	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.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	X drawing commands are output.
 *
 *----------------------------------------------------------------------
 */
static void
DrawNormalLine(
    Graph *graphPtr,		/* Graph widget record */
    Drawable drawable,		/* Pixmap or window to draw into */
    Element *elemPtr)		/* Element to be drawn */
{
    Line *linePtr = (Line *)elemPtr;
    LinePen *penPtr;
    Blt_ChainLink *linkPtr;
    register LinePenStyle *stylePtr;
    unsigned int count;

    /* Fill area under the curve */
    if (linePtr->fillPts != NULL) {
	XPoint *points;
	Point2D *endPtr, *pointPtr;

	points = Blt_Malloc(sizeof(XPoint) * linePtr->nFillPts);
	count = 0;
	for(pointPtr = linePtr->fillPts, 
		endPtr = linePtr->fillPts + linePtr->nFillPts;
	    pointPtr < endPtr; pointPtr++) {
	    points[count].x = (short int)pointPtr->x;
	    points[count].y = (short int)pointPtr->y;
	    count++;
	}
	if (Blt_HasTile(linePtr->fillTile)) {
	    Blt_SetTileOrigin(graphPtr->tkwin, linePtr->fillTile, 0, 0);
	    Blt_TilePolygon(graphPtr->tkwin, drawable, linePtr->fillTile, 
			    points, linePtr->nFillPts);
	} else if (linePtr->fillStipple != None) {
	    XFillPolygon(graphPtr->display, drawable, linePtr->fillGC, 
		 points, linePtr->nFillPts, Complex, CoordModeOrigin);
	}
	Blt_Free(points);
    }

    /* Lines: stripchart segments or graph traces. */

    if (linePtr->nStrips > 0) {
	for (linkPtr = Blt_ChainFirstLink(linePtr->palette); linkPtr != NULL;
	     linkPtr = Blt_ChainNextLink(linkPtr)) {
	    stylePtr = Blt_ChainGetValue(linkPtr);
	    penPtr = stylePtr->penPtr;
	    if ((stylePtr->nStrips > 0) && (penPtr->traceWidth > 0)) {
		Blt_Draw2DSegments(graphPtr->display, drawable, 
			penPtr->traceGC, stylePtr->strips, stylePtr->nStrips);
	    }
	}
    } else if ((Blt_ChainGetLength(linePtr->traces) > 0) &&
	(linePtr->normalPenPtr->traceWidth > 0)) {
	DrawTraces(graphPtr, drawable, linePtr, linePtr->normalPenPtr);
    }

    if (linePtr->reqMaxSymbols > 0) {
	int total;

	total = 0;
	for (linkPtr = Blt_ChainFirstLink(linePtr->palette); linkPtr != NULL;
	     linkPtr = Blt_ChainNextLink(linkPtr)) {
	    stylePtr = Blt_ChainGetValue(linkPtr);
	    total += stylePtr->nSymbolPts;
	}
	linePtr->symbolInterval = total / linePtr->reqMaxSymbols;
	linePtr->symbolCounter = 0;
    }

    /* Symbols, error bars, values. */

    count = 0;
    for (linkPtr = Blt_ChainFirstLink(linePtr->palette); linkPtr != NULL;
	 linkPtr = Blt_ChainNextLink(linkPtr)) {
	stylePtr = Blt_ChainGetValue(linkPtr);
	penPtr = stylePtr->penPtr;
	if ((stylePtr->xErrorBarCnt > 0) && (penPtr->errorBarShow & SHOW_X)) {
	    Blt_Draw2DSegments(graphPtr->display, drawable, penPtr->errorBarGC, 
			       stylePtr->xErrorBars, stylePtr->xErrorBarCnt);
	}
	if ((stylePtr->yErrorBarCnt > 0) && (penPtr->errorBarShow & SHOW_Y)) {
	    Blt_Draw2DSegments(graphPtr->display, drawable, penPtr->errorBarGC, 
			       stylePtr->yErrorBars, stylePtr->yErrorBarCnt);
	}
	if ((stylePtr->nSymbolPts > 0) && 
	    (penPtr->symbol.type != SYMBOL_NONE)) {
	    DrawSymbols(graphPtr, drawable, linePtr, penPtr, 
			stylePtr->symbolSize, stylePtr->nSymbolPts, 
			stylePtr->symbolPts);
	}
	if (penPtr->valueShow != SHOW_NONE) {
	    DrawValues(graphPtr, drawable, linePtr, penPtr, 
		       stylePtr->nSymbolPts, stylePtr->symbolPts, 
		       linePtr->symbolToData + count);
	}
	count += stylePtr->nSymbolPts;
    }
    linePtr->symbolInterval = 0;
}

/*
 * -----------------------------------------------------------------
 *
 * GetSymbolPostScriptInfo --
 *
 *	Set up the PostScript environment with the macros and
 *	attributes needed to draw the symbols of the element.
 *
 * Results:
 *	None.
 *
 * -----------------------------------------------------------------
 */
static void
GetSymbolPostScriptInfo(
    Graph *graphPtr,
    PsToken psToken,
    LinePen *penPtr,
    int size)
{
    XColor *outlineColor, *fillColor, *defaultColor;

    /* Set line and foreground attributes */
    outlineColor = penPtr->symbol.outlineColor;
    fillColor = penPtr->symbol.fillColor;
    defaultColor = penPtr->traceColor;

    if (fillColor == COLOR_DEFAULT) {
	fillColor = defaultColor;
    }
    if (outlineColor == COLOR_DEFAULT) {
	outlineColor = defaultColor;
    }
    if (penPtr->symbol.type == SYMBOL_NONE) {
	Blt_LineAttributesToPostScript(psToken, defaultColor,
	    penPtr->traceWidth + 2, &(penPtr->traceDashes), 
	    CapButt, JoinMiter);
    } else {
	Blt_LineWidthToPostScript(psToken, penPtr->symbol.outlineWidth);
	Blt_LineDashesToPostScript(psToken, (Blt_Dashes *)NULL);
    }

    /*
     * Build a PostScript procedure to draw the symbols.  For bitmaps,
     * paint both the bitmap and its mask. Otherwise fill and stroke
     * the path formed already.
     */
    Blt_AppendToPostScript(psToken, "\n/DrawSymbolProc {\n", (char *)NULL);
    switch (penPtr->symbol.type) {
    case SYMBOL_NONE:
	break;			/* Do nothing */
    case SYMBOL_BITMAP:
	{
	    int width, height;
	    double sx, sy, scale;

	    /*
	     * Compute how much to scale the bitmap.  Don't let the
	     * scaled bitmap exceed the bounding square for the
	     * symbol.
	     */
	    Tk_SizeOfBitmap(graphPtr->display, penPtr->symbol.bitmap,
		&width, &height);
	    sx = (double)size / (double)width;
	    sy = (double)size / (double)height;
	    scale = MIN(sx, sy);

	    if ((penPtr->symbol.mask != None) && (fillColor != NULL)) {
		Blt_AppendToPostScript(psToken,
		    "\n  % Bitmap mask is \"",
		    Tk_NameOfBitmap(graphPtr->display, penPtr->symbol.mask),
		    "\"\n\n  ", (char *)NULL);
		Blt_BackgroundToPostScript(psToken, fillColor);
		Blt_BitmapToPostScript(psToken, graphPtr->display,
		    penPtr->symbol.mask, scale, scale);
	    }
	    Blt_AppendToPostScript(psToken,
		"\n  % Bitmap symbol is \"",
		Tk_NameOfBitmap(graphPtr->display, penPtr->symbol.bitmap),
		"\"\n\n  ", (char *)NULL);
	    Blt_ForegroundToPostScript(psToken, outlineColor);
	    Blt_BitmapToPostScript(psToken, graphPtr->display, 
			penPtr->symbol.bitmap, scale, scale);
	}
	break;
    default:
	if (fillColor != NULL) {
	    Blt_AppendToPostScript(psToken, "  ", (char *)NULL);
	    Blt_BackgroundToPostScript(psToken, fillColor);
	    Blt_AppendToPostScript(psToken, "  Fill\n", (char *)NULL);
	}
	if ((outlineColor != NULL) && (penPtr->symbol.outlineWidth > 0)) {
	    Blt_AppendToPostScript(psToken, "  ", (char *)NULL);
	    Blt_ForegroundToPostScript(psToken, outlineColor);
	    Blt_AppendToPostScript(psToken, "  stroke\n", (char *)NULL);
	}
	break;
    }
    Blt_AppendToPostScript(psToken, "} def\n\n", (char *)NULL);
}

/*
 * -----------------------------------------------------------------
 *
 * SymbolsToPostScript --
 *
 * 	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
SymbolsToPostScript(
    Graph *graphPtr,
    PsToken psToken,
    LinePen *penPtr,
    int size,
    int nSymbolPts,
    Point2D *symbolPts)
{
    double symbolSize;
    register Point2D *pointPtr, *endPtr;
    static char *symbolMacros[] =
    {
	"Li", "Sq", "Ci", "Di", "Pl", "Cr", "Sp", "Sc", "Tr", "Ar", "Bm", 
	(char *)NULL,
    };
    GetSymbolPostScriptInfo(graphPtr, psToken, penPtr, size);

    symbolSize = (double)size;
    switch (penPtr->symbol.type) {
    case SYMBOL_SQUARE:
    case SYMBOL_CROSS:
    case SYMBOL_PLUS:
    case SYMBOL_SCROSS:
    case SYMBOL_SPLUS:
	symbolSize = (double)Round(size * S_RATIO);
	break;
    case SYMBOL_TRIANGLE:
    case SYMBOL_ARROW:
	symbolSize = (double)Round(size * 0.7);
	break;
    case SYMBOL_DIAMOND:
	symbolSize = (double)Round(size * M_SQRT1_2);
	break;

    default:
	break;
    }
    for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
	 pointPtr < endPtr; pointPtr++) {
	Blt_FormatToPostScript(psToken, "%g %g %g %s\n", pointPtr->x,
	    pointPtr->y, symbolSize, symbolMacros[penPtr->symbol.type]);
    }
}

/*
 * -----------------------------------------------------------------
 *
 * SymbolToPostScript --
 *
 * 	Draw the symbol centered at the each given x,y coordinate.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Draws a symbol at the coordinate given.
 *
 * -----------------------------------------------------------------
 */
static void
SymbolToPostScript(
    Graph *graphPtr,		/* Graph widget record */
    PsToken psToken,
    Element *elemPtr,		/* Line element information */
    double x, double y,		/* Center position of symbol */
    int size)			/* Size of element */
{
    Line *linePtr = (Line *)elemPtr;
    LinePen *penPtr = linePtr->normalPenPtr;

    if (penPtr->traceWidth > 0) {
	/*
	 * Draw an extra line offset by one pixel from the previous to
	 * give a thicker appearance.  This is only for the legend
	 * entry.  This routine is never called for drawing the actual
	 * line segments.
	 */
	Blt_LineAttributesToPostScript(psToken, penPtr->traceColor,
	    penPtr->traceWidth + 2, &(penPtr->traceDashes), CapButt, JoinMiter);
	Blt_FormatToPostScript(psToken, "%g %g %d Li\n", x, y, size + size);
    }
    if (penPtr->symbol.type != SYMBOL_NONE) {
	Point2D point;

	point.x = x, point.y = y;
	SymbolsToPostScript(graphPtr, psToken, penPtr, size, 1, &point);
    }
}

static void
SetLineAttributes(
    PsToken psToken,
    LinePen *penPtr)
{
    /* Set the attributes of the line (color, dashes, linewidth) */
    Blt_LineAttributesToPostScript(psToken, penPtr->traceColor,
	penPtr->traceWidth, &(penPtr->traceDashes), CapButt, JoinMiter);
    if ((LineIsDashed(penPtr->traceDashes)) && 
	(penPtr->traceOffColor != NULL)) {
	Blt_AppendToPostScript(psToken, "/DashesProc {\n  gsave\n    ",
	    (char *)NULL);
	Blt_BackgroundToPostScript(psToken, penPtr->traceOffColor);
	Blt_AppendToPostScript(psToken, "    ", (char *)NULL);
	Blt_LineDashesToPostScript(psToken, (Blt_Dashes *)NULL);
	Blt_AppendToPostScript(psToken, "stroke\n  grestore\n} def\n",
	    (char *)NULL);
    } else {
	Blt_AppendToPostScript(psToken, "/DashesProc {} def\n", (char *)NULL);
    }
}

static void
TracesToPostScript(
    PsToken psToken,
    Line *linePtr,
    LinePen *penPtr)
{
    Blt_ChainLink *linkPtr;
    Trace *tracePtr;
    register Point2D *pointPtr, *endPtr;
    int count;

    SetLineAttributes(psToken, penPtr);
    for (linkPtr = Blt_ChainFirstLink(linePtr->traces); linkPtr != NULL;
	linkPtr = Blt_ChainNextLink(linkPtr)) {
	tracePtr = Blt_ChainGetValue(linkPtr);
	if (tracePtr->nScreenPts <= 0) {
	    continue;
	}
#define PS_MAXPATH	1500	/* Maximum number of components in a PostScript
				 * (level 1) path. */
	pointPtr = tracePtr->screenPts;
	Blt_FormatToPostScript(psToken, " newpath %g %g moveto\n", 
			       pointPtr->x, 
			       pointPtr->y);
	pointPtr++;
	count = 0;
	for (endPtr = tracePtr->screenPts + (tracePtr->nScreenPts - 1);
	     pointPtr < endPtr; pointPtr++) {
	    Blt_FormatToPostScript(psToken, " %g %g lineto\n", 
				   pointPtr->x, 
				   pointPtr->y);
	    if ((count % PS_MAXPATH) == 0) {
		Blt_FormatToPostScript(psToken,
			       "DashesProc stroke\n newpath  %g %g moveto\n", 
				       pointPtr->x, 
				       pointPtr->y);
	    }
	    count++;
	}
	Blt_FormatToPostScript(psToken, " %g %g lineto\n", 
			       pointPtr->x, 
			       pointPtr->y);
	Blt_AppendToPostScript(psToken, "DashesProc stroke\n", (char *)NULL);
    }
}


static void
ValuesToPostScript(
    PsToken psToken,
    Line *linePtr,
    LinePen *penPtr,
    int nSymbolPts,
    Point2D *symbolPts,
    int *pointToData)
{
    Point2D *pointPtr, *endPtr;
    int count;
    char string[TCL_DOUBLE_SPACE * 2 + 2];
    char *fmt;
    double x, y;
    
    fmt = penPtr->valueFormat;
    if (fmt == NULL) {
	fmt = "%g";
    }
    count = 0;
    for (pointPtr = symbolPts, endPtr = symbolPts + nSymbolPts;
	 pointPtr < endPtr; pointPtr++) {
	x = linePtr->x.valueArr[pointToData[count]];
	y = linePtr->y.valueArr[pointToData[count]];
	count++;
	if (penPtr->valueShow == SHOW_X) {
	    sprintf(string, fmt, x); 
	} else if (penPtr->valueShow == SHOW_Y) {
	    sprintf(string, fmt, y); 
	} else if (penPtr->valueShow == SHOW_BOTH) {
	    sprintf(string, fmt, x);
	    strcat(string, ",");
	    sprintf(string + strlen(string), fmt, y);
	}
	Blt_TextToPostScript(psToken, string, &(penPtr->valueStyle), 
		     pointPtr->x, pointPtr->y);
    } 
}


/*
 *----------------------------------------------------------------------
 *
 * ActiveLineToPostScript --
 *
 *	Generates PostScript commands to draw as "active" the points
 *	(symbols) and or line segments (trace) representing the
 *	element.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	PostScript pen width, dashes, and color settings are changed.
 *
 *----------------------------------------------------------------------
 */
static void
ActiveLineToPostScript(
    Graph *graphPtr,
    PsToken psToken,
    Element *elemPtr)
{
    Line *linePtr = (Line *)elemPtr;
    LinePen *penPtr = linePtr->activePenPtr;
    int symbolSize;

    if (penPtr == NULL) {
	return;
    }
    symbolSize = ScaleSymbol(elemPtr, penPtr->symbol.size);
    if (linePtr->nActiveIndices > 0) {
	if (linePtr->flags & ACTIVE_PENDING) {
	    MapActiveSymbols(graphPtr, linePtr);
	}
	if (penPtr->symbol.type != SYMBOL_NONE) {
	    SymbolsToPostScript(graphPtr, psToken, penPtr, symbolSize,
		linePtr->nActivePts, linePtr->activePts);
	}
	if (penPtr->valueShow != SHOW_NONE) {
	    ValuesToPostScript(psToken, linePtr, penPtr, linePtr->nActivePts,
		       linePtr->activePts, linePtr->activeToData);
	}
    } else if (linePtr->nActiveIndices < 0) {
	if (penPtr->traceWidth > 0) {
	    if (linePtr->nStrips > 0) {
		SetLineAttributes(psToken, penPtr);
		Blt_2DSegmentsToPostScript(psToken, linePtr->strips, 
			  linePtr->nStrips);
	    }
	    if (Blt_ChainGetLength(linePtr->traces) > 0) {
		TracesToPostScript(psToken, linePtr, (LinePen *)penPtr);
	    }
	}
	if (penPtr->symbol.type != SYMBOL_NONE) {
	    SymbolsToPostScript(graphPtr, psToken, penPtr, symbolSize,
		linePtr->nSymbolPts, linePtr->symbolPts);
	}
	if (penPtr->valueShow != SHOW_NONE) {
	    ValuesToPostScript(psToken, linePtr, penPtr, linePtr->nSymbolPts, 
		       linePtr->symbolPts, linePtr->symbolToData);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * NormalLineToPostScript --
 *
 *	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
NormalLineToPostScript(
    Graph *graphPtr,
    PsToken psToken,
    Element *elemPtr)
{
    Line *linePtr = (Line *)elemPtr;
    register LinePenStyle *stylePtr;
    Blt_ChainLink *linkPtr;
    LinePen *penPtr;
    unsigned int count;
    XColor *colorPtr;

    /* Draw fill area */
    if (linePtr->fillPts != NULL) {
	/* Create a path to use for both the polygon and its outline. */
	Blt_PathToPostScript(psToken, linePtr->fillPts, linePtr->nFillPts);
	Blt_AppendToPostScript(psToken, "closepath\n", (char *)NULL);

	/* If the background fill color was specified, draw the
	 * polygon in a solid fashion with that color.  */
	if (linePtr->fillBgColor != NULL) {
	    Blt_BackgroundToPostScript(psToken, linePtr->fillBgColor);
	    Blt_AppendToPostScript(psToken, "Fill\n", (char *)NULL);
	}
	Blt_ForegroundToPostScript(psToken, linePtr->fillFgColor);
	if (Blt_HasTile(linePtr->fillTile)) {
	    /* TBA: Transparent tiling is the hard part. */
	} else if ((linePtr->fillStipple != None) &&
		   (linePtr->fillStipple != PATTERN_SOLID)) {
	    /* Draw the stipple in the foreground color. */
	    Blt_StippleToPostScript(psToken, graphPtr->display,
		    linePtr->fillStipple);
	} else {
	    Blt_AppendToPostScript(psToken, "Fill\n", (char *)NULL);
	}
    }
    /* Draw lines */
    if (linePtr->nStrips > 0) {
	for (linkPtr = Blt_ChainFirstLink(linePtr->palette); linkPtr != NULL;
	     linkPtr = Blt_ChainNextLink(linkPtr)) {
	    stylePtr = Blt_ChainGetValue(linkPtr);
	    penPtr = stylePtr->penPtr;
	    if ((stylePtr->nStrips > 0) && (penPtr->traceWidth > 0)) {
		SetLineAttributes(psToken, penPtr);
		Blt_2DSegmentsToPostScript(psToken, stylePtr->strips,
			stylePtr->nStrips);
	    }
	}
    } else if ((Blt_ChainGetLength(linePtr->traces) > 0) &&
	(linePtr->normalPenPtr->traceWidth > 0)) {
	TracesToPostScript(psToken, linePtr, linePtr->normalPenPtr);
    }

    /* Draw symbols, error bars, values. */

    count = 0;
    for (linkPtr = Blt_ChainFirstLink(linePtr->palette); linkPtr != NULL;
	 linkPtr = Blt_ChainNextLink(linkPtr)) {
	stylePtr = Blt_ChainGetValue(linkPtr);
	penPtr = stylePtr->penPtr;
	colorPtr = penPtr->errorBarColor;
	if (colorPtr == COLOR_DEFAULT) {
	    colorPtr = penPtr->traceColor;
	}
	if ((stylePtr->xErrorBarCnt > 0) && (penPtr->errorBarShow & SHOW_X)) {
	    Blt_LineAttributesToPostScript(psToken, colorPtr,
		penPtr->errorBarLineWidth, NULL, CapButt, JoinMiter);
	    Blt_2DSegmentsToPostScript(psToken, stylePtr->xErrorBars,
		stylePtr->xErrorBarCnt);
	}
	if ((stylePtr->yErrorBarCnt > 0) && (penPtr->errorBarShow & SHOW_Y)) {
	    Blt_LineAttributesToPostScript(psToken, colorPtr,
		   penPtr->errorBarLineWidth, NULL, CapButt, JoinMiter);
	    Blt_2DSegmentsToPostScript(psToken, stylePtr->yErrorBars,
		stylePtr->yErrorBarCnt);
	}
	if ((stylePtr->nSymbolPts > 0) &&
	    (stylePtr->penPtr->symbol.type != SYMBOL_NONE)) {
	    SymbolsToPostScript(graphPtr, psToken, penPtr, 
				stylePtr->symbolSize, stylePtr->nSymbolPts, 
				stylePtr->symbolPts);
	}
	if (penPtr->valueShow != SHOW_NONE) {
	    ValuesToPostScript(psToken, linePtr, penPtr, 
			       stylePtr->nSymbolPts, stylePtr->symbolPts, 
			       linePtr->symbolToData + count);
	}
	count += stylePtr->nSymbolPts;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DestroyLine --
 *
 *	Release memory and resources allocated for the line element.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Everything associated with the line element is freed up.
 *
 *----------------------------------------------------------------------
 */
#define FreeVector(v) \
    if ((v).clientId != NULL) { \
	Blt_FreeVectorId((v).clientId); \
    } else if ((v).valueArr != NULL) { \
	Blt_Free((v).valueArr); \
    } 

static void
DestroyLine(
    Graph *graphPtr,
    Element *elemPtr)
{
    Line *linePtr = (Line *)elemPtr;

    if (linePtr->normalPenPtr != &(linePtr->builtinPen)) {
	Blt_FreePen(graphPtr, (Pen *)linePtr->normalPenPtr);
    }
    DestroyPen(graphPtr, (Pen *)&(linePtr->builtinPen));
    if (linePtr->activePenPtr != NULL) {
	Blt_FreePen(graphPtr, (Pen *)linePtr->activePenPtr);
    }

    FreeVector(linePtr->w);
    FreeVector(linePtr->x);
    FreeVector(linePtr->xHigh);
    FreeVector(linePtr->xLow);
    FreeVector(linePtr->xError);
    FreeVector(linePtr->y);
    FreeVector(linePtr->yHigh);
    FreeVector(linePtr->yLow);
    FreeVector(linePtr->yError);

    ResetLine(linePtr);
    if (linePtr->palette != NULL) {
	Blt_FreePalette(graphPtr, linePtr->palette);
	Blt_ChainDestroy(linePtr->palette);
    }
    if (linePtr->tags != NULL) {
	Blt_Free(linePtr->tags);
    }
    if (linePtr->activeIndices != NULL) {
	Blt_Free(linePtr->activeIndices);
    }
    if (linePtr->fillPts != NULL) {
	Blt_Free(linePtr->fillPts);
    }
    if (linePtr->fillTile != NULL) {
	Blt_FreeTile(linePtr->fillTile);
    }
    if ((linePtr->fillStipple != None) && 
	(linePtr->fillStipple != PATTERN_SOLID)) {
	Tk_FreeBitmap(graphPtr->display, linePtr->fillStipple);
    }
    if (linePtr->fillGC != NULL) {
	Tk_FreeGC(graphPtr->display, linePtr->fillGC);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Blt_LineElement --
 *
 *	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 ElementProcs lineProcs =
{
    ClosestLine,		/* Finds the closest element/data point */
    ConfigureLine,		/* Configures the element. */
    DestroyLine,		/* Destroys the element. */
    DrawActiveLine,		/* Draws active element */
    DrawNormalLine,		/* Draws normal element */
    DrawSymbol,			/* Draws the element symbol. */
    GetLineExtents,		/* Find the extents of the element's data. */
    ActiveLineToPostScript,	/* Prints active element. */
    NormalLineToPostScript,	/* Prints normal element. */
    SymbolToPostScript,		/* Prints the line's symbol. */
    MapLine			/* Compute element's screen coordinates. */
};

Element *
Blt_LineElement(
    Graph *graphPtr,
    char *name,
    Blt_Uid classUid)
{
    register Line *linePtr;

    linePtr = Blt_Calloc(1, sizeof(Line));
    assert(linePtr);
    linePtr->procsPtr = &lineProcs;
    if (classUid == bltLineElementUid) {
	linePtr->configSpecs = lineElemConfigSpecs;
    } else {
	linePtr->configSpecs = stripElemConfigSpecs;
    }

    /* By default an element's name and label are the same. */
    linePtr->label = Blt_Strdup(name);
    linePtr->name = Blt_Strdup(name);

    linePtr->classUid = classUid;
    linePtr->flags = SCALE_SYMBOL;
    linePtr->graphPtr = graphPtr;
    linePtr->labelRelief = TK_RELIEF_FLAT;
    linePtr->normalPenPtr = &linePtr->builtinPen;
    linePtr->palette = Blt_ChainCreate();
    linePtr->penDir = PEN_BOTH_DIRECTIONS;
    linePtr->reqSmooth = PEN_SMOOTH_NONE;
    InitPen(linePtr->normalPenPtr);
    return (Element *)linePtr;
}
