/*
 * rbcGrLine.c --
 *
 *      This module implements line graph and stripchart elements for
 *      the rbc graph widget.
 *
 * Copyright (c) 2001 BLT was created by George Howlett.
 * Copyright (c) 2009 RBC was created by Samuel Green, Nicholas Hudson, Stanton Sievers, Jarrod Stormo
 * Copyright (c) 2018 Rene Zaumseil

 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tkoGraph.h"

#define PATTERN_SOLID    ((Pixmap)1)
/* Draw line segments for only those data points whose abscissas are
 * monotonically increasing in order */
#define PEN_INCREASING  1
 /* Lines will be drawn between only those points whose abscissas are
 * decreasing in order */
#define PEN_DECREASING  2
#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 {
    const 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},
    {(const char *)NULL, PEN_SMOOTH_LAST}
};

typedef struct {
    RbcPoint2D *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 */
    RbcPoint2D *screenPts;     /* Array of screen coordinates
                                * (malloc-ed) representing the
                                * trace. */
    int *symbolToData;         /* Reverse mapping of screen
                                * coordinate indices back to their
                                * data coordinates */
} LineTrace;

typedef struct {
    const char *name;          /* Name of pen style. If the pen was
                                * statically allocated the name will
                                * be NULL. */
    Tk_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. */
    Tcl_HashEntry *hashPtr;
    Tk_ConfigSpec *configSpecs; /* Configuration specifications */
    PenConfigureProc *configProc;
    PenDestroyProc *destroyProc;
    /* Symbol attributes. */
    Symbol symbol;             /* Element symbol type */
    /* LineTrace attributes. */
    int traceWidth;            /* Width of the line segments. If
                                * lineWidth is 0, no line will be
                                * drawn, only symbols. */
    RbcDashes 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. */
    RbcTextStyle valueStyle;   /* Text attributes (color, font,
                                * rotation, etc.) of the value. */
} LinePen;

typedef struct {
    RbcElemWeight weight;      /* Weight range where this pen is valid. */
    LinePen *penPtr;           /* Pen used to draw symbols, traces, error
                                * bars, segments, etc. */
    RbcSegment2D *xErrorBars;  /* Point to start of this pen's X-error bar
                                * segments in the element's array. */
    RbcSegment2D *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. */
    RbcPoint2D *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. */
    RbcSegment2D *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. */
    Tk_Uid classUid;           /* Type of element */
    RbcGraph *graph;           /* 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. */
    Tcl_HashEntry *hashPtr;
    char *label;               /* Label displayed in legend */
    int labelRelief;           /* Relief of label in legend. */
    RbcAxis2D axes;
    RbcElemVector x, y, w;     /* Contains array of numeric values */
    RbcElemVector xError;      /* Relative/symmetric X error values. */
    RbcElemVector yError;      /* Relative/symmetric Y error values. */
    RbcElemVector xHigh, xLow; /* Absolute/asymmetric X-coordinate high/low
                                * error values. */
    RbcElemVector 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. */
    RbcElementProcs *procsPtr;
    Tk_ConfigSpec *configSpecs; /* Configuration specifications */
    RbcSegment2D *xErrorBars;  /* Point to start of this pen's X-error bar
                                * segments in the element's array. */
    RbcSegment2D *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. */
    RbcChain *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;
    RbcTile fillTile;          /* Tile for fill area. */
    Pixmap fillStipple;        /* Stipple for fill area. */
    int nFillPts;
    RbcPoint2D *fillPts;       /* Array of points used to draw
                                * polygon to fill area under the
                                * curve */
    /* Symbol points */
    RbcPoint2D *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 */
    RbcPoint2D *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). */
    RbcChain *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 */
    RbcSegment2D *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;

typedef double TriDiagonalMatrix[3];
typedef struct {
    double b, c, d;
} Cubic2D;

typedef struct {
    double t;                  /* Arc length of interval. */
    double x;                  /* 2nd derivative of X with respect to T */
    double y;                  /* 2nd derivative of Y with respect to T */
} CubicSpline;

#define SQR(x)    ((x)*(x))

/*
* Quadratic spline parameters
*/
#define E1    param[0]
#define E2    param[1]
#define V1    param[2]
#define V2    param[3]
#define W1    param[4]
#define W2    param[5]
#define Z1    param[6]
#define Z2    param[7]
#define Y1    param[8]
#define Y2    param[9]

static Tk_OptionParseProc StringToPattern;
static Tk_OptionPrintProc PatternToString;
static Tk_OptionParseProc StringToSmooth;
static Tk_OptionPrintProc SmoothToString;
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 = {
    RbcStringToStyles, RbcStylesToString, (ClientData) sizeof(LinePenStyle)
};

static Tk_CustomOption penDirOption = {
    StringToPenDir, PenDirToString, (ClientData) 0
};

static Tk_CustomOption symbolOption = {
    StringToSymbol, SymbolToString, (ClientData) 0
};

extern Tk_CustomOption rbcColorOption;
extern Tk_CustomOption rbcDashesOption;
extern Tk_CustomOption rbcDataOption;
extern Tk_CustomOption rbcDataPairsOption;
extern Tk_CustomOption rbcDistanceOption;
extern Tk_CustomOption rbcListOption;
extern Tk_CustomOption rbcLinePenOption;
extern Tk_CustomOption rbcShadowOption;
extern Tk_CustomOption rbcXAxisOption;
extern Tk_CustomOption rbcYAxisOption;
extern Tk_CustomOption rbcTileOption;
extern Tk_CustomOption rbcFillOption;
extern Tk_CustomOption rbcStateOption;
#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              "navyblue"
#define DEF_LINE_PEN_DIRECTION          "both"
#define DEF_LINE_PEN_MONO               "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            "blue"
#define DEF_PEN_ACTIVE_MONO             "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            "navyblue"
#define DEF_PEN_NORMAL_MONO             "#000000"
#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             "black"
#define    DEF_PEN_VALUE_FONT              RBC_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, offsetof(Line, activePenPtr),
            TK_CONFIG_NULL_OK, &rbcLinePenOption},
    {TK_CONFIG_CUSTOM, "-areapattern", "areaPattern", "AreaPattern",
            DEF_LINE_PATTERN, offsetof(Line, fillStipple),
        TK_CONFIG_NULL_OK, &patternOption},
    {TK_CONFIG_COLOR, "-areaforeground", "areaForeground", "areaForeground",
        DEF_LINE_PATTERN_FG, offsetof(Line, fillFgColor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_COLOR, "-areabackground", "areaBackground", "areaBackground",
        DEF_LINE_PATTERN_BG, offsetof(Line, fillBgColor), TK_CONFIG_NULL_OK},
    {TK_CONFIG_CUSTOM, "-areatile", "areaTile", "AreaTile",
            DEF_LINE_PATTERN_TILE, offsetof(Line, fillTile),
        TK_CONFIG_NULL_OK, &rbcTileOption},
    {TK_CONFIG_CUSTOM, "-bindtags", "bindTags", "BindTags",
            DEF_LINE_TAGS, offsetof(Line, tags),
        TK_CONFIG_NULL_OK, &rbcListOption},
    {TK_CONFIG_COLOR, "-color", "color", "Color",
            DEF_LINE_PEN_COLOR, offsetof(Line, builtinPen.traceColor),
        TK_CONFIG_COLOR_ONLY},
    {TK_CONFIG_COLOR, "-color", "color", "Color",
            DEF_LINE_PEN_MONO, offsetof(Line, builtinPen.traceColor),
        TK_CONFIG_MONO_ONLY},
    {TK_CONFIG_CUSTOM, "-dashes", "dashes", "Dashes",
            DEF_LINE_DASHES, offsetof(Line, builtinPen.traceDashes),
        TK_CONFIG_NULL_OK, &rbcDashesOption},
    {TK_CONFIG_CUSTOM, "-data", "data", "Data",
        DEF_LINE_DATA, 0, 0, &rbcDataPairsOption},
    {TK_CONFIG_CUSTOM, "-errorbarcolor", "errorBarColor", "ErrorBarColor",
            DEF_LINE_ERRORBAR_COLOR, offsetof(Line, builtinPen.errorBarColor),
        0, &rbcColorOption},
    {TK_CONFIG_CUSTOM, "-errorbarwidth", "errorBarWidth", "ErrorBarWidth",
            DEF_LINE_ERRORBAR_LINE_WIDTH,
            offsetof(Line, builtinPen.errorBarLineWidth),
        TK_CONFIG_DONT_SET_DEFAULT, &rbcDistanceOption},
    {TK_CONFIG_CUSTOM, "-errorbarcap", "errorBarCap", "ErrorBarCap",
            DEF_LINE_ERRORBAR_CAP_WIDTH,
            offsetof(Line, builtinPen.errorBarCapWidth),
        TK_CONFIG_DONT_SET_DEFAULT, &rbcDistanceOption},
    {TK_CONFIG_CUSTOM, "-fill", "fill", "Fill",
            DEF_LINE_FILL_COLOR, offsetof(Line, builtinPen.symbol.fillColor),
        TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY, &rbcColorOption},
    {TK_CONFIG_CUSTOM, "-fill", "fill", "Fill",
            DEF_LINE_FILL_MONO, offsetof(Line, builtinPen.symbol.fillColor),
        TK_CONFIG_NULL_OK | TK_CONFIG_MONO_ONLY, &rbcColorOption},
    {TK_CONFIG_BOOLEAN, "-hide", "hide", "Hide",
        DEF_LINE_HIDE, offsetof(Line, hidden), TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_STRING, "-label", "label", "Label",
        (char *)NULL, offsetof(Line, label), TK_CONFIG_NULL_OK},
    {TK_CONFIG_RELIEF, "-labelrelief", "labelRelief", "LabelRelief",
            DEF_LINE_LABEL_RELIEF, offsetof(Line, labelRelief),
        TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-linewidth", "lineWidth", "LineWidth",
            DEF_LINE_PEN_WIDTH, offsetof(Line, builtinPen.traceWidth),
        TK_CONFIG_DONT_SET_DEFAULT, &rbcDistanceOption},
    {TK_CONFIG_CUSTOM, "-mapx", "mapX", "MapX",
        DEF_LINE_AXIS_X, offsetof(Line, axes.x), 0, &rbcXAxisOption},
    {TK_CONFIG_CUSTOM, "-mapy", "mapY", "MapY",
        DEF_LINE_AXIS_Y, offsetof(Line, axes.y), 0, &rbcYAxisOption},
    {TK_CONFIG_CUSTOM, "-maxsymbols", "maxSymbols", "MaxSymbols",
            DEF_LINE_MAX_SYMBOLS, offsetof(Line, reqMaxSymbols),
        TK_CONFIG_DONT_SET_DEFAULT, &rbcDistanceOption},
    {TK_CONFIG_CUSTOM, "-offdash", "offDash", "OffDash",
            DEF_LINE_OFFDASH_COLOR, offsetof(Line, builtinPen.traceOffColor),
        TK_CONFIG_NULL_OK | TK_CONFIG_COLOR_ONLY, &rbcColorOption},
    {TK_CONFIG_CUSTOM, "-offdash", "offDash", "OffDash",
            DEF_LINE_OFFDASH_MONO, offsetof(Line, builtinPen.traceOffColor),
        TK_CONFIG_NULL_OK | TK_CONFIG_MONO_ONLY, &rbcColorOption},
    {TK_CONFIG_CUSTOM, "-outline", "outline", "Outline",
            DEF_LINE_OUTLINE_COLOR, offsetof(Line,
                builtinPen.symbol.outlineColor),
        TK_CONFIG_COLOR_ONLY, &rbcColorOption},
    {TK_CONFIG_CUSTOM, "-outline", "outline", "Outline",
            DEF_LINE_OUTLINE_MONO, offsetof(Line,
                builtinPen.symbol.outlineColor),
        TK_CONFIG_MONO_ONLY, &rbcColorOption},
    {TK_CONFIG_CUSTOM, "-outlinewidth", "outlineWidth", "OutlineWidth",
            DEF_LINE_OUTLINE_WIDTH, offsetof(Line,
                builtinPen.symbol.outlineWidth),
        TK_CONFIG_DONT_SET_DEFAULT, &rbcDistanceOption},
    {TK_CONFIG_CUSTOM, "-pen", "pen", "Pen",
            (char *)NULL, offsetof(Line, normalPenPtr),
        TK_CONFIG_NULL_OK, &rbcLinePenOption},
    {TK_CONFIG_CUSTOM, "-pixels", "pixels", "Pixels",
            DEF_LINE_PIXELS, offsetof(Line, builtinPen.symbol.size),
        RBC_GRAPH | RBC_STRIPCHART, &rbcDistanceOption},
    {TK_CONFIG_DOUBLE, "-reduce", "reduce", "Reduce",
            DEF_LINE_REDUCE, offsetof(Line, rTolerance),
        RBC_GRAPH | RBC_STRIPCHART | TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_BOOLEAN, "-scalesymbols", "scaleSymbols", "ScaleSymbols",
            DEF_LINE_SCALE_SYMBOLS, offsetof(Line, scaleSymbols),
        TK_CONFIG_DONT_SET_DEFAULT},
    {TK_CONFIG_CUSTOM, "-showerrorbars", "showErrorBars", "ShowErrorBars",
            DEF_LINE_SHOW_ERRORBARS, offsetof(Line, builtinPen.errorBarShow),
        TK_CONFIG_DONT_SET_DEFAULT, &rbcFillOption},
    {TK_CONFIG_CUSTOM, "-showvalues", "showValues", "ShowValues",
            DEF_PEN_SHOW_VALUES, offsetof(Line, builtinPen.valueShow),
        TK_CONFIG_DONT_SET_DEFAULT, &rbcFillOption},
    {TK_CONFIG_CUSTOM, "-smooth", "smooth", "Smooth",
            DEF_LINE_SMOOTH, offsetof(Line, reqSmooth),
        TK_CONFIG_DONT_SET_DEFAULT, &smoothOption},
    {TK_CONFIG_CUSTOM, "-state", "state", "State",
            DEF_LINE_STATE, offsetof(Line, state),
        TK_CONFIG_DONT_SET_DEFAULT, &rbcStateOption},
    {TK_CONFIG_CUSTOM, "-styles", "styles", "Styles",
            DEF_LINE_STYLES, offsetof(Line, palette),
        TK_CONFIG_NULL_OK, &stylesOption},
    {TK_CONFIG_CUSTOM, "-symbol", "symbol", "Symbol",
            DEF_LINE_SYMBOL, offsetof(Line, builtinPen.symbol),
        TK_CONFIG_DONT_SET_DEFAULT, &symbolOption},
    {TK_CONFIG_CUSTOM, "-trace", "trace", "Trace",
            DEF_LINE_PEN_DIRECTION, offsetof(Line, penDir),
        TK_CONFIG_DONT_SET_DEFAULT, &penDirOption},
    {TK_CONFIG_ANCHOR, "-valueanchor", "valueAnchor", "ValueAnchor",
            DEF_PEN_VALUE_ANCHOR,
        offsetof(Line, builtinPen.valueStyle.anchor), 0},
    {TK_CONFIG_COLOR, "-valuecolor", "valueColor", "ValueColor",
        DEF_PEN_VALUE_COLOR, offsetof(Line, builtinPen.valueStyle.color), 0},
    {TK_CONFIG_FONT, "-valuefont", "valueFont", "ValueFont",
        DEF_PEN_VALUE_FONT, offsetof(Line, builtinPen.valueStyle.font), 0},
    {TK_CONFIG_STRING, "-valueformat", "valueFormat", "ValueFormat",
            DEF_PEN_VALUE_FORMAT, offsetof(Line, builtinPen.valueFormat),
        TK_CONFIG_NULL_OK},
    {TK_CONFIG_DOUBLE, "-valuerotate", "valueRotate", "ValueRotate",
        DEF_PEN_VALUE_ROTATE, offsetof(Line, builtinPen.valueStyle.theta),
            0},
    {TK_CONFIG_CUSTOM, "-valueshadow", "valueShadow", "ValueShadow",
            DEF_PEN_VALUE_SHADOW, offsetof(Line, builtinPen.valueStyle.shadow),
        0, &rbcShadowOption},
    {TK_CONFIG_CUSTOM, "-weights", "weights", "Weights",
        (char *)NULL, offsetof(Line, w), 0, &rbcDataOption},
    {TK_CONFIG_CUSTOM, "-x", "xData", "XData",
        (char *)NULL, offsetof(Line, x), 0, &rbcDataOption},
    {TK_CONFIG_CUSTOM, "-xdata", "xData", "XData",
        (char *)NULL, offsetof(Line, x), 0, &rbcDataOption},
    {TK_CONFIG_CUSTOM, "-xerror", "xError", "XError",
        (char *)NULL, offsetof(Line, xError), 0, &rbcDataOption},
    {TK_CONFIG_CUSTOM, "-xhigh", "xHigh", "XHigh",
        (char *)NULL, offsetof(Line, xHigh), 0, &rbcDataOption},
    {TK_CONFIG_CUSTOM, "-xlow", "xLow", "XLow",
        (char *)NULL, offsetof(Line, xLow), 0, &rbcDataOption},
    {TK_CONFIG_CUSTOM, "-y", "yData", "YData",
        (char *)NULL, offsetof(Line, y), 0, &rbcDataOption},
    {TK_CONFIG_CUSTOM, "-ydata", "yData", "YData",
        (char *)NULL, offsetof(Line, y), 0, &rbcDataOption},
    {TK_CONFIG_CUSTOM, "-yerror", "yError", "YError",
        (char *)NULL, offsetof(Line, yError), 0, &rbcDataOption},
    {TK_CONFIG_CUSTOM, "-yhigh", "yHigh", "YHigh",
        (char *)NULL, offsetof(Line, yHigh), 0, &rbcDataOption},
    {TK_CONFIG_CUSTOM, "-ylow", "yLow", "YLow",
        (char *)NULL, offsetof(Line, yLow), 0, &rbcDataOption},
    {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
};

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

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

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

/* Forward declarations */
static PenConfigureProc ConfigurePen;
static PenDestroyProc DestroyPen;
static RbcElementClosestProc ClosestLine;
static RbcElementConfigProc ConfigureLine;
static RbcElementDestroyProc DestroyLine;
static RbcElementDrawProc DrawActiveLine;
static RbcElementDrawProc DrawNormalLine;
static RbcElementDrawSymbolProc DrawSymbol;
static RbcElementExtentsProc GetLineExtents;
static RbcElementToPostScriptProc ActiveLineToPostScript;
static RbcElementToPostScriptProc NormalLineToPostScript;
static RbcElementSymbolToPostScriptProc SymbolToPostScript;
static RbcElementMapProc MapLine;
static DistanceProc DistanceToY;
static DistanceProc DistanceToX;
static DistanceProc DistanceToLine;
static RbcTileChangedProc TileChangedProc;
static int Round(
    register double x);
static int StringToBitmap(
    Tcl_Interp * interp,
    Tk_Window tkwin,
    Symbol * symbolPtr,
    const char *string);
static const char *NameOfSymbol(
    Symbol * symbolPtr);
static const char *NameOfSmooth(
    Smoothing value);
static const char *NameOfPenDir(
    int penDir);
static void ClearPalette(
    RbcChain * palette);
static void InitPen(
    LinePen * penPtr);
static int ScaleSymbol(
    RbcElement * elemPtr,
    int normalSize);
static void GetScreenPoints(
    RbcGraph * graph,
    Line * linePtr,
    MapInfo * mapPtr);
static void ReducePoints(
    MapInfo * mapPtr,
    double tolerance);
static void GenerateSteps(
    MapInfo * mapPtr);
static void GenerateSpline(
    RbcGraph * graph,
    Line * linePtr,
    MapInfo * mapPtr);
static void GenerateParametricSpline(
    RbcGraph * graph,
    Line * linePtr,
    MapInfo * mapPtr);
static void MapSymbols(
    RbcGraph * graph,
    Line * linePtr,
    MapInfo * mapPtr);
static void MapActiveSymbols(
    RbcGraph * graph,
    Line * linePtr);
static void MapStrip(
    RbcGraph * graph,
    Line * linePtr,
    MapInfo * mapPtr);
static void MergePens(
    Line * linePtr,
    RbcPenStyle ** dataToStyle);
static int OutCode(
    RbcExtents2D * extsPtr,
    RbcPoint2D * p);
static int ClipSegment(
    RbcExtents2D * extsPtr,
    register int code1,
    register int code2,
    register RbcPoint2D * p,
    register RbcPoint2D * q);
static void SaveTrace(
    Line * linePtr,
    int start,
    int length,
    MapInfo * mapPtr);
static void FreeTraces(
    Line * linePtr);
static void MapTraces(
    RbcGraph * graph,
    Line * linePtr,
    MapInfo * mapPtr);
static void MapFillArea(
    RbcGraph * graph,
    Line * linePtr,
    MapInfo * mapPtr);
static void ResetLine(
    Line * linePtr);
static int ClosestTrace(
    RbcGraph * graph,
    Line * linePtr,
    RbcClosestSearch * searchPtr,
    DistanceProc * distProc);
static int ClosestStrip(
    RbcGraph * graph,
    Line * linePtr,
    RbcClosestSearch * searchPtr,
    DistanceProc * distProc);
static void ClosestPoint(
    Line * linePtr,
    RbcClosestSearch * searchPtr);
static void DrawCircles(
    Display * display,
    Drawable drawable,
    Line * linePtr,
    LinePen * penPtr,
    int nSymbolPts,
    RbcPoint2D * symbolPts,
    int radius);
static void DrawSquares(
    Display * display,
    Drawable drawable,
    Line * linePtr,
    LinePen * penPtr,
    int nSymbolPts,
    register RbcPoint2D * symbolPts,
    int r);
static void DrawSymbols(
    RbcGraph * graph,
    Drawable drawable,
    Line * linePtr,
    LinePen * penPtr,
    int size,
    int nSymbolPts,
    RbcPoint2D * symbolPts);
static void DrawTraces(
    RbcGraph * graph,
    Drawable drawable,
    Line * linePtr,
    LinePen * penPtr);
static void DrawValues(
    RbcGraph * graph,
    Drawable drawable,
    Line * linePtr,
    LinePen * penPtr,
    int nSymbolPts,
    RbcPoint2D * symbolPts,
    int *pointToData);
static void GetSymbolPostScriptInfo(
    RbcGraph * graph,
    RbcPsToken * psToken,
    LinePen * penPtr,
    int size);
static void SymbolsToPostScript(
    RbcGraph * graph,
    RbcPsToken * psToken,
    LinePen * penPtr,
    int size,
    int nSymbolPts,
    RbcPoint2D * symbolPts);
static void SetLineAttributes(
    RbcPsToken * psToken,
    LinePen * penPtr);
static void TracesToPostScript(
    RbcPsToken * psToken,
    Line * linePtr,
    LinePen * penPtr);
static void ValuesToPostScript(
    RbcPsToken * psToken,
    Line * linePtr,
    LinePen * penPtr,
    int nSymbolPts,
    RbcPoint2D * symbolPts,
    int *pointToData);
static int SplineCmd(
    ClientData clientData,
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[]);
static int SearchSpline(
    RbcPoint2D points[],
    int nPoints,
    double key,
    int *foundPtr);
static int QuadChoose(
    RbcPoint2D * p,
    RbcPoint2D * q,
    double m1,
    double m2,
    double epsilon);
static void QuadCases(
    RbcPoint2D * p,
    RbcPoint2D * q,
    double m1,
    double m2,
    double param[],
    int which);
static int QuadSelect(
    RbcPoint2D * p,
    RbcPoint2D * q,
    double m1,
    double m2,
    double epsilon,
    double param[]);
static double QuadGetImage(
    double p1,
    double p2,
    double p3,
    double x1,
    double x2,
    double x3);
static void QuadSpline(
    RbcPoint2D * intp,
    RbcPoint2D * left,
    RbcPoint2D * right,
    double param[],
    int ncase);
static void QuadSlopes(
    RbcPoint2D points[],
    double *m,
    int nPoints);
static int QuadEval(
    RbcPoint2D origPts[],
    int nOrigPts,
    RbcPoint2D intpPts[],
    int nIntpPts,
    double *m,
    double epsilon);
static int SolveCubic1(
    TriDiagonalMatrix A[],
    int n);
static void SolveCubic2(
    TriDiagonalMatrix A[],
    CubicSpline spline[],
    int nIntervals);
static CubicSpline *CubicSlopes(
    RbcPoint2D points[],
    int nPoints,
    int isClosed,
    double unitX,
    double unitY);
static int CubicEval(
    RbcPoint2D origPts[],
    int nOrigPts,
    RbcPoint2D intpPts[],
    int nIntpPts,
    CubicSpline spline[]);
static void CatromCoeffs(
    RbcPoint2D * p,
    RbcPoint2D * a,
    RbcPoint2D * b,
    RbcPoint2D * c,
    RbcPoint2D * d);
/* rbcSpline.c */
static int MakeQuadraticSpline(
    RbcPoint2D * origPts,
    int nOrigPts,
    RbcPoint2D * intpPts,
    int nIntpPts);
static int MakeNaturalSpline(
    RbcPoint2D * origPts,
    int nOrigPts,
    RbcPoint2D * intpPts,
    int nIntpPts);
static int MakeNaturalParametricSpline(
    RbcPoint2D * origPts,
    int nOrigPts,
    RbcExtents2D * extsPtr,
    int isClosed,
    RbcPoint2D * intpPts,
    int nIntpPts);
static int MakeCatromParametricSpline(
    RbcPoint2D * origPts,
    int nOrigPts,
    RbcPoint2D * intpPts,
    int nIntpPts);



/*
 *----------------------------------------------------------------------
 *
 * Round --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static int
Round(
    register double x)
{
    return (int)(x + ((x < 0.0) ? -0.5 : 0.5));
}

/*
 * ----------------------------------------------------------------------
 *     Custom configuration option (parse and print) routines
 * ----------------------------------------------------------------------
 */
/*
 *----------------------------------------------------------------------
 *
 * StringToBitmap --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static int
StringToBitmap(
    Tcl_Interp * interp,
    Tk_Window tkwin,
    Symbol * symbolPtr,
    const char *string)
{
    Pixmap bitmap, mask;
    const char **elemArr;
    Tcl_Size 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;
        }
    }
    ckfree((char *)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:
    ckfree((char *)elemArr);
    return result;
}

/*
 *----------------------------------------------------------------------
 *
 * PatternToString --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static const char *
PatternToString(
    ClientData clientData,     /* Not used. */
    Tk_Window tkwin,
    char *widgRec,             /* Element information record */
    Tcl_Size 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);
}

/*
 *----------------------------------------------------------------------
 *
 * StringToPattern --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static int
StringToPattern(
    ClientData clientData,     /* Not used. */
    Tcl_Interp * interp,       /* Interpreter to send results back to */
    Tk_Window tkwin,           /* Not used. */
    const char *string,        /* String representing field */
    char *widgRec,             /* Element information record */
    Tcl_Size 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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static const 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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static int
StringToSymbol(
    ClientData clientData,     /* Not used. */
    Tcl_Interp * interp,       /* Interpreter to send results back to */
    Tk_Window tkwin,           /* Not used. */
    const char *string,        /* String representing symbol type */
    char *widgRec,             /* Element information record */
    Tcl_Size 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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static const char *
SymbolToString(
    ClientData clientData,     /* Not used. */
    Tk_Window tkwin,
    char *widgRec,             /* Element information record */
    Tcl_Size offset,                /* Offset of symbol type field in record */
    Tcl_FreeProc ** freeProcPtr)
{              /* Not used. */
    Symbol *symbolPtr = (Symbol *) (widgRec + offset);
    const 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 = RbcStrdup(Tcl_DStringValue(&dString));
        Tcl_DStringFree(&dString);
        *freeProcPtr = (Tcl_FreeProc *) Tcl_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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static const 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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static int
StringToSmooth(
    ClientData clientData,     /* Not used. */
    Tcl_Interp * interp,       /* Interpreter to send results back to */
    Tk_Window tkwin,           /* Not used. */
    const char *string,        /* String representing smooth type */
    char *widgRec,             /* Element information record */
    Tcl_Size 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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static const char *
SmoothToString(
    ClientData clientData,     /* Not used. */
    Tk_Window tkwin,           /* Not used. */
    char *widgRec,             /* Element information record */
    Tcl_Size 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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static int
StringToPenDir(
    ClientData clientData,     /* Not used. */
    Tcl_Interp * interp,       /* Interpreter to send results back to */
    Tk_Window tkwin,           /* Not used. */
    const char *string,        /* String representing pen direction */
    char *widgRec,             /* Element information record */
    Tcl_Size 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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static const 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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static const char *
PenDirToString(
    ClientData clientData,     /* Not used. */
    Tk_Window tkwin,           /* Not used. */
    char *widgRec,             /* Element information record */
    Tcl_Size offset,                /* Offset of pen direction field in record */
    Tcl_FreeProc ** freeProcPtr)
{              /* Not used. */
    int penDir = *(int *)(widgRec + offset);
    return NameOfPenDir(penDir);
}

/*
 *----------------------------------------------------------------------
 *
 * ClearPalette --
 *
 *      Clear the number of points and segments, in case there are no
 *      segments or points
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
ClearPalette(
    RbcChain * palette)
{
register LinePenStyle *stylePtr;
RbcChainLink *linkPtr;
    for(linkPtr = RbcChainFirstLink(palette); linkPtr != NULL;
        linkPtr = RbcChainNextLink(linkPtr)) {
        stylePtr = RbcChainGetValue(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 Tcl_GetString(Tcl_GetObjResult(interp)) contains an error message.
 *
 * Side effects:
 *      Configuration information such as line width, line style, color
 *      etc. get set in a new GC.
 *
 *----------------------------------------------------------------------
 */
static int
ConfigurePen(
    RbcGraph * graph,
    RbcPen * penPtr)
{
LinePen *lpPtr = (LinePen *) penPtr;
unsigned long gcMask;
GC  newGC;
XGCValues gcValues;
XColor *colorPtr;
    Tk_Window tkWin = graph->widget.tkWin;
    if (tkWin == NULL) {
        return TCL_ERROR;
    }

    RbcResetTextStyle(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 == RBC_COLOR_DEFAULT) {
        colorPtr = lpPtr->traceColor;
    }
    gcValues.foreground = colorPtr->pixel;
    if(lpPtr->symbol.type == SYMBOL_BITMAP) {
        colorPtr = lpPtr->symbol.fillColor;
        if(colorPtr == RBC_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 = RbcLineWidth(lpPtr->symbol.outlineWidth);
    newGC = Tk_GetGC(tkWin, gcMask, &gcValues);
    if(lpPtr->symbol.outlineGC != NULL) {
        Tk_FreeGC(graph->widget.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 == RBC_COLOR_DEFAULT) {
        colorPtr = lpPtr->traceColor;
    }
    newGC = NULL;
    if(colorPtr != NULL) {
        gcValues.foreground = colorPtr->pixel;
        newGC = Tk_GetGC(tkWin, gcMask, &gcValues);
    }
    if(lpPtr->symbol.fillGC != NULL) {
        Tk_FreeGC(graph->widget.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 = RbcLineWidth(lpPtr->traceWidth);
    colorPtr = lpPtr->traceOffColor;
    if(colorPtr == RBC_COLOR_DEFAULT) {
        colorPtr = lpPtr->traceColor;
    }
    if(colorPtr != NULL) {
        gcMask |= GCBackground;
        gcValues.background = colorPtr->pixel;
    }
    gcValues.foreground = lpPtr->traceColor->pixel;
    if(RbcLineIsDashed(lpPtr->traceDashes)) {
        gcValues.line_width = lpPtr->traceWidth;
        gcValues.line_style =
            (colorPtr == NULL) ? LineOnOffDash : LineDoubleDash;
    }
    newGC = RbcGetPrivateGC(tkWin, gcMask, &gcValues);
    if(lpPtr->traceGC != NULL) {
        RbcFreePrivateGC(graph->widget.display, lpPtr->traceGC);
    }
    if(RbcLineIsDashed(lpPtr->traceDashes)) {
        lpPtr->traceDashes.offset = lpPtr->traceDashes.values[0] / 2;
        RbcSetDashes(graph->widget.display, newGC, &(lpPtr->traceDashes));
    }
    lpPtr->traceGC = newGC;
    gcMask = (GCLineWidth | GCForeground);
    colorPtr = lpPtr->errorBarColor;
    if(colorPtr == RBC_COLOR_DEFAULT) {
        colorPtr = lpPtr->traceColor;
    }
    gcValues.line_width = RbcLineWidth(lpPtr->errorBarLineWidth);
    gcValues.foreground = colorPtr->pixel;
    newGC = Tk_GetGC(tkWin, gcMask, &gcValues);
    if(lpPtr->errorBarGC != NULL) {
        Tk_FreeGC(graph->widget.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(
    RbcGraph * graph,
    RbcPen * penPtr)
{
LinePen *lpPtr = (LinePen *) penPtr;
    RbcFreeTextStyle(graph->widget.display, &(lpPtr->valueStyle));
    if(lpPtr->symbol.outlineGC != NULL) {
        Tk_FreeGC(graph->widget.display, lpPtr->symbol.outlineGC);
    }
    if(lpPtr->symbol.fillGC != NULL) {
        Tk_FreeGC(graph->widget.display, lpPtr->symbol.fillGC);
    }
    if(lpPtr->errorBarGC != NULL) {
        Tk_FreeGC(graph->widget.display, lpPtr->errorBarGC);
    }
    if(lpPtr->traceGC != NULL) {
        RbcFreePrivateGC(graph->widget.display, lpPtr->traceGC);
    }
    if(lpPtr->symbol.bitmap != None) {
        Tk_FreeBitmap(graph->widget.display, lpPtr->symbol.bitmap);
        lpPtr->symbol.bitmap = None;
    }
    if(lpPtr->symbol.mask != None) {
        Tk_FreeBitmap(graph->widget.display, lpPtr->symbol.mask);
        lpPtr->symbol.mask = None;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * InitPen --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
InitPen(
    LinePen * penPtr)
{
    RbcInitTextStyle(&penPtr->valueStyle);
    penPtr->configProc = ConfigurePen;
    penPtr->configSpecs = linePenConfigSpecs;
    penPtr->destroyProc = DestroyPen;
    penPtr->errorBarLineWidth = 1;
    penPtr->errorBarShow = RBC_SHOW_BOTH;
    penPtr->flags = RBC_NORMAL_PEN;
    penPtr->name = "";
    penPtr->symbol.bitmap = penPtr->symbol.mask = None;
    penPtr->symbol.outlineColor = penPtr->symbol.fillColor = RBC_COLOR_DEFAULT;
    penPtr->symbol.outlineWidth = penPtr->traceWidth = 1;
    penPtr->symbol.type = SYMBOL_CIRCLE;
    penPtr->valueShow = RBC_SHOW_NONE;
}

/*
 *----------------------------------------------------------------------
 *
 * RbcLinePen --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
RbcPen *
RbcLinePen(
    const char *penName)
{
    LinePen *penPtr;
    penPtr=ckalloc(sizeof(LinePen));
    assert(penPtr);
    memset(penPtr,0,sizeof(LinePen));
    InitPen(penPtr);
    penPtr->name = RbcStrdup(penName);
    if(strcmp(penName, "activeLine") == 0) {
        penPtr->flags = RBC_ACTIVE_PEN;
    }
    return (RbcPen *) 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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static int
ScaleSymbol(
    RbcElement * 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 & RBC_SCALE_SYMBOL) {
            /* Save the ranges as a baseline for future scaling. */
            elemPtr->xRange = xRange;
            elemPtr->yRange = yRange;
            elemPtr->flags &= ~RBC_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(
    RbcGraph * graph,
    Line * linePtr,
    MapInfo * mapPtr)
{
double *x, *y;
register int i, n;
register int count;
register RbcPoint2D *screenPts;
register int *indices;
    n = RbcNumberOfPoints(linePtr);
    x = linePtr->x.valueArr;
    y = linePtr->y.valueArr;
    screenPts = (RbcPoint2D *) ckalloc(sizeof(RbcPoint2D) * n);
    assert(screenPts);
    indices = (int *)ckalloc(sizeof(int) * n);
    assert(indices);
    count = 0; /* Count the valid screen coordinates */
    if(graph->inverted) {
        for(i = 0; i < n; i++) {
            if((!isinf(x[i])) && (!isinf(y[i]))) {
                screenPts[count].x = RbcHMap(graph, linePtr->axes.y, y[i]);
                screenPts[count].y = RbcVMap(graph, linePtr->axes.x, x[i]);
                indices[count] = i;
                count++;
            }
        }
    } else {
        for(i = 0; i < n; i++) {
            if((!isinf(x[i])) && (!isinf(y[i]))) {
                screenPts[count].x = RbcHMap(graph, linePtr->axes.x, x[i]);
                screenPts[count].y = RbcVMap(graph, 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;
    RbcPoint2D *screenPts;
    int *indices, *simple;
    simple = (int *)ckalloc(sizeof(int) * mapPtr->nScreenPts);
    indices = (int *)ckalloc(sizeof(int) * mapPtr->nScreenPts);
    screenPts = (RbcPoint2D *) ckalloc(sizeof(RbcPoint2D) * mapPtr->nScreenPts);
    n = RbcSimplifyLine(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];
    }
    ckfree((char *)mapPtr->screenPts);
    ckfree((char *)mapPtr->indices);
    ckfree((char *)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;
RbcPoint2D *screenPts;
int *indices;
    newSize = ((mapPtr->nScreenPts - 1) * 2) + 1;
    screenPts = (RbcPoint2D *) ckalloc(newSize * sizeof(RbcPoint2D));
    assert(screenPts);
    indices = (int *)ckalloc(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;
    }
    ckfree((char *)mapPtr->screenPts);
    ckfree((char *)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(
    RbcGraph * graph,
    Line * linePtr,
    MapInfo * mapPtr)
{
int extra;
register int i, j, count;
RbcPoint2D *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)graph->right)) ||
        ((origPts[mapPtr->nScreenPts - 1].x < (double)graph->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 = (graph->right - graph->left) + 1;
    if(extra < 1) {
        return;
    }
    nIntpPts = nOrigPts + extra + 1;
    intpPts = (RbcPoint2D *) ckalloc(nIntpPts * sizeof(RbcPoint2D));
    assert(intpPts);
    indices = (int *)ckalloc(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)graph->left) ||
            (origPts[i].x <= (double)graph->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, graph->left);
            last = (int)MIN(origPts[j].x, graph->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 = MakeNaturalSpline(origPts, nOrigPts, intpPts, nIntpPts);
    } else if(linePtr->smooth == PEN_SMOOTH_QUADRATIC) {
        result = MakeQuadraticSpline(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;
        ckfree((char *)intpPts);
        ckfree((char *)indices);
    } else {
        ckfree((char *)mapPtr->screenPts);
        ckfree((char *)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(
    RbcGraph * graph,
    Line * linePtr,
    MapInfo * mapPtr)
{
RbcExtents2D exts;
RbcPoint2D *origPts, *intpPts;
RbcPoint2D 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);
    RbcGraphExtents(graph, &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(RbcLineRectClip(&exts, &p, &q)) {
            count += (int)(hypot(q.x - p.x, q.y - p.y) * 0.5);
        }
    }
    nIntpPts = count;
    intpPts = (RbcPoint2D *) ckalloc(nIntpPts * sizeof(RbcPoint2D));
    assert(intpPts);
    indices = (int *)ckalloc(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(RbcLineRectClip(&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 = MakeNaturalParametricSpline(origPts, nOrigPts, &exts, FALSE,
            intpPts, nIntpPts);
    } else if(linePtr->smooth == PEN_SMOOTH_CATROM) {
        result = MakeCatromParametricSpline(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;
        ckfree((char *)intpPts);
        ckfree((char *)indices);
    } else {
        ckfree((char *)mapPtr->screenPts);
        ckfree((char *)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(
    RbcGraph * graph,
    Line * linePtr,
    MapInfo * mapPtr)
{
RbcExtents2D exts;
RbcPoint2D *symbolPts;
int *indices;
register int i, count;
    symbolPts = (RbcPoint2D *) ckalloc(sizeof(RbcPoint2D) * mapPtr->nScreenPts);
    assert(symbolPts);
    indices = (int *)ckalloc(sizeof(int) * mapPtr->nScreenPts);
    assert(indices);
    RbcGraphExtents(graph, &exts);
    count = 0; /* Count the number of visible points */
    for(i = 0; i < mapPtr->nScreenPts; i++) {
        if(RbcPointInRegion(&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(
    RbcGraph * graph,
    Line * linePtr)
{
RbcExtents2D exts;
double x, y;
int count;
RbcPoint2D *activePts;
register int i;
int pointIndex;
int nPoints;
int *activeToData;
    if(linePtr->activePts != NULL) {
        ckfree((char *)linePtr->activePts);
        linePtr->activePts = NULL;
    }
    if(linePtr->activeToData != NULL) {
        ckfree((char *)linePtr->activeToData);
        linePtr->activeToData = NULL;
    }
    RbcGraphExtents(graph, &exts);
    activePts =
        (RbcPoint2D *) ckalloc(sizeof(RbcPoint2D) * linePtr->nActiveIndices);
    assert(activePts);
    activeToData = (int *)ckalloc(sizeof(int) * linePtr->nActiveIndices);
    nPoints = RbcNumberOfPoints(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] = RbcMap2D(graph, x, y, &(linePtr->axes));
        activeToData[count] = pointIndex;
        if(RbcPointInRegion(&exts, activePts[count].x, activePts[count].y)) {
            count++;
        }
    }
    if(count > 0) {
        linePtr->activePts = activePts;
        linePtr->activeToData = activeToData;
    } else {
        /* No active points were visible. */
        ckfree((char *)activePts);
        ckfree((char *)activeToData);
    }
    linePtr->nActivePts = count;
    linePtr->flags &= ~RBC_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(
    RbcGraph * graph,
    Line * linePtr,
    MapInfo * mapPtr)
{
RbcExtents2D exts;
RbcSegment2D *strips;
int *indices, *indexPtr;
register RbcPoint2D *endPtr, *pointPtr;
register RbcSegment2D *segPtr;
register int count;
    indices = (int *)ckalloc(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 =
        (RbcSegment2D *) ckalloc(mapPtr->nScreenPts * sizeof(RbcSegment2D));
    assert(strips);
    RbcGraphExtents(graph, &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(RbcLineRectClip(&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,
    RbcPenStyle ** dataToStyle)
{
LinePenStyle *stylePtr;
register int i;
RbcChainLink *linkPtr;
    if(RbcChainGetLength(linePtr->palette) < 2) {
        linkPtr = RbcChainFirstLink(linePtr->palette);
        stylePtr = RbcChainGetValue(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) {
RbcSegment2D *strips;
int *stripToData;
register RbcSegment2D *segPtr;
register int *indexPtr;
int dataIndex;
        strips =
            (RbcSegment2D *) ckalloc(linePtr->nStrips * sizeof(RbcSegment2D));
        stripToData = (int *)ckalloc(linePtr->nStrips * sizeof(int));
        assert(strips && stripToData);
        segPtr = strips, indexPtr = stripToData;
        for(linkPtr = RbcChainFirstLink(linePtr->palette); linkPtr != NULL;
            linkPtr = RbcChainNextLink(linkPtr)) {
            stylePtr = RbcChainGetValue(linkPtr);
            stylePtr->strips = segPtr;
            for(i = 0; i < linePtr->nStrips; i++) {
                dataIndex = linePtr->stripToData[i];
                if(dataToStyle[dataIndex] == (RbcPenStyle *) stylePtr) {
                    *segPtr++ = linePtr->strips[i];
                    *indexPtr++ = dataIndex;
                }
            }
            stylePtr->nStrips = segPtr - stylePtr->strips;
        }
        ckfree((char *)linePtr->strips);
        linePtr->strips = strips;
        ckfree((char *)linePtr->stripToData);
        linePtr->stripToData = stripToData;
    }
    if(linePtr->nSymbolPts > 0) {
int *indexPtr;
register RbcPoint2D *symbolPts, *pointPtr;
register int *symbolToData;
int dataIndex;
        symbolPts =
            (RbcPoint2D *) ckalloc(linePtr->nSymbolPts * sizeof(RbcPoint2D));
        symbolToData = (int *)ckalloc(linePtr->nSymbolPts * sizeof(int));
        assert(symbolPts && symbolToData);
        pointPtr = symbolPts, indexPtr = symbolToData;
        for(linkPtr = RbcChainFirstLink(linePtr->palette); linkPtr != NULL;
            linkPtr = RbcChainNextLink(linkPtr)) {
            stylePtr = RbcChainGetValue(linkPtr);
            stylePtr->symbolPts = pointPtr;
            for(i = 0; i < linePtr->nSymbolPts; i++) {
                dataIndex = linePtr->symbolToData[i];
                if(dataToStyle[dataIndex] == (RbcPenStyle *) stylePtr) {
                    *pointPtr++ = linePtr->symbolPts[i];
                    *indexPtr++ = dataIndex;
                }
            }
            stylePtr->nSymbolPts = pointPtr - stylePtr->symbolPts;
        }
        ckfree((char *)linePtr->symbolPts);
        linePtr->symbolPts = symbolPts;
        ckfree((char *)linePtr->symbolToData);
        linePtr->symbolToData = symbolToData;
    }
    if(linePtr->xErrorBarCnt > 0) {
RbcSegment2D *xErrorBars, *segPtr;
int *xErrorToData, *indexPtr;
int dataIndex;
        xErrorBars =
            (RbcSegment2D *) ckalloc(linePtr->xErrorBarCnt *
            sizeof(RbcSegment2D));
        xErrorToData = (int *)ckalloc(linePtr->xErrorBarCnt * sizeof(int));
        assert(xErrorBars);
        segPtr = xErrorBars, indexPtr = xErrorToData;
        for(linkPtr = RbcChainFirstLink(linePtr->palette);
            linkPtr != NULL; linkPtr = RbcChainNextLink(linkPtr)) {
            stylePtr = RbcChainGetValue(linkPtr);
            stylePtr->xErrorBars = segPtr;
            for(i = 0; i < linePtr->xErrorBarCnt; i++) {
                dataIndex = linePtr->xErrorToData[i];
                if(dataToStyle[dataIndex] == (RbcPenStyle *) stylePtr) {
                    *segPtr++ = linePtr->xErrorBars[i];
                    *indexPtr++ = dataIndex;
                }
            }
            stylePtr->xErrorBarCnt = segPtr - stylePtr->xErrorBars;
        }
        ckfree((char *)linePtr->xErrorBars);
        linePtr->xErrorBars = xErrorBars;
        ckfree((char *)linePtr->xErrorToData);
        linePtr->xErrorToData = xErrorToData;
    }
    if(linePtr->yErrorBarCnt > 0) {
RbcSegment2D *errorBars, *segPtr;
int *errorToData, *indexPtr;
int dataIndex;
        errorBars =
            (RbcSegment2D *) ckalloc(linePtr->yErrorBarCnt *
            sizeof(RbcSegment2D));
        errorToData = (int *)ckalloc(linePtr->yErrorBarCnt * sizeof(int));
        assert(errorBars);
        segPtr = errorBars, indexPtr = errorToData;
        for(linkPtr = RbcChainFirstLink(linePtr->palette);
            linkPtr != NULL; linkPtr = RbcChainNextLink(linkPtr)) {
            stylePtr = RbcChainGetValue(linkPtr);
            stylePtr->yErrorBars = segPtr;
            for(i = 0; i < linePtr->yErrorBarCnt; i++) {
                dataIndex = linePtr->yErrorToData[i];
                if(dataToStyle[dataIndex] == (RbcPenStyle *) stylePtr) {
                    *segPtr++ = linePtr->yErrorBars[i];
                    *indexPtr++ = dataIndex;
                }
            }
            stylePtr->yErrorBarCnt = segPtr - stylePtr->yErrorBars;
        }
        ckfree((char *)linePtr->yErrorBars);
        linePtr->yErrorBars = errorBars;
        ckfree((char *)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)
/*
 *----------------------------------------------------------------------
 *
 * OutCode --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static int
OutCode(
    RbcExtents2D * extsPtr,
    RbcPoint2D * 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;
}

/*
 *----------------------------------------------------------------------
 *
 * ClipSegment --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static int
ClipSegment(
    RbcExtents2D * extsPtr,
    register int code1,
    register int code2,
    register RbcPoint2D * p,
    register RbcPoint2D * 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) {
    RbcPoint2D *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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
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)
{
    LineTrace *tracePtr;
    RbcPoint2D *screenPts;
    int *indices;
    register int i, j;
    tracePtr = (LineTrace *) ckalloc(sizeof(LineTrace));
    assert(tracePtr);
    screenPts = (RbcPoint2D *) ckalloc(sizeof(RbcPoint2D) * length);
    assert(screenPts);
    indices = (int *)ckalloc(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 = RbcChainCreate();
    }
    RbcChainAppend(linePtr->traces, tracePtr);
}

/*
 *----------------------------------------------------------------------
 *
 * FreeTraces --
 *
 *      Deletes all the traces for the line.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
FreeTraces(
    Line * linePtr)
{
RbcChainLink *linkPtr;
LineTrace *tracePtr;
    for(linkPtr = RbcChainFirstLink(linePtr->traces); linkPtr != NULL;
        linkPtr = RbcChainNextLink(linkPtr)) {
        tracePtr = RbcChainGetValue(linkPtr);
        ckfree((char *)tracePtr->symbolToData);
        ckfree((char *)tracePtr->screenPts);
        ckfree((char *)tracePtr);
    }
    RbcChainDestroy(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(
    RbcGraph * graph,
    Line * linePtr,
    MapInfo * mapPtr)
{
int start, count;
int code1, code2;
RbcPoint2D *p, *q;
RbcPoint2D s;
RbcExtents2D exts;
register int i;
int broken, offscreen;
    RbcGraphExtents(graph, &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(
    RbcGraph * graph,
    Line * linePtr,
    MapInfo * mapPtr)
{
RbcPoint2D *origPts, *clipPts;
RbcExtents2D exts;
double maxY;
register int i, n;
    if(linePtr->fillPts != NULL) {
        ckfree((char *)linePtr->fillPts);
        linePtr->fillPts = NULL;
        linePtr->nFillPts = 0;
    }
    if(mapPtr->nScreenPts < 3) {
        return;
    }
    n = mapPtr->nScreenPts + 3;
    RbcGraphExtents(graph, &exts);
    maxY = (double)graph->bottom;
    origPts = (RbcPoint2D *) ckalloc(sizeof(RbcPoint2D) * 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 = (RbcPoint2D *) ckalloc(sizeof(RbcPoint2D) * n * 3);
    assert(clipPts);
    n = RbcPolyRectClip(&exts, origPts, n - 1, clipPts);
    ckfree((char *)origPts);
    if(n < 3) {
        ckfree((char *)clipPts);
    } else {
        linePtr->fillPts = clipPts;
        linePtr->nFillPts = n;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ResetLine --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
ResetLine(
    Line * linePtr)
{
    FreeTraces(linePtr);
    ClearPalette(linePtr->palette);
    if(linePtr->symbolPts != NULL) {
        ckfree((char *)linePtr->symbolPts);
    }
    if(linePtr->symbolToData != NULL) {
        ckfree((char *)linePtr->symbolToData);
    }
    if(linePtr->strips != NULL) {
        ckfree((char *)linePtr->strips);
    }
    if(linePtr->stripToData != NULL) {
        ckfree((char *)linePtr->stripToData);
    }
    if(linePtr->activePts != NULL) {
        ckfree((char *)linePtr->activePts);
    }
    if(linePtr->activeToData != NULL) {
        ckfree((char *)linePtr->activeToData);
    }
    if(linePtr->xErrorBars != NULL) {
        ckfree((char *)linePtr->xErrorBars);
    }
    if(linePtr->xErrorToData != NULL) {
        ckfree((char *)linePtr->xErrorToData);
    }
    if(linePtr->yErrorBars != NULL) {
        ckfree((char *)linePtr->yErrorBars);
    }
    if(linePtr->yErrorToData != NULL) {
        ckfree((char *)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(
    RbcGraph * graph,          /* Graph widget record */
    RbcElement * elemPtr)
{              /* Element component record */
Line *linePtr = (Line *) elemPtr;
MapInfo mapInfo;
int size, nPoints;
RbcPenStyle **dataToStyle;
RbcChainLink *linkPtr;
LinePenStyle *stylePtr;
    ResetLine(linePtr);
    nPoints = RbcNumberOfPoints(linePtr);
    if(nPoints < 1) {
        return; /* No data points */
    }
    GetScreenPoints(graph, linePtr, &mapInfo);
    MapSymbols(graph, linePtr, &mapInfo);
    if((linePtr->flags & RBC_ACTIVE_PENDING) && (linePtr->nActiveIndices > 0)) {
        MapActiveSymbols(graph, linePtr);
    }
    /*
     * Map connecting line segments if they are to be displayed.
     */
    if((nPoints > 1) && ((graph->classUid == rbcStripElementUid) ||
            (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(graph, 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(graph, linePtr, &mapInfo);
            }
            break;
        default:
            break;
        }
        if(linePtr->rTolerance > 0.0) {
            ReducePoints(&mapInfo, linePtr->rTolerance);
        }
        if((linePtr->fillTile != NULL) || (linePtr->fillStipple != None)) {
            MapFillArea(graph, linePtr, &mapInfo);
        }
        if(graph->classUid == rbcStripElementUid) {
            MapStrip(graph, linePtr, &mapInfo);
        } else {
            MapTraces(graph, linePtr, &mapInfo);
        }
    }
    ckfree((char *)mapInfo.screenPts);
    ckfree((char *)mapInfo.indices);
    /* Set the symbol size of all the pen styles. */
    for(linkPtr = RbcChainFirstLink(linePtr->palette); linkPtr != NULL;
        linkPtr = RbcChainNextLink(linkPtr)) {
        stylePtr = RbcChainGetValue(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 = RbcStyleMap((RbcElement *) 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)) {
        RbcMapErrorBars(graph, (RbcElement *) linePtr, dataToStyle);
    }
    MergePens(linePtr, dataToStyle);
    ckfree((char *)dataToStyle);
}

/*
 *----------------------------------------------------------------------
 *
 * DistanceToLine --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static double
DistanceToLine(
    int x,                     /* Sample X coordinate. */
    int y,                     /* Sample Y coordinate. */
    RbcPoint2D * p,            /* End points of the line segment. */
    RbcPoint2D * q,            /* End points of the line segment. */
    RbcPoint2D * t)
{              /* (out) Point on line segment. */
    double right, left, top, bottom;
    *t = RbcGetProjection(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));
}

/*
 *----------------------------------------------------------------------
 *
 * DistanceToX --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static double
DistanceToX(
    int x,                     /* Search X coordinate. */
    int y,                     /* Search Y coordinate. */
    RbcPoint2D * p,            /* End points of the line segment. */
    RbcPoint2D * q,            /* End points of the line segment. */
    RbcPoint2D * 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);
}

/*
 *----------------------------------------------------------------------
 *
 * DistanceToY --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static double
DistanceToY(
    int x,                     /* Search X-Y coordinate. */
    int y,                     /* Search X-Y coordinate. */
    RbcPoint2D * p,            /* End points of the line segment. */
    RbcPoint2D * q,            /* End points of the line segment. */
    RbcPoint2D * 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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static int
ClosestTrace(
    RbcGraph * graph,          /* Graph widget record */
    Line * linePtr,            /* Line element record */
    RbcClosestSearch * searchPtr,       /* Info about closest point in element */
    DistanceProc * distProc)
{
RbcChainLink *linkPtr;
RbcPoint2D closest = { 0., 0. };        /* make compiler happy */
RbcPoint2D b;
LineTrace *tracePtr;
double dist, minDist;
register RbcPoint2D *pointPtr, *endPtr;
int i;
    i = -1;    /* Suppress compiler warning. */
    minDist = searchPtr->dist;
    for(linkPtr = RbcChainFirstLink(linePtr->traces); linkPtr != NULL;
        linkPtr = RbcChainNextLink(linkPtr)) {
        tracePtr = RbcChainGetValue(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 = (RbcElement *) linePtr;
        searchPtr->index = i;
        searchPtr->point =
            RbcInvMap2D(graph, 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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static int
ClosestStrip(
    RbcGraph * graph,          /* Graph widget record */
    Line * linePtr,            /* Line element record */
    RbcClosestSearch * searchPtr,       /* Info about closest point in element */
    DistanceProc * distProc)
{
RbcPoint2D closest = { 0., 0. };        /* make compiler happy */
RbcPoint2D b;
double dist, minDist;
int count;
int i;
register RbcSegment2D *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 = (RbcElement *) linePtr;
        searchPtr->index = i;
        searchPtr->point =
            RbcInvMap2D(graph, 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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
ClosestPoint(
    Line * linePtr,            /* Line element that we are looking at */
    RbcClosestSearch * searchPtr)
{              /* Assorted information related to searching
                * for the closest point */
double dist, minDist;
double dx, dy;
int count, i;
register RbcPoint2D *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 == RBC_SEARCH_BOTH) {
            dist = hypot(dx, dy);
        } else if(searchPtr->along == RBC_SEARCH_X) {
            dist = dx;
        } else if(searchPtr->along == RBC_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 = (RbcElement *) 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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
GetLineExtents(
    RbcElement * elemPtr,
    RbcExtents2D * extsPtr)
{
int nPoints;
    extsPtr->top = extsPtr->left = DBL_MAX;
    extsPtr->bottom = extsPtr->right = -DBL_MAX;
    nPoints = RbcNumberOfPoints(elemPtr);
    if(nPoints < 1) {
        return;
    }
    extsPtr->right = elemPtr->x.max;
    if((elemPtr->x.min <= 0.0) && (elemPtr->axes.x->logScale)) {
        extsPtr->left = RbcFindElemVectorMinimum(&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 = RbcFindElemVectorMinimum(&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 = RbcFindElemVectorMinimum(&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 = RbcFindElemVectorMinimum(&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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
TileChangedProc(
    ClientData clientData,
    RbcTile tile)
{              /* Not used. */
    Line *linePtr = clientData;
    RbcGraph *graph;
    Tk_Window tkWin;
    graph = linePtr->graph;
    tkWin = graph->widget.tkWin;
    if (tkWin == NULL) {
        return;
    }

    graph->flags |= RBC_REDRAW_WORLD;
    RbcEventuallyRedrawGraph(graph);
}

/*
 *----------------------------------------------------------------------
 *
 * 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 Tcl_GetString(Tcl_GetObjResult(interp)) contains an error message.
 *
 * Side effects:
 *      Configuration information such as line width, line style, color
 *      etc. get set in a new GC.
 *
 *----------------------------------------------------------------------
 */
static int
ConfigureLine(
    RbcGraph * graph,
    RbcElement * elemPtr)
{
Line *linePtr = (Line *) elemPtr;
unsigned long gcMask;
XGCValues gcValues;
GC  newGC;
RbcChainLink *linkPtr;
    Tk_Window tkWin = graph->widget.tkWin;
    if (tkWin == NULL) {
        return TCL_ERROR;
    }

    if(ConfigurePen(graph, (RbcPen *) & (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 = RbcChainFirstLink(linePtr->palette);
    if(linkPtr != NULL) {
LinePenStyle *stylePtr;
        stylePtr = RbcChainGetValue(linkPtr);
        stylePtr->penPtr = linePtr->normalPenPtr;
    }
    if(linePtr->fillTile != NULL) {
        RbcSetTileChangedProc(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(tkWin, gcMask, &gcValues);
    if(linePtr->fillGC != NULL) {
        Tk_FreeGC(graph->widget.display, linePtr->fillGC);
    }
    linePtr->fillGC = newGC;
    if(RbcConfigModified(linePtr->configSpecs, "-scalesymbols", (char *)NULL)) {
        linePtr->flags |= (RBC_MAP_ITEM | RBC_SCALE_SYMBOL);
    }
    if(RbcConfigModified(linePtr->configSpecs, "-pixels", "-trace", "-*data",
            "-smooth", "-map*", "-label", "-hide", "-x", "-y", "-areapattern",
            (char *)NULL)) {
        linePtr->flags |= RBC_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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
ClosestLine(
    RbcGraph * graph,          /* Graph widget record */
    RbcElement * elemPtr,      /* Element to examine */
    RbcClosestSearch * searchPtr)
{              /* Info about closest point in element */
Line *linePtr = (Line *) elemPtr;
int mode;
    mode = searchPtr->mode;
    if(mode == RBC_SEARCH_AUTO) {
LinePen *penPtr = linePtr->normalPenPtr;
        mode = RBC_SEARCH_POINTS;
        if((RbcNumberOfPoints(linePtr) > 1) && (penPtr->traceWidth > 0)) {
            mode = RBC_SEARCH_TRACES;
        }
    }
    if(mode == RBC_SEARCH_POINTS) {
        ClosestPoint(linePtr, searchPtr);
    } else {
DistanceProc *distProc;
int found;
        if(searchPtr->along == RBC_SEARCH_X) {
            distProc = DistanceToX;
        } else if(searchPtr->along == RBC_SEARCH_Y) {
            distProc = DistanceToY;
        } else {
            distProc = DistanceToLine;
        }
        if(elemPtr->classUid == rbcStripElementUid) {
            found = ClosestStrip(graph, linePtr, searchPtr, distProc);
        } else {
            found = ClosestTrace(graph, linePtr, searchPtr, distProc);
        }
        if((!found) && (searchPtr->along != RBC_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)        RbcMaxRequestSize(d, sizeof(XPoint))
#define MAX_DRAWPOLYGON(d)        RbcMaxRequestSize(d, sizeof(XPoint))
#define MAX_DRAWSEGMENTS(d)        RbcMaxRequestSize(d, sizeof(XSegment))
#define MAX_DRAWRECTANGLES(d)    RbcMaxRequestSize(d, sizeof(XRectangle))
#define MAX_DRAWARCS(d)            RbcMaxRequestSize(d, sizeof(XArc))
#ifdef _WIN32
/*
 *----------------------------------------------------------------------
 *
 * DrawCircles --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
DrawCircles(
    Display * display,
    Drawable drawable,
    Line * linePtr,
    LinePen * penPtr,
    int nSymbolPts,
    RbcPoint2D * symbolPts,
    int radius)
{
    HBRUSH brush, oldBrush;
    HPEN pen, oldPen;
    HDC dc;
    TkWinDCState state;
    register RbcPoint2D *pointPtr, *endPtr;
    if(drawable == None) {
        return; /* Huh? */
    }
    if((penPtr->symbol.fillGC == NULL) && (penPtr->symbol.outlineWidth == 0)) {
        return;
    }
    dc = TkWinGetDrawableDC(display, drawable, &state);
    /* RbcSetROP2(dc, 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 = RbcGCToPen(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
/*
 *----------------------------------------------------------------------
 *
 * DrawCircles --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
DrawCircles(
    Display * display,
    Drawable drawable,
    Line * linePtr,
    LinePen * penPtr,
    int nSymbolPts,
    RbcPoint2D * symbolPts,
    int radius)
{
    register int i;
    XArc *arcArr;              /* Array of arcs (circle) */
    register XArc *arcPtr;
    int reqSize, nArcs;
    int s;
    int count;
    register RbcPoint2D *pointPtr, *endPtr;
    s = radius + radius;
    arcArr = (XArc *) ckalloc(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);
        }
    }
    ckfree((char *)arcArr);
}
#endif
/*
 *----------------------------------------------------------------------
 *
 * DrawSquares --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
DrawSquares(
    Display * display,
    Drawable drawable,
    Line * linePtr,
    LinePen * penPtr,
    int nSymbolPts,
    register RbcPoint2D * symbolPts,
    int r)
{
    XRectangle *rectArr;
    register RbcPoint2D *pointPtr, *endPtr;
    register XRectangle *rectPtr;
    int reqSize, nRects;
    int s;
    register int i;
    int count;
    s = r + r;
    rectArr = (XRectangle *) ckalloc(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);
        }
    }
    ckfree((char *)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(
    RbcGraph * graph,          /* 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 */
    RbcPoint2D * 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 RbcPoint2D *pointPtr, *endPtr;
    Tk_Window tkWin = graph->widget.tkWin;
    if (tkWin == NULL) {
        return;
    }

#define SQRT_PI        1.77245385090552
#define S_RATIO        0.886226925452758
    if(size < 3) {
        if(penPtr->symbol.fillGC != NULL) {
    XPoint *points;
            points = (XPoint *) ckalloc(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(graph->widget.display, drawable, penPtr->symbol.fillGC, points,
                nSymbolPts, CoordModeOrigin);
            ckfree((char *)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(graph->widget.display, drawable, linePtr, penPtr, nSymbolPts,
            symbolPts, r2);
        break;
    case SYMBOL_CIRCLE:
        DrawCircles(graph->widget.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 = (XSegment *) ckalloc(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(graph->widget.display);
        for(i = 0; i < nSegs; i += reqSize) {
            chunk = ((i + reqSize) > nSegs) ? (nSegs - i) : reqSize;
            XDrawSegments(graph->widget.display, drawable,
                penPtr->symbol.outlineGC, segArr + i, chunk);
        }
        ckfree((char *)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 = (XPoint *) ckalloc(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(graph->widget.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(graph->widget.display, drawable,
                    penPtr->symbol.outlineGC, p, 13, CoordModeOrigin);
            }
        }
        ckfree((char *)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 = (XPoint *) ckalloc(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(graph->widget.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(graph->widget.display, drawable,
                    penPtr->symbol.outlineGC, p, 5, CoordModeOrigin);
            }
        }
        ckfree((char *)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 = (XPoint *) ckalloc(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(graph->widget.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(graph->widget.display, drawable,
                    penPtr->symbol.outlineGC, p, 4, CoordModeOrigin);
            }
        }
        ckfree((char *)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(graph->widget.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(graph->widget.display, penPtr->symbol.outlineGC, None);
        if(penPtr->symbol.mask != None) {
            mask =
                RbcScaleBitmap(tkWin, penPtr->symbol.mask, width,
                height, bmWidth, bmHeight);
            XSetClipMask(graph->widget.display, penPtr->symbol.outlineGC, mask);
        }
        bitmap =
            RbcScaleBitmap(tkWin, penPtr->symbol.bitmap, width, height,
            bmWidth, bmHeight);
        if(penPtr->symbol.fillGC == NULL) {
            XSetClipMask(graph->widget.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(graph->widget.display,
                            penPtr->symbol.outlineGC, x, y);
                    }
                    XCopyPlane(graph->widget.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(graph->widget.display,
                        penPtr->symbol.outlineGC, x, y);
                }
                XCopyPlane(graph->widget.display, bitmap, drawable,
                    penPtr->symbol.outlineGC, 0, 0, bmWidth, bmHeight, x, y, 1);
            }
        }
        Tk_FreePixmap(graph->widget.display, bitmap);
        if(mask != None) {
            Tk_FreePixmap(graph->widget.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(
    RbcGraph * graph,          /* Graph widget record */
    Drawable drawable,         /* Pixmap or window to draw into */
    RbcElement * elemPtr,      /* Line element information */
    int x,                     /* Center position of symbol */
    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(graph->widget.display, drawable, penPtr->traceGC, x - size,
            y, x + size, y);
        XDrawLine(graph->widget.display, drawable, penPtr->traceGC, x - size,
            y + 1, x + size, y + 1);
    }
    if(penPtr->symbol.type != SYMBOL_NONE) {
    RbcPoint2D point;
        point.x = x, point.y = y;
        DrawSymbols(graph, drawable, linePtr, linePtr->normalPenPtr, size,
            1, &point);
    }
}

#ifdef _WIN32
/*
 *----------------------------------------------------------------------
 *
 * DrawTraces --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
DrawTraces(
    RbcGraph * graph,
    Drawable drawable,         /* Pixmap or window to draw into */
    Line * linePtr,
    LinePen * penPtr)
{
RbcChainLink *linkPtr;
HBRUSH brush, oldBrush;
HDC dc;
HPEN pen, oldPen;
POINT *points;
TkWinDCState state;
LineTrace *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 = RbcMaxRequestSize(graph->widget.display, sizeof(POINT)) - 1;
    }
    points = (POINT *) ckalloc((nPoints + 1) * sizeof(POINT));
    dc = TkWinGetDrawableDC(graph->widget.display, drawable, &state);
    pen = RbcGCToPen(dc, penPtr->traceGC);
    oldPen = SelectPen(dc, pen);
    brush = CreateSolidBrush(penPtr->traceGC->foreground);
    oldBrush = SelectBrush(dc, brush);
    RbcSetROP2(dc, penPtr->traceGC->function);
    for(linkPtr = RbcChainFirstLink(linePtr->traces); linkPtr != NULL;
        linkPtr = RbcChainNextLink(linkPtr)) {
        tracePtr = RbcChainGetValue(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);
        }
    }
    ckfree((char *)points);
    DeletePen(SelectPen(dc, oldPen));
    DeleteBrush(SelectBrush(dc, oldBrush));
    TkWinReleaseDrawableDC(drawable, dc, &state);
}
#else
/*
 *----------------------------------------------------------------------
 *
 * DrawTraces --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
DrawTraces(
    RbcGraph * graph,
    Drawable drawable,         /* Pixmap or window to draw into */
    Line * linePtr,
    LinePen * penPtr)
{
RbcChainLink *linkPtr;
LineTrace *tracePtr;
XPoint *points;
int j;
int nPoints, remaining;
register XPoint *p;
register int count;
    nPoints = RbcMaxRequestSize(graph->widget.display, sizeof(XPoint)) - 1;
    points = (XPoint *) ckalloc((nPoints + 1) * sizeof(XPoint));
    for(linkPtr = RbcChainFirstLink(linePtr->traces); linkPtr != NULL;
        linkPtr = RbcChainNextLink(linkPtr)) {
int n;
        tracePtr = RbcChainGetValue(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(graph->widget.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(graph->widget.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(graph->widget.display, drawable, penPtr->traceGC, points,
                remaining + 1, CoordModeOrigin);
        }
    }
    ckfree((char *)points);
}
#endif /* _WIN32 */
/*
 *----------------------------------------------------------------------
 *
 * DrawValues --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
DrawValues(
    RbcGraph * graph,
    Drawable drawable,
    Line * linePtr,
    LinePen * penPtr,
    int nSymbolPts,
    RbcPoint2D * symbolPts,
    int *pointToData)
{
    RbcPoint2D *pointPtr, *endPtr;
    int count;
    char string[TCL_DOUBLE_SPACE * 2 + 2];
    const char *fmt;
    double x, y;
    Tk_Window tkWin = graph->widget.tkWin;
    if (tkWin == NULL) {
        return;
    }

    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 == RBC_SHOW_X) {
            sprintf(string, fmt, x);
        } else if(penPtr->valueShow == RBC_SHOW_Y) {
            sprintf(string, fmt, y);
        } else if(penPtr->valueShow == RBC_SHOW_BOTH) {
            sprintf(string, fmt, x);
            strcat(string, ",");
            sprintf(string + strlen(string), fmt, y);
        }
        RbcDrawText(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(
    RbcGraph * graph,          /* Graph widget record */
    Drawable drawable,         /* Pixmap or window to draw into */
    RbcElement * 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 & RBC_ACTIVE_PENDING) {
            MapActiveSymbols(graph, linePtr);
        }
        if(penPtr->symbol.type != SYMBOL_NONE) {
            DrawSymbols(graph, drawable, linePtr, penPtr, symbolSize,
                linePtr->nActivePts, linePtr->activePts);
        }
        if(penPtr->valueShow != RBC_SHOW_NONE) {
            DrawValues(graph, drawable, linePtr, penPtr,
                linePtr->nActivePts, linePtr->activePts, linePtr->activeToData);
        }
    } else if(linePtr->nActiveIndices < 0) {
        if(penPtr->traceWidth > 0) {
            if(linePtr->nStrips > 0) {
                RbcDraw2DSegments(graph->widget.display, drawable,
                    penPtr->traceGC, linePtr->strips, linePtr->nStrips);
            } else if(RbcChainGetLength(linePtr->traces) > 0) {
                DrawTraces(graph, drawable, linePtr, penPtr);
            }
        }
        if(penPtr->symbol.type != SYMBOL_NONE) {
            DrawSymbols(graph, drawable, linePtr, penPtr, symbolSize,
                linePtr->nSymbolPts, linePtr->symbolPts);
        }
        if(penPtr->valueShow != RBC_SHOW_NONE) {
            DrawValues(graph, 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(
    RbcGraph * graph,          /* Graph widget record */
    Drawable drawable,         /* Pixmap or window to draw into */
    RbcElement * elemPtr)
{              /* Element to be drawn */
Line *linePtr = (Line *) elemPtr;
LinePen *penPtr;
RbcChainLink *linkPtr;
register LinePenStyle *stylePtr;
unsigned int count;
    Tk_Window tkWin = graph->widget.tkWin;
    if (tkWin == NULL) {
        return;
    }

    /* Fill area under the curve */
    if(linePtr->fillPts != NULL) {
XPoint *points;
RbcPoint2D *endPtr, *pointPtr;
        points = (XPoint *) ckalloc(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(linePtr->fillTile != NULL) {
            RbcSetTileOrigin(tkWin, linePtr->fillTile, 0, 0);
            RbcTilePolygon(tkWin, drawable, linePtr->fillTile,
                points, linePtr->nFillPts);
        } else if(linePtr->fillStipple != None) {
            XFillPolygon(graph->widget.display, drawable, linePtr->fillGC,
                points, linePtr->nFillPts, Complex, CoordModeOrigin);
        }
        ckfree((char *)points);
    }
    /* Lines: stripchart segments or graph traces. */
    if(linePtr->nStrips > 0) {
        for(linkPtr = RbcChainFirstLink(linePtr->palette); linkPtr != NULL;
            linkPtr = RbcChainNextLink(linkPtr)) {
            stylePtr = RbcChainGetValue(linkPtr);
            penPtr = stylePtr->penPtr;
            if((stylePtr->nStrips > 0) && (penPtr->errorBarLineWidth > 0)) {
                RbcDraw2DSegments(graph->widget.display, drawable,
                    penPtr->traceGC, stylePtr->strips, stylePtr->nStrips);
            }
        }
    } else if((RbcChainGetLength(linePtr->traces) > 0) &&
        (linePtr->normalPenPtr->traceWidth > 0)) {
        DrawTraces(graph, drawable, linePtr, linePtr->normalPenPtr);
    }
    if(linePtr->reqMaxSymbols > 0) {
int total;
        total = 0;
        for(linkPtr = RbcChainFirstLink(linePtr->palette); linkPtr != NULL;
            linkPtr = RbcChainNextLink(linkPtr)) {
            stylePtr = RbcChainGetValue(linkPtr);
            total += stylePtr->nSymbolPts;
        }
        linePtr->symbolInterval = total / linePtr->reqMaxSymbols;
        linePtr->symbolCounter = 0;
    }
    /* Symbols, error bars, values. */
    count = 0;
    for(linkPtr = RbcChainFirstLink(linePtr->palette); linkPtr != NULL;
        linkPtr = RbcChainNextLink(linkPtr)) {
        stylePtr = RbcChainGetValue(linkPtr);
        penPtr = stylePtr->penPtr;
        if((stylePtr->xErrorBarCnt > 0) && (penPtr->errorBarShow & RBC_SHOW_X)) {
            RbcDraw2DSegments(graph->widget.display, drawable, penPtr->errorBarGC,
                stylePtr->xErrorBars, stylePtr->xErrorBarCnt);
        }
        if((stylePtr->yErrorBarCnt > 0) && (penPtr->errorBarShow & RBC_SHOW_Y)) {
            RbcDraw2DSegments(graph->widget.display, drawable, penPtr->errorBarGC,
                stylePtr->yErrorBars, stylePtr->yErrorBarCnt);
        }
        if((stylePtr->nSymbolPts > 0) && (penPtr->symbol.type != SYMBOL_NONE)) {
            DrawSymbols(graph, drawable, linePtr, penPtr,
                stylePtr->symbolSize, stylePtr->nSymbolPts,
                stylePtr->symbolPts);
        }
        if(penPtr->valueShow != RBC_SHOW_NONE) {
            DrawValues(graph, 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.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 * -----------------------------------------------------------------
 */
static void
GetSymbolPostScriptInfo(
    RbcGraph * graph,
    RbcPsToken * 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 == RBC_COLOR_DEFAULT) {
        fillColor = defaultColor;
    }
    if(outlineColor == RBC_COLOR_DEFAULT) {
        outlineColor = defaultColor;
    }
    if(penPtr->symbol.type == SYMBOL_NONE) {
        RbcLineAttributesToPostScript(psToken, defaultColor,
            penPtr->traceWidth + 2, &(penPtr->traceDashes), CapButt, JoinMiter);
    } else {
        RbcLineWidthToPostScript(psToken, penPtr->symbol.outlineWidth);
        RbcLineDashesToPostScript(psToken, (RbcDashes *) 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.
     */
    RbcAppendToPostScript(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(graph->widget.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)) {
            RbcAppendToPostScript(psToken,
                "\n  % Bitmap mask is \"",
                Tk_NameOfBitmap(graph->widget.display, penPtr->symbol.mask),
                "\"\n\n  ", (char *)NULL);
            RbcBackgroundToPostScript(psToken, fillColor);
            RbcBitmapToPostScript(psToken, graph->widget.display,
                penPtr->symbol.mask, scale, scale);
        }
        RbcAppendToPostScript(psToken,
            "\n  % Bitmap symbol is \"",
            Tk_NameOfBitmap(graph->widget.display, penPtr->symbol.bitmap),
            "\"\n\n  ", (char *)NULL);
        RbcForegroundToPostScript(psToken, outlineColor);
        RbcBitmapToPostScript(psToken, graph->widget.display,
            penPtr->symbol.bitmap, scale, scale);
    }
        break;
    default:
        if(fillColor != NULL) {
            RbcAppendToPostScript(psToken, "  ", (char *)NULL);
            RbcBackgroundToPostScript(psToken, fillColor);
            RbcAppendToPostScript(psToken, "  Fill\n", (char *)NULL);
        }
        if((outlineColor != NULL) && (penPtr->symbol.outlineWidth > 0)) {
            RbcAppendToPostScript(psToken, "  ", (char *)NULL);
            RbcForegroundToPostScript(psToken, outlineColor);
            RbcAppendToPostScript(psToken, "  stroke\n", (char *)NULL);
        }
        break;
    }
    RbcAppendToPostScript(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.
 *
 *      Most notable problem is the round-off errors generated when
 *      calculating the centered position of the symbol.
 *
 * Results:
 *      None.
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 * -----------------------------------------------------------------
 */
static void
SymbolsToPostScript(
    RbcGraph * graph,
    RbcPsToken * psToken,
    LinePen * penPtr,
    int size,
    int nSymbolPts,
    RbcPoint2D * symbolPts)
{
    double symbolSize;
    register RbcPoint2D *pointPtr, *endPtr;
    static const char *symbolMacros[] = {
        "Li", "Sq", "Ci", "Di", "Pl", "Cr", "Sp", "Sc", "Tr", "Ar", "Bm",
        (char *)NULL,
    };
    GetSymbolPostScriptInfo(graph, 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++) {
        RbcFormatToPostScript(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(
    RbcGraph * graph,          /* Graph widget record */
    RbcPsToken * psToken,
    RbcElement * elemPtr,      /* Line element information */
    double x,                  /* Center position of symbol */
    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.
         */
        RbcLineAttributesToPostScript(psToken, penPtr->traceColor,
            penPtr->traceWidth + 2, &(penPtr->traceDashes), CapButt, JoinMiter);
        RbcFormatToPostScript(psToken, "%g %g %d Li\n", x, y, size + size);
    }
    if(penPtr->symbol.type != SYMBOL_NONE) {
    RbcPoint2D point;
        point.x = x, point.y = y;
        SymbolsToPostScript(graph, psToken, penPtr, size, 1, &point);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * SetLineAttributes --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
SetLineAttributes(
    RbcPsToken * psToken,
    LinePen * penPtr)
{
    /* Set the attributes of the line (color, dashes, linewidth) */
    RbcLineAttributesToPostScript(psToken, penPtr->traceColor,
        penPtr->traceWidth, &(penPtr->traceDashes), CapButt, JoinMiter);
    if((RbcLineIsDashed(penPtr->traceDashes)) &&
        (penPtr->traceOffColor != NULL)) {
        RbcAppendToPostScript(psToken, "/DashesProc {\n  gsave\n    ",
            (char *)NULL);
        RbcBackgroundToPostScript(psToken, penPtr->traceOffColor);
        RbcAppendToPostScript(psToken, "    ", (char *)NULL);
        RbcLineDashesToPostScript(psToken, (RbcDashes *) NULL);
        RbcAppendToPostScript(psToken, "stroke\n  grestore\n} def\n",
            (char *)NULL);
    } else {
        RbcAppendToPostScript(psToken, "/DashesProc {} def\n", (char *)NULL);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TracesToPostScript --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
TracesToPostScript(
    RbcPsToken * psToken,
    Line * linePtr,
    LinePen * penPtr)
{
RbcChainLink *linkPtr;
LineTrace *tracePtr;
register RbcPoint2D *pointPtr, *endPtr;
int count;
    SetLineAttributes(psToken, penPtr);
    for(linkPtr = RbcChainFirstLink(linePtr->traces); linkPtr != NULL;
        linkPtr = RbcChainNextLink(linkPtr)) {
        tracePtr = RbcChainGetValue(linkPtr);
        if(tracePtr->nScreenPts <= 0) {
            continue;
        }
#define PS_MAXPATH    1500    /* Maximum number of components in a PostScript
                                 * (level 1) path. */
        pointPtr = tracePtr->screenPts;
        RbcFormatToPostScript(psToken, " newpath %g %g moveto\n",
            pointPtr->x, pointPtr->y);
        pointPtr++;
        count = 0;
        for(endPtr = tracePtr->screenPts + (tracePtr->nScreenPts - 1);
            pointPtr < endPtr; pointPtr++) {
            RbcFormatToPostScript(psToken, " %g %g lineto\n",
                pointPtr->x, pointPtr->y);
            if((count % PS_MAXPATH) == 0) {
                RbcFormatToPostScript(psToken,
                    "DashesProc stroke\n newpath  %g %g moveto\n",
                    pointPtr->x, pointPtr->y);
            }
            count++;
        }
        RbcFormatToPostScript(psToken, " %g %g lineto\n",
            pointPtr->x, pointPtr->y);
        RbcAppendToPostScript(psToken, "DashesProc stroke\n", (char *)NULL);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * ValuesToPostScript --
 *
 *      TODO: Description
 *
 * Results:
 *      TODO: Results
 *
 * Side Effects:
 *      TODO: Side Effects
 *
 *----------------------------------------------------------------------
 */
static void
ValuesToPostScript(
    RbcPsToken * psToken,
    Line * linePtr,
    LinePen * penPtr,
    int nSymbolPts,
    RbcPoint2D * symbolPts,
    int *pointToData)
{
    RbcPoint2D *pointPtr, *endPtr;
    int count;
    char string[TCL_DOUBLE_SPACE * 2 + 2];
    const 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 == RBC_SHOW_X) {
            sprintf(string, fmt, x);
        } else if(penPtr->valueShow == RBC_SHOW_Y) {
            sprintf(string, fmt, y);
        } else if(penPtr->valueShow == RBC_SHOW_BOTH) {
            sprintf(string, fmt, x);
            strcat(string, ",");
            sprintf(string + strlen(string), fmt, y);
        }
        RbcTextToPostScript(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(
    RbcGraph * graph,
    RbcPsToken * psToken,
    RbcElement * 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 & RBC_ACTIVE_PENDING) {
            MapActiveSymbols(graph, linePtr);
        }
        if(penPtr->symbol.type != SYMBOL_NONE) {
            SymbolsToPostScript(graph, psToken, penPtr, symbolSize,
                linePtr->nActivePts, linePtr->activePts);
        }
        if(penPtr->valueShow != RBC_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);
                Rbc2DSegmentsToPostScript(psToken, linePtr->strips,
                    linePtr->nStrips);
            }
            if(RbcChainGetLength(linePtr->traces) > 0) {
                TracesToPostScript(psToken, linePtr, (LinePen *) penPtr);
            }
        }
        if(penPtr->symbol.type != SYMBOL_NONE) {
            SymbolsToPostScript(graph, psToken, penPtr, symbolSize,
                linePtr->nSymbolPts, linePtr->symbolPts);
        }
        if(penPtr->valueShow != RBC_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(
    RbcGraph * graph,
    RbcPsToken * psToken,
    RbcElement * elemPtr)
{
Line *linePtr = (Line *) elemPtr;
register LinePenStyle *stylePtr;
RbcChainLink *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. */
        RbcPathToPostScript(psToken, linePtr->fillPts, linePtr->nFillPts);
        RbcAppendToPostScript(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) {
            RbcBackgroundToPostScript(psToken, linePtr->fillBgColor);
            RbcAppendToPostScript(psToken, "Fill\n", (char *)NULL);
        }
        RbcForegroundToPostScript(psToken, linePtr->fillFgColor);
        if(linePtr->fillTile != NULL) {
            /* TBA: Transparent tiling is the hard part. */
        } else if((linePtr->fillStipple != None) &&
            (linePtr->fillStipple != PATTERN_SOLID)) {
            /* Draw the stipple in the foreground color. */
            RbcStippleToPostScript(psToken, graph->widget.display,
                linePtr->fillStipple);
        } else {
            RbcAppendToPostScript(psToken, "Fill\n", (char *)NULL);
        }
    }
    /* Draw lines */
    if(linePtr->nStrips > 0) {
        for(linkPtr = RbcChainFirstLink(linePtr->palette); linkPtr != NULL;
            linkPtr = RbcChainNextLink(linkPtr)) {
            stylePtr = RbcChainGetValue(linkPtr);
            penPtr = stylePtr->penPtr;
            if((stylePtr->nStrips > 0) && (penPtr->traceWidth > 0)) {
                SetLineAttributes(psToken, penPtr);
                Rbc2DSegmentsToPostScript(psToken, stylePtr->strips,
                    stylePtr->nStrips);
            }
        }
    } else if((RbcChainGetLength(linePtr->traces) > 0) &&
        (linePtr->normalPenPtr->traceWidth > 0)) {
        TracesToPostScript(psToken, linePtr, linePtr->normalPenPtr);
    }
    /* Draw symbols, error bars, values. */
    count = 0;
    for(linkPtr = RbcChainFirstLink(linePtr->palette); linkPtr != NULL;
        linkPtr = RbcChainNextLink(linkPtr)) {
        stylePtr = RbcChainGetValue(linkPtr);
        penPtr = stylePtr->penPtr;
        colorPtr = penPtr->errorBarColor;
        if(colorPtr == RBC_COLOR_DEFAULT) {
            colorPtr = penPtr->traceColor;
        }
        if((stylePtr->xErrorBarCnt > 0) && (penPtr->errorBarShow & RBC_SHOW_X)) {
            RbcLineAttributesToPostScript(psToken, colorPtr,
                penPtr->errorBarLineWidth, NULL, CapButt, JoinMiter);
            Rbc2DSegmentsToPostScript(psToken, stylePtr->xErrorBars,
                stylePtr->xErrorBarCnt);
        }
        if((stylePtr->yErrorBarCnt > 0) && (penPtr->errorBarShow & RBC_SHOW_Y)) {
            RbcLineAttributesToPostScript(psToken, colorPtr,
                penPtr->errorBarLineWidth, NULL, CapButt, JoinMiter);
            Rbc2DSegmentsToPostScript(psToken, stylePtr->yErrorBars,
                stylePtr->yErrorBarCnt);
        }
        if((stylePtr->nSymbolPts > 0) &&
            (stylePtr->penPtr->symbol.type != SYMBOL_NONE)) {
            SymbolsToPostScript(graph, psToken, penPtr,
                stylePtr->symbolSize, stylePtr->nSymbolPts,
                stylePtr->symbolPts);
        }
        if(penPtr->valueShow != RBC_SHOW_NONE) {
            ValuesToPostScript(psToken, linePtr, penPtr,
                stylePtr->nSymbolPts, stylePtr->symbolPts,
                linePtr->symbolToData + count);
        }
        count += stylePtr->nSymbolPts;
    }
}

#define FreeVector(v) \
    if ((v).clientId != NULL) { \
    Rbc_FreeVectorId((v).clientId); \
    } else if ((v).valueArr != NULL) { \
    ckfree((char *)(v).valueArr); \
    }
/*
 *----------------------------------------------------------------------
 *
 * DestroyLine --
 *
 *      Release memory and resources allocated for the line element.
 *
 * Results:
 *      None.
 *
 * Side effects:
 *      Everything associated with the line element is freed up.
 *
 *----------------------------------------------------------------------
 */
static void
DestroyLine(
    RbcGraph * graph,
    RbcElement * elemPtr)
{
Line *linePtr = (Line *) elemPtr;
    if(linePtr->normalPenPtr != &(linePtr->builtinPen)) {
        RbcFreePen(graph, (RbcPen *) linePtr->normalPenPtr);
    }
    DestroyPen(graph, (RbcPen *) & (linePtr->builtinPen));
    if(linePtr->activePenPtr != NULL) {
        RbcFreePen(graph, (RbcPen *) 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) {
        RbcFreePalette(graph, linePtr->palette);
        RbcChainDestroy(linePtr->palette);
    }
    if(linePtr->tags != NULL) {
        ckfree((char *)linePtr->tags);
    }
    if(linePtr->activeIndices != NULL) {
        ckfree((char *)linePtr->activeIndices);
    }
    if(linePtr->fillPts != NULL) {
        ckfree((char *)linePtr->fillPts);
    }
    if(linePtr->fillTile != NULL) {
        RbcFreeTile(linePtr->fillTile);
    }
    if((linePtr->fillStipple != None) &&
        (linePtr->fillStipple != PATTERN_SOLID)) {
        Tk_FreeBitmap(graph->widget.display, linePtr->fillStipple);
    }
    if(linePtr->fillGC != NULL) {
        Tk_FreeGC(graph->widget.display, linePtr->fillGC);
    }
}

static RbcElementProcs 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. */
};

/*
 *----------------------------------------------------------------------
 *
 * RbcLineElement --
 *
 *      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.
 *
 *----------------------------------------------------------------------
 */
RbcElement *
RbcLineElement(
    RbcGraph * graph,
    const char *name,
    Tk_Uid classUid)
{
    register Line *linePtr;
    linePtr=ckalloc(sizeof(Line));
    assert(linePtr);
    memset(linePtr,0,sizeof(Line));
    linePtr->procsPtr = &lineProcs;
    if(classUid == rbcLineElementUid) {
        linePtr->configSpecs = lineElemConfigSpecs;
    } else {
        linePtr->configSpecs = stripElemConfigSpecs;
    }
    /* By default an element's name and label are the same. */
    linePtr->label = RbcStrdup(name);
    linePtr->name = RbcStrdup(name);
    linePtr->classUid = classUid;
    linePtr->flags = RBC_SCALE_SYMBOL;
    linePtr->graph = graph;
    linePtr->labelRelief = TK_RELIEF_FLAT;
    linePtr->normalPenPtr = &linePtr->builtinPen;
    linePtr->palette = RbcChainCreate();
    linePtr->penDir = PEN_BOTH_DIRECTIONS;
    linePtr->reqSmooth = PEN_SMOOTH_NONE;
    InitPen(linePtr->normalPenPtr);
    return (RbcElement *) linePtr;
}

/*
* -----------------------------------------------------------------------
*
* SearchSpline --
*
*      Conducts a binary search for a value.  This routine is called
*      only if key is between x(0) and x(len - 1).
*
* Results:
*      Returns the index of the largest value in xtab for which
*      x[i] < key.
*
* Side effects:
*      TODO: Side Effects
*
* -----------------------------------------------------------------------
*/
static int
SearchSpline(
    RbcPoint2D points[],       /* Contains the abscissas of the data
                               * points of interpolation. */
    int nPoints,               /* Dimension of x. */
    double key,                /* Value whose relative position in
                               * x is to be located. */
    int *foundPtr)
{              /* (out) Returns 1 if s is found in
               * x and 0 otherwise. */
    int high, low, mid;

    low = 0;
    high = nPoints - 1;

    while (high >= low) {
        mid = (high + low) / 2;
        if (key > points[mid].x) {
            low = mid + 1;
        }
        else if (key < points[mid].x) {
            high = mid - 1;
        }
        else {
            *foundPtr = 1;
            return mid;
        }
    }
    *foundPtr = 0;
    return low;
}

/*
*-----------------------------------------------------------------------
*
* QuadChoose --
*
*      Determines the case needed for the computation of the parame-
*      ters of the quadratic spline.
*
* Results:
*      Returns a case number (1-4) which controls how the parameters
*      of the quadratic spline are evaluated.
*
* Side effects:
*      TODO: Side Effects
*
*-----------------------------------------------------------------------
*/
static int
QuadChoose(
    RbcPoint2D * p,            /* Coordinates of one of the points of
                               * interpolation */
    RbcPoint2D * q,            /* Coordinates of one of the points of
                               * interpolation */
    double m1,                 /* Derivative condition at point P */
    double m2,                 /* Derivative condition at point Q */
    double epsilon)
{              /* Error tolerance used to distinguish
               * cases when m1 or m2 is relatively
               * close to the slope or twice the
               * slope of the line segment joining
               * the points P and Q.  If
               * epsilon is not 0.0, then epsilon
               * should be greater than or equal to
               * machine epsilon.  */
    double slope;

    /* Calculate the slope of the line joining P and Q. */
    slope = (q->y - p->y) / (q->x - p->x);

    if (slope != 0.0) {
        double relerr;
        double mref, mref1, mref2, prod1, prod2;

        prod1 = slope * m1;
        prod2 = slope * m2;

        /* Find the absolute values of the slopes slope, m1, and m2. */
        mref = FABS(slope);
        mref1 = FABS(m1);
        mref2 = FABS(m2);

        /*
        * If the relative deviation of m1 or m2 from slope is less than
        * epsilon, then choose case 2 or case 3.
        */
        relerr = epsilon * mref;
        if ((FABS(slope - m1) > relerr) && (FABS(slope - m2) > relerr) &&
            (prod1 >= 0.0) && (prod2 >= 0.0)) {
            double prod;

            prod = (mref - mref1) * (mref - mref2);
            if (prod < 0.0) {
                /*
                * l1, the line through (x1,y1) with slope m1, and l2,
                * the line through (x2,y2) with slope m2, intersect
                * at a point whose abscissa is between x1 and x2.
                * The abscissa becomes a knot of the spline.
                */
                return 1;
            }
            if (mref1 >(mref * 2.0)) {
                if (mref2 <= ((2.0 - epsilon) * mref)) {
                    return 3;
                }
            }
            else if (mref2 <= (mref * 2.0)) {
                /*
                * Both l1 and l2 cross the line through
                * (x1+x2)/2.0,y1 and (x1+x2)/2.0,y2, which is the
                * midline of the rectangle formed by P and Q or both
                * m1 and m2 have signs different than the sign of
                * slope, or one of m1 and m2 has opposite sign from
                * slope and l1 and l2 intersect to the left of x1 or
                * to the right of x2.  The point (x1+x2)/2. is a knot
                * of the spline.
                */
                return 2;
            }
            else if (mref1 <= ((2.0 - epsilon) * mref)) {
                /*
                * In cases 3 and 4, sign(m1)=sign(m2)=sign(slope).
                * Either l1 or l2 crosses the midline, but not both.
                * Choose case 4 if mref1 is greater than
                * (2.-epsilon)*mref; otherwise, choose case 3.
                */
                return 3;
            }
            /*
            * If neither l1 nor l2 crosses the midline, the spline
            * requires two knots between x1 and x2.
            */
            return 4;
        }
        else {
            /*
            * The sign of at least one of the slopes m1 or m2 does not
            * agree with the sign of *slope*.
            */
            if ((prod1 < 0.0) && (prod2 < 0.0)) {
                return 2;
            }
            else if (prod1 < 0.0) {
                if (mref2 >((epsilon + 1.0) * mref)) {
                    return 1;
                }
                else {
                    return 2;
                }
            }
            else if (mref1 > ((epsilon + 1.0) * mref)) {
                return 1;
            }
            else {
                return 2;
            }
        }
    }
    else if ((m1 * m2) >= 0.0) {
        return 2;
    }
    else {
        return 1;
    }
}

/*
* -----------------------------------------------------------------------
*
* QuadCases --
*
*       Computes the knots and other parameters of the spline on the
*       interval PQ.
*
*
*       On input--
*
*         P and Q    the coordinates of the points of interpolation.
*         m1         the slope at P.
*         m2         the slope at Q.
*         ncase      controls the number and location of the knots.
*
*
*      On output--
*
*        (v1,v2),(w1,w2),(z1,z2), (e1,e2)    the coordinates of the
*                                            knots and other
*                                            parameters of the spline
*                                            on P.  (e1,e2) and Q
*                                            are used only if ncase=4.
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
* -----------------------------------------------------------------------
*/
static void
QuadCases(
    RbcPoint2D * p,
    RbcPoint2D * q,
    double m1,
    double m2,
    double param[],
    int which)
{
    if ((which == 3) || (which == 4)) {  /* Parameters used in both 3 and 4 */
        double mbar1, mbar2, mbar3, c1, d1, h1, j1, k1;

        c1 = p->x + (q->y - p->y) / m1;
        d1 = q->x + (p->y - q->y) / m2;
        h1 = c1 * 2.0 - p->x;
        j1 = d1 * 2.0 - q->x;
        mbar1 = (q->y - p->y) / (h1 - p->x);
        mbar2 = (p->y - q->y) / (j1 - q->x);

        if (which == 4) {        /* Case 4. */
            Y1 = (p->x + c1) / 2.0;
            V1 = (p->x + Y1) / 2.0;
            V2 = m1 * (V1 - p->x) + p->y;
            Z1 = (d1 + q->x) / 2.0;
            W1 = (q->x + Z1) / 2.0;
            W2 = m2 * (W1 - q->x) + q->y;
            mbar3 = (W2 - V2) / (W1 - V1);
            Y2 = mbar3 * (Y1 - V1) + V2;
            Z2 = mbar3 * (Z1 - V1) + V2;
            E1 = (Y1 + Z1) / 2.0;
            E2 = mbar3 * (E1 - V1) + V2;
        }
        else {        /* Case 3. */
            k1 = (p->y - q->y + q->x * mbar2 - p->x * mbar1) / (mbar2 - mbar1);
            if (FABS(m1) > FABS(m2)) {
                Z1 = (k1 + p->x) / 2.0;
            }
            else {
                Z1 = (k1 + q->x) / 2.0;
            }
            V1 = (p->x + Z1) / 2.0;
            V2 = p->y + m1 * (V1 - p->x);
            W1 = (q->x + Z1) / 2.0;
            W2 = q->y + m2 * (W1 - q->x);
            Z2 = V2 + (W2 - V2) / (W1 - V1) * (Z1 - V1);
        }
    }
    else if (which == 2) {     /* Case 2. */
        Z1 = (p->x + q->x) / 2.0;
        V1 = (p->x + Z1) / 2.0;
        V2 = p->y + m1 * (V1 - p->x);
        W1 = (Z1 + q->x) / 2.0;
        W2 = q->y + m2 * (W1 - q->x);
        Z2 = (V2 + W2) / 2.0;
    }
    else {   /* Case 1. */
        double ztwo;

        Z1 = (p->y - q->y + m2 * q->x - m1 * p->x) / (m2 - m1);
        ztwo = p->y + m1 * (Z1 - p->x);
        V1 = (p->x + Z1) / 2.0;
        V2 = (p->y + ztwo) / 2.0;
        W1 = (Z1 + q->x) / 2.0;
        W2 = (ztwo + q->y) / 2.0;
        Z2 = V2 + (W2 - V2) / (W1 - V1) * (Z1 - V1);
    }
}

/*
*--------------------------------------------------------------
*
* QuadSelect --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
static int
QuadSelect(
    RbcPoint2D * p,
    RbcPoint2D * q,
    double m1,
    double m2,
    double epsilon,
    double param[])
{
    int ncase;

    ncase = QuadChoose(p, q, m1, m2, epsilon);
    QuadCases(p, q, m1, m2, param, ncase);
    return ncase;
}

/*
*--------------------------------------------------------------
*
* QuadGetImage --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
static double
QuadGetImage(
    double p1,
    double p2,
    double p3,
    double x1,
    double x2,
    double x3)
{
    double A, B, C;
    double y;

    A = x1 - x2;
    B = x2 - x3;
    C = x1 - x3;

    y = (p1 * (A * A) + p2 * 2.0 * B * A + p3 * (B * B)) / (C * C);
    return y;
}

/*
* -----------------------------------------------------------------------
*
* QuadSpline --
*
*      Finds the image of a point in x.
*
*      On input
*
*        x                             Contains the value at which
*                                      the spline is evaluated.
*        leftX, leftY                  Coordinates of the left-hand
*                                      data point used in the
*                                      evaluation of x values.
*        rightX, rightY                Coordinates of the right-hand
*                                      data point used in the
*                                      evaluation of x values.
*        Z1, Z2, Y1, Y2, E2, W2, V2    Parameters of the spline.
*        ncase                         Controls the evaluation of
*                                      the spline by indicating
*                                      whether one or two knots
*                                      were placed in the interval
*                                      (xtabs,xtabs1).
*
* Results:
*      The image of the spline at x.
*
* Side effects:
*      TODO: Side Effects
*
* -----------------------------------------------------------------------
*/
static void
QuadSpline(
    RbcPoint2D * intp,         /* Value at which spline is evaluated */
    RbcPoint2D * left,         /* Point to the left of the data point to
                               * be evaluated */
    RbcPoint2D * right,        /* Point to the right of the data point to
                               * be evaluated */
    double param[],            /* Parameters of the spline */
    int ncase)
{              /* Controls the evaluation of the
               * spline by indicating whether one or
               * two knots were placed in the
               * interval (leftX,rightX) */
    double y;

    if (ncase == 4) {
        /*
        * Case 4:  More than one knot was placed in the interval.
        */

        /*
        * Determine the location of data point relative to the 1st knot.
        */
        if (Y1 > intp->x) {
            y = QuadGetImage(left->y, V2, Y2, Y1, intp->x, left->x);
        }
        else if (Y1 < intp->x) {
            /*
            * Determine the location of the data point relative to
            * the 2nd knot.
            */
            if (Z1 > intp->x) {
                y = QuadGetImage(Y2, E2, Z2, Z1, intp->x, Y1);
            }
            else if (Z1 < intp->x) {
                y = QuadGetImage(Z2, W2, right->y, right->x, intp->x, Z1);
            }
            else {
                y = Z2;
            }
        }
        else {
            y = Y2;
        }
    }
    else {

        /*
        * Cases 1, 2, or 3:
        *
        * Determine the location of the data point relative to the
        * knot.
        */
        if (Z1 < intp->x) {
            y = QuadGetImage(Z2, W2, right->y, right->x, intp->x, Z1);
        }
        else if (Z1 > intp->x) {
            y = QuadGetImage(left->y, V2, Z2, Z1, intp->x, left->x);
        }
        else {
            y = Z2;
        }
    }
    intp->y = y;
}

/*
* -----------------------------------------------------------------------
*
* QuadSlopes --
*
*       Calculates the derivative at each of the data points.  The
*       slopes computed will insure that an osculatory quadratic
*       spline will have one additional knot between two adjacent
*       points of interpolation.  Convexity and monotonicity are
*       preserved wherever these conditions are compatible with the
*       data.
*
* Results:
*      The output array "m" is filled with the derivates at each
*      data point.
*
* Side effects:
*      TODO: Side Effects
*
* -----------------------------------------------------------------------
*/
static void
QuadSlopes(
    RbcPoint2D points[],
    double *m,                 /* (out) To be filled with the first
                               * derivative at each data point. */
    int nPoints)
{              /* Number of data points (dimension of
               * x, y, and m). */
    double xbar, xmid, xhat, ydif1, ydif2;
    double yxmid;
    double m1, m2;
    double m1s, m2s;
    register int i, n, l;

    m1s = m2s = m1 = m2 = 0;
    for (l = 0, i = 1, n = 2; i < (nPoints - 1); l++, i++, n++) {
        /*
        * Calculate the slopes of the two lines joining three
        * consecutive data points.
        */
        ydif1 = points[i].y - points[l].y;
        ydif2 = points[n].y - points[i].y;
        m1 = ydif1 / (points[i].x - points[l].x);
        m2 = ydif2 / (points[n].x - points[i].x);
        if (i == 1) {
            m1s = m1, m2s = m2; /* Save slopes of starting point */
        }
        /*
        * If one of the preceding slopes is zero or if they have opposite
        * sign, assign the value zero to the derivative at the middle
        * point.
        */
        if ((m1 == 0.0) || (m2 == 0.0) || ((m1 * m2) <= 0.0)) {
            m[i] = 0.0;
        }
        else if (FABS(m1) > FABS(m2)) {
            /*
            * Calculate the slope by extending the line with slope m1.
            */
            xbar = ydif2 / m1 + points[i].x;
            xhat = (xbar + points[n].x) / 2.0;
            m[i] = ydif2 / (xhat - points[i].x);
        }
        else {
            /*
            * Calculate the slope by extending the line with slope m2.
            */
            xbar = -ydif1 / m2 + points[i].x;
            xhat = (points[l].x + xbar) / 2.0;
            m[i] = ydif1 / (points[i].x - xhat);
        }
    }

    /* Calculate the slope at the last point, x(n). */
    i = nPoints - 2;
    n = nPoints - 1;
    if ((m1 * m2) < 0.0) {
        m[n] = m2 * 2.0;
    }
    else {
        xmid = (points[i].x + points[n].x) / 2.0;
        yxmid = m[i] * (xmid - points[i].x) + points[i].y;
        m[n] = (points[n].y - yxmid) / (points[n].x - xmid);
        if ((m[n] * m2) < 0.0) {
            m[n] = 0.0;
        }
    }

    /* Calculate the slope at the first point, x(0). */
    if ((m1s * m2s) < 0.0) {
        m[0] = m1s * 2.0;
    }
    else {
        xmid = (points[0].x + points[1].x) / 2.0;
        yxmid = m[1] * (xmid - points[1].x) + points[1].y;
        m[0] = (yxmid - points[0].y) / (xmid - points[0].x);
        if ((m[0] * m1s) < 0.0) {
            m[0] = 0.0;
        }
    }

}

/*
*--------------------------------------------------------------
*
* QuadEval --
*
*      QuadEval controls the evaluation of an osculatory
*      quadratic spline.  The user may provide his own
*      slopes at the points of interpolation or use the
*      subroutine 'QuadSlopes' to calculate slopes which
*      are consistent with the shape of the data.
*
*      ON INPUT--
*
*        intpPts      must be a nondecreasing vector of
*                     points at which the spline will be
*                     evaluated.
*        origPts      contains the abscissas of the data
*                     points to be interpolated. xtab must
*                     be increasing.
*        y            contains the ordinates of the data
*                     points to be interpolated.
*        m            contains the slope of the spline at
*                     each point of interpolation.
*        nPoints      number of data points (dimension of
*                     xtab and y).
*        numEval      the number of points of evaluation
*                     (dimension of xval and yval).
*        epsilon      a relative error tolerance used in
*                     subroutine 'QuadChoose' to distinguish
*                     the situation m(i) or m(i+1) is
*                     relatively close to the slope or twice
*                     the slope of the linear segment
*                     between xtab(i) and xtab(i+1).  If
*                     this situation occurs, roundoff may
*                     cause a change in convexity or
*                     monotonicity of the resulting spline
*                     and a change in the case number
*                     provided by 'QuadChoose'.  If epsilon
*                     is not equal to zero, then epsilon
*                     should be greater than or equal to
*                     machine epsilon.
*
*      ON OUTPUT--
*        yval         contains the images of the points in
*                     xval.
*        err          one of the following error codes:
*                       0 - QuadEval ran normally.
*                       1 - xval(i) is less than xtab(1)
*                           for at least one i or xval(i)
*                           is greater than xtab(num) for
*                           at least one i. QuadEval will
*                           extrapolate to provide function
*                           values for these abscissas.
*                       2 - xval(i+1) < xval(i) for some i.
*
*      QuadEval calls the following subroutines or functions:
*
*        SearchSpline
*        QuadCases
*        QuadChoose
*        QuadSpline
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
static int
QuadEval(
    RbcPoint2D origPts[],
    int nOrigPts,
    RbcPoint2D intpPts[],
    int nIntpPts,
    double *m,                 /* Slope of the spline at each point
                               * of interpolation. */
    double epsilon)
{              /* Relative error tolerance (see choose) */
    int error;
    register int i, j;
    double param[10];
    int ncase;
    int start, end;
    int l, p;
    register int n;
    int found;

    /* Initialize indices and set error result */
    error = 0;
    l = nOrigPts - 1;
    p = l - 1;
    ncase = 1;

    /*
    * Determine if abscissas of new vector are non-decreasing.
    */
    for (j = 1; j < nIntpPts; j++) {
        if (intpPts[j].x < intpPts[j - 1].x) {
            return 2;
        }
    }
    /*
    * Determine if any of the points in xval are LESS than the
    * abscissa of the first data point.
    */
    for (start = 0; start < nIntpPts; start++) {
        if (intpPts[start].x >= origPts[0].x) {
            break;
        }
    }
    /*
    * Determine if any of the points in xval are GREATER than the
    * abscissa of the l data point.
    */
    for (end = nIntpPts - 1; end >= 0; end--) {
        if (intpPts[end].x <= origPts[l].x) {
            break;
        }
    }

    if (start > 0) {
        error = 1;      /* Set error value to indicate that
                        * extrapolation has occurred. */
                        /*
                        * Calculate the images of points of evaluation whose abscissas
                        * are less than the abscissa of the first data point.
                        */
        ncase = QuadSelect(origPts, origPts + 1, m[0], m[1], epsilon, param);
        for (j = 0; j < (start - 1); j++) {
            QuadSpline(intpPts + j, origPts, origPts + 1, param, ncase);
        }
        if (nIntpPts == 1) {
            return error;
        }
    }
    if ((nIntpPts == 1) && (end != (nIntpPts - 1))) {
        goto noExtrapolation;
    }

    /*
    * Search locates the interval in which the first in-range
    * point of evaluation lies.
    */

    i = SearchSpline(origPts, nOrigPts, intpPts[start].x, &found);

    n = i + 1;
    if (n >= nOrigPts) {
        n = nOrigPts - 1;
        i = nOrigPts - 2;
    }
    /*
    * If the first in-range point of evaluation is equal to one
    * of the data points, assign the appropriate value from y.
    * Continue until a point of evaluation is found which is not
    * equal to a data point.
    */
    if (found) {
        do {
            intpPts[start].y = origPts[i].y;
            start++;
            if (start >= nIntpPts) {
                return error;
            }
        } while (intpPts[start - 1].x == intpPts[start].x);

        for (;;) {
            if (intpPts[start].x < origPts[n].x) {
                break;  /* Break out of for-loop */
            }
            if (intpPts[start].x == origPts[n].x) {
                do {
                    intpPts[start].y = origPts[n].y;
                    start++;
                    if (start >= nIntpPts) {
                        return error;
                    }
                } while (intpPts[start].x == intpPts[start - 1].x);
            }
            i++;
            n++;
        }
    }
    /*
    * Calculate the images of all the points which lie within
    * range of the data.
    */
    if ((i > 0) || (error != 1)) {
        ncase = QuadSelect(origPts + i, origPts + n, m[i], m[n],
            epsilon, param);
    }
    for (j = start; j <= end; j++) {
        /*
        * If xx(j) - x(n) is negative, do not recalculate
        * the parameters for this section of the spline since
        * they are already known.
        */
        if (intpPts[j].x == origPts[n].x) {
            intpPts[j].y = origPts[n].y;
            continue;
        }
        else if (intpPts[j].x > origPts[n].x) {
            double delta;

            /* Determine that the routine is in the correct part of
            * the spline. */
            do {
                i++, n++;
                delta = intpPts[j].x - origPts[n].x;
            } while (delta > 0.0);

            if (delta < 0.0) {
                ncase = QuadSelect(origPts + i, origPts + n, m[i],
                    m[n], epsilon, param);
            }
            else if (delta == 0.0) {
                intpPts[j].y = origPts[n].y;
                continue;
            }
        }
        QuadSpline(intpPts + j, origPts + i, origPts + n, param, ncase);
    }

    if (end == (nIntpPts - 1)) {
        return error;
    }
    if ((n == l) && (intpPts[end].x != origPts[l].x)) {
        goto noExtrapolation;
    }

    error = 1; /* Set error value to indicate that
               * extrapolation has occurred. */
    ncase = QuadSelect(origPts + p, origPts + l, m[p], m[l], epsilon, param);

noExtrapolation:
    /*
    * Calculate the images of the points of evaluation whose
    * abscissas are greater than the abscissa of the last data point.
    */
    for (j = (end + 1); j < nIntpPts; j++) {
        QuadSpline(intpPts + j, origPts + p, origPts + l, param, ncase);
    }
    return error;
}

/*
* -----------------------------------------------------------------------
*
*          Shape preserving quadratic splines
*           by D.F.Mcallister & J.A.Roulier
*            Coded by S.L.Dodd & M.Roulier
*             N.C.State University
*
* -----------------------------------------------------------------------
*/

/*
*--------------------------------------------------------------
*
* MakeQuadraticSpline --
*
*      Driver routine for quadratic spline package
*
*      On input--
*
*        X,Y    Contain n-long arrays of data (x is
*               increasing)
*        XM     Contains m-long array of x values
*               (increasing)
*        eps    Relative error tolerance
*        n      Number of input data points
*        m      Number of output data points
*
*      On output--
*
*        work   Contains the value of the first derivative
*               at each data point
*        ym     Contains the interpolated spline value at
*               each data point
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
static int
MakeQuadraticSpline(
    RbcPoint2D origPts[],
    int nOrigPts,
    RbcPoint2D intpPts[],
    int nIntpPts)
{
    double epsilon;
    double *work;
    int result;

    work = (double *)ckalloc(nOrigPts * sizeof(double));
    assert(work);

    epsilon = 0.0;      /* TBA: adjust error via command-line option */
                        /* allocate space for vectors used in calculation */
    QuadSlopes(origPts, work, nOrigPts);
    result = QuadEval(origPts, nOrigPts, intpPts, nIntpPts, work, epsilon);
    ckfree((char *)work);
    if (result > 1) {
        return FALSE;
    }
    return TRUE;
}

/*
*--------------------------------------------------------------
*
* MakeNaturalSpline --
*
*      Reference:
*
*        Numerical Analysis, R. Burden, J. Faires
*          and A. Reynolds.
*        Prindle, Weber & Schmidt 1981 pp 112
*
*      Parameters:
*
*        origPts - vector of points, assumed to be
*                  sorted along x.
*        intpPts - vector of new points.
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
static int
MakeNaturalSpline(
    RbcPoint2D origPts[],
    int nOrigPts,
    RbcPoint2D intpPts[],
    int nIntpPts)
{
    Cubic2D *eq;
    RbcPoint2D *iPtr, *endPtr;
    TriDiagonalMatrix *A;
    double *dx;                /* vector of deltas in x */
    double x, dy, alpha;
    int isKnot;
    register int i, j, n;

    dx = (double *)ckalloc(sizeof(double) * nOrigPts);
    /* Calculate vector of differences */
    for (i = 0, j = 1; j < nOrigPts; i++, j++) {
        dx[i] = origPts[j].x - origPts[i].x;
        if (dx[i] < 0.0) {
            return 0;
        }
    }
    n = nOrigPts - 1;   /* Number of intervals. */
    A = (TriDiagonalMatrix *)ckalloc(sizeof(TriDiagonalMatrix) * nOrigPts);
    if (A == NULL) {
        ckfree((char *)dx);
        return 0;
    }
    /* Vectors to solve the tridiagonal matrix */
    A[0][0] = A[n][0] = 1.0;
    A[0][1] = A[n][1] = 0.0;
    A[0][2] = A[n][2] = 0.0;

    /* Calculate the intermediate results */
    for (i = 0, j = 1; j < n; j++, i++) {
        alpha = 3.0 * ((origPts[j + 1].y / dx[j]) - (origPts[j].y / dx[i]) -
            (origPts[j].y / dx[j]) + (origPts[i].y / dx[i]));
        A[j][0] = 2 * (dx[j] + dx[i]) - dx[i] * A[i][1];
        A[j][1] = dx[j] / A[j][0];
        A[j][2] = (alpha - dx[i] * A[i][2]) / A[j][0];
    }
    eq = (Cubic2D *)ckalloc(sizeof(Cubic2D) * nOrigPts);

    if (eq == NULL) {
        ckfree((char *)A);
        ckfree((char *)dx);
        return FALSE;
    }
    eq[0].c = eq[n].c = 0.0;
    for (j = n, i = n - 1; i >= 0; i--, j--) {
        eq[i].c = A[i][2] - A[i][1] * eq[j].c;
        dy = origPts[i + 1].y - origPts[i].y;
        eq[i].b = (dy) / dx[i] - dx[i] * (eq[j].c + 2.0 * eq[i].c) / 3.0;
        eq[i].d = (eq[j].c - eq[i].c) / (3.0 * dx[i]);
    }
    ckfree((char *)A);
    ckfree((char *)dx);

    endPtr = intpPts + nIntpPts;
    /* Now calculate the new values */
    for (iPtr = intpPts; iPtr < endPtr; iPtr++) {
        iPtr->y = 0.0;
        x = iPtr->x;

        /* Is it outside the interval? */
        if ((x < origPts[0].x) || (x > origPts[n].x)) {
            continue;
        }
        /* Search for the interval containing x in the point array */
        i = SearchSpline(origPts, nOrigPts, x, &isKnot);
        if (isKnot) {
            iPtr->y = origPts[i].y;
        }
        else {
            i--;
            x -= origPts[i].x;
            iPtr->y = origPts[i].y +
                x * (eq[i].b + x * (eq[i].c + x * eq[i].d));
        }
    }
    ckfree((char *)eq);
    return TRUE;
}

static RbcOpSpec splineOps[] = {
    { "natural", 1, (RbcOp)MakeNaturalSpline, 6, 6, "x y splx sply", },
    { "quadratic", 1, (RbcOp)MakeQuadraticSpline, 6, 6, "x y splx sply", },
};

static int nSplineOps = sizeof(splineOps) / sizeof(RbcOpSpec);

/*
*--------------------------------------------------------------
*
* SplineCmd --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
static int
SplineCmd(
    ClientData clientData,     /* Not used. */
    Tcl_Interp * interp,
    Tcl_Size objc,
    Tcl_Obj * const objv[])
{
    RbcOp proc;
    Rbc_Vector *x, *y, *splX, *splY;
    double *xArr, *yArr;
    register int i;
    RbcPoint2D *origPts, *intpPts;
    int nOrigPts, nIntpPts;

    proc = RbcGetOp(interp, nSplineOps, splineOps, RBC_OP_ARG1, objc, objv, 0);
    if (proc == NULL) {
        return TCL_ERROR;
    }
    if ((Rbc_GetVector(interp, Tcl_GetString(objv[2]), &x) != TCL_OK) ||
        (Rbc_GetVector(interp, Tcl_GetString(objv[3]), &y) != TCL_OK) ||
        (Rbc_GetVector(interp, Tcl_GetString(objv[4]), &splX) != TCL_OK)) {
        return TCL_ERROR;
    }
    nOrigPts = x->numValues;
    if (nOrigPts < 3) {
        Tcl_AppendResult(interp, "length of vector \"", Tcl_GetString(objv[2]), "\" is < 3",
            (char *)NULL);
        return TCL_ERROR;
    }
    for (i = 1; i < nOrigPts; i++) {
        if (x->valueArr[i] < x->valueArr[i - 1]) {
            Tcl_AppendResult(interp, "x vector \"", Tcl_GetString(objv[2]),
                "\" must be monotonically increasing", (char *)NULL);
            return TCL_ERROR;
        }
    }
    /* Check that all the data points aren't the same. */
    if (x->valueArr[i - 1] <= x->valueArr[0]) {
        Tcl_AppendResult(interp, "x vector \"", Tcl_GetString(objv[2]),
            "\" must be monotonically increasing", (char *)NULL);
        return TCL_ERROR;
    }
    if (nOrigPts != y->numValues) {
        Tcl_AppendResult(interp, "vectors \"", Tcl_GetString(objv[2]), "\" and \"", Tcl_GetString(objv[3]),
            " have different lengths", (char *)NULL);
        return TCL_ERROR;
    }
    nIntpPts = splX->numValues;
    if (Rbc_GetVector(interp, Tcl_GetString(objv[5]), &splY) != TCL_OK) {
        /*
        * If the named vector to hold the ordinates of the spline
        * doesn't exist, create one the same size as the vector
        * containing the abscissas.
        */
        if (Rbc_CreateVector(interp, Tcl_GetString(objv[5]), nIntpPts, &splY) != TCL_OK) {
            return TCL_ERROR;
        }
    }
    else if (nIntpPts != splY->numValues) {
        /*
        * The x and y vectors differ in size. Make the number of ordinates
        * the same as the number of abscissas.
        */
        if (Rbc_ResizeVector(splY, nIntpPts) != TCL_OK) {
            return TCL_ERROR;
        }
    }
    origPts = (RbcPoint2D *)ckalloc(sizeof(RbcPoint2D) * nOrigPts);
    if (origPts == NULL) {
        Tcl_AppendPrintfToObj(Tcl_GetObjResult(interp),
            "can't allocate \"%d\" points", nOrigPts);
        return TCL_ERROR;
    }
    intpPts = (RbcPoint2D *)ckalloc(sizeof(RbcPoint2D) * nIntpPts);
    if (intpPts == NULL) {
        Tcl_AppendPrintfToObj(Tcl_GetObjResult(interp),
            "can't allocate \"%d\" points", nIntpPts);
        ckfree((char *)origPts);
        return TCL_ERROR;
    }
    xArr = x->valueArr;
    yArr = y->valueArr;
    for (i = 0; i < nOrigPts; i++) {
        origPts[i].x = xArr[i];
        origPts[i].y = yArr[i];
    }
    xArr = splX->valueArr;
    yArr = splY->valueArr;
    for (i = 0; i < nIntpPts; i++) {
        intpPts[i].x = xArr[i];
        intpPts[i].y = yArr[i];
    }
    if (!(*proc) (origPts, nOrigPts, intpPts, nIntpPts)) {
        Tcl_AppendResult(interp, "error generating spline for \"",
            RbcNameOfVector(splY), "\"", (char *)NULL);
        ckfree((char *)origPts);
        ckfree((char *)intpPts);
        return TCL_ERROR;
    }
    yArr = splY->valueArr;
    for (i = 0; i < nIntpPts; i++) {
        yArr[i] = intpPts[i].y;
    }
    ckfree((char *)origPts);
    ckfree((char *)intpPts);

    /* Finally update the vector. The size of the vector hasn't
    * changed, just the data. Reset the vector using TCL_STATIC to
    * indicate this. */
    if (Rbc_ResetVector(splY, splY->valueArr, splY->numValues,
        splY->arraySize, TCL_STATIC) != TCL_OK) {
        return TCL_ERROR;
    }
    return TCL_OK;
}

/*
*--------------------------------------------------------------
*
* RbcSplineInit --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
int
RbcSplineInit(
    Tcl_Interp * interp)
{
    Tcl_CreateObjCommand2(interp, "spline", SplineCmd, NULL,
        NULL);
    return TCL_OK;
}

/*
*--------------------------------------------------------------
*
* SolveCubic1 --
*
*      The Cholesky decomposition A = C^T * D * C (C is
*      upper triangle with unit diagonal, D is diagonal)
*      is calculated.
*
*      If x is assumed cyclic ( x[i]=x[n+i] ) the
*      equations can be written as (i=0,1,...,n-1):
*
*        m[i][0] * x[i-1] + m[i][1] * x[i] + m[i][2] *
*        x[i+1] = b[i] .
*
*      In matrix notation one gets A * x = b, where the
*      matrix A is tridiagonal with additional elements
*      in the upper right and lower left position:
*
*        A[i][0] = A_{i,i-1}  for i=1,2,...,n-1
*          and    m[0][0] = A_{0,n-1} ,
*
*        A[i][1] = A_{i, i }  for i=0,1,...,n-1
*
*        A[i][2] = A_{i,i+1}  for i=0,1,...,n-2
*          and    m[n-1][2] = A_{n-1,0}.
*
*      A should be symmetric (A[i+1][0] == A[i][2]) and positive definite.
*      The size of the system is given in n (n>=1).
*
* Results:
*      Return TRUE if decomposition exist.
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
static int
SolveCubic1(
    TriDiagonalMatrix A[],
    int n)
{
    int i;
    double m_ij, m_n, m_nn, d;

    if (n < 1) {
        return FALSE;   /* Dimension should be at least 1 */
    }
    d = A[0][1];        /* D_{0,0} = A_{0,0} */
    if (d <= 0.0) {
        return FALSE;   /* A (or D) should be positive definite */
    }
    m_n = A[0][0];      /*  A_{0,n-1}  */
    m_nn = A[n - 1][1]; /* A_{n-1,n-1} */
    for (i = 0; i < n - 2; i++) {
        m_ij = A[i][2]; /*  A_{i,1}  */
        A[i][2] = m_ij / d;     /* C_{i,i+1} */
        A[i][0] = m_n / d;      /* C_{i,n-1} */
        m_nn -= A[i][0] * m_n;  /* to get C_{n-1,n-1} */
        m_n = -A[i][2] * m_n;   /* to get C_{i+1,n-1} */
        d = A[i + 1][1] - A[i][2] * m_ij;       /* D_{i+1,i+1} */
        if (d <= 0.0) {
            return FALSE;       /* Elements of D should be positive */
        }
        A[i + 1][1] = d;
    }
    if (n >= 2) {        /* Complete last column */
        m_n += A[n - 2][2];     /* add A_{n-2,n-1} */
        A[n - 2][0] = m_n / d;  /* C_{n-2,n-1} */
        A[n - 1][1] = d = m_nn - A[n - 2][0] * m_n;     /* D_{n-1,n-1} */
        if (d <= 0.0) {
            return FALSE;
        }
    }
    return TRUE;
}

/*
*--------------------------------------------------------------
*
* SolveCubic2 --
*
*      The second procedure solves the linear system, with
*      the Cholesky decomposition calculated above (in m[][])
*      and the right side b given in x[]. The solution x
*      overwrites the right side in x[].
*
*      If x is assumed cyclic ( x[i]=x[n+i] ) the
*      equations can be written as (i=0,1,...,n-1):
*
*        m[i][0] * x[i-1] + m[i][1] * x[i] + m[i][2] *
*        x[i+1] = b[i] .
*
*      In matrix notation one gets A * x = b, where the
*      matrix A is tridiagonal with additional elements
*      in the upper right and lower left position:
*
*        A[i][0] = A_{i,i-1}  for i=1,2,...,n-1
*          and    m[0][0] = A_{0,n-1} ,
*
*        A[i][1] = A_{i, i }  for i=0,1,...,n-1
*
*        A[i][2] = A_{i,i+1}  for i=0,1,...,n-2
*          and    m[n-1][2] = A_{n-1,0}.
*
*      A should be symmetric (A[i+1][0] == A[i][2]) and positive definite.
*      The size of the system is given in n (n>=1).
*
* Results:
*      None.
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
static void
SolveCubic2(
    TriDiagonalMatrix A[],
    CubicSpline spline[],
    int nIntervals)
{
    int i;
    double x, y;
    int n, m;

    n = nIntervals - 2;
    m = nIntervals - 1;

    /* Division by transpose of C : b = C^{-T} * b */
    x = spline[m].x;
    y = spline[m].y;
    for (i = 0; i < n; i++) {
        spline[i + 1].x -= A[i][2] * spline[i].x;       /* C_{i,i+1} * x(i) */
        spline[i + 1].y -= A[i][2] * spline[i].y;       /* C_{i,i+1} * x(i) */
        x -= A[i][0] * spline[i].x;     /* C_{i,n-1} * x(i) */
        y -= A[i][0] * spline[i].y;     /* C_{i,n-1} * x(i) */
    }
    if (n >= 0) {
        /* C_{n-2,n-1} * x_{n-1} */
        spline[m].x = x - A[n][0] * spline[n].x;
        spline[m].y = y - A[n][0] * spline[n].y;
    }
    /* Division by D: b = D^{-1} * b */
    for (i = 0; i < nIntervals; i++) {
        spline[i].x /= A[i][1];
        spline[i].y /= A[i][1];
    }

    /* Division by C: b = C^{-1} * b */
    x = spline[m].x;
    y = spline[m].y;
    if (n >= 0) {
        /* C_{n-2,n-1} * x_{n-1} */
        spline[n].x -= A[n][0] * x;
        spline[n].y -= A[n][0] * y;
    }
    for (i = (n - 1); i >= 0; i--) {
        /* C_{i,i+1} * x_{i+1} + C_{i,n-1} * x_{n-1} */
        spline[i].x -= A[i][2] * spline[i + 1].x + A[i][0] * x;
        spline[i].y -= A[i][2] * spline[i + 1].y + A[i][0] * y;
    }
}

/*
*--------------------------------------------------------------
*
* CubicSlopes --
*
*      Find second derivatives (x''(t_i),y''(t_i)) of cubic
*      spline interpolation through list of points (x_i,y_i).
*      The parameter t is calculated as the length of the
*      linear stroke. The number of points must be at least 3.
*
*      Note: For CLOSED_CONTOURs the first and last point
*      must be equal.
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
static CubicSpline *
CubicSlopes(
    RbcPoint2D points[],
    int nPoints,               /* Number of points (nPoints>=3) */
    int isClosed,              /* CLOSED_CONTOUR or OPEN_CONTOUR  */
    double unitX,              /* Unit length in x and y (norm=1) */
    double unitY)
{              /* Unit length in x and y (norm=1) */
    CubicSpline *spline;
    register CubicSpline *s1, *s2;
    int n, i;
    double norm, dx, dy;
    TriDiagonalMatrix *A;      /* The tri-diagonal matrix is saved here. */

    spline = (CubicSpline *)ckalloc(sizeof(CubicSpline) * nPoints);
    if (spline == NULL) {
        return NULL;
    }
    A = (TriDiagonalMatrix *)ckalloc(sizeof(TriDiagonalMatrix) * nPoints);
    if (A == NULL) {
        ckfree((char *)spline);
        return NULL;
    }
    /*
    * Calculate first differences in (dxdt2[i], y[i]) and interval lengths
    * in dist[i]:
    */
    s1 = spline;
    for (i = 0; i < nPoints - 1; i++) {
        s1->x = points[i + 1].x - points[i].x;
        s1->y = points[i + 1].y - points[i].y;

        /*
        * The Norm of a linear stroke is calculated in "normal coordinates"
        * and used as interval length:
        */
        dx = s1->x / unitX;
        dy = s1->y / unitY;
        s1->t = sqrt(dx * dx + dy * dy);

        s1->x /= s1->t; /* first difference, with unit norm: */
        s1->y /= s1->t; /*   || (dxdt2[i], y[i]) || = 1      */
        s1++;
    }

    /*
    * Setup linear System:  Ax = b
    */
    n = nPoints - 2;    /* Without first and last point */
    if (isClosed) {
        /* First and last points must be equal for CLOSED_CONTOURs */
        spline[nPoints - 1].t = spline[0].t;
        spline[nPoints - 1].x = spline[0].x;
        spline[nPoints - 1].y = spline[0].y;
        n++;   /* Add last point (= first point) */
    }
    s1 = spline, s2 = s1 + 1;
    for (i = 0; i < n; i++) {
        /* Matrix A, mainly tridiagonal with cyclic second index
        * ("j = j+n mod n")
        */
        A[i][0] = s1->t;        /* Off-diagonal element A_{i,i-1} */
        A[i][1] = 2.0 * (s1->t + s2->t);        /* A_{i,i} */
        A[i][2] = s2->t;        /* Off-diagonal element A_{i,i+1} */

                                /* Right side b_x and b_y */
        s1->x = (s2->x - s1->x) * 6.0;
        s1->y = (s2->y - s1->y) * 6.0;

        /*
        * If the linear stroke shows a cusp of more than 90 degree,
        * the right side is reduced to avoid oscillations in the
        * spline:
        */
        /*
        * The Norm of a linear stroke is calculated in "normal coordinates"
        * and used as interval length:
        */
        dx = s1->x / unitX;
        dy = s1->y / unitY;
        norm = sqrt(dx * dx + dy * dy) / 8.5;
        if (norm > 1.0) {
            /* The first derivative will not be continuous */
            s1->x /= norm;
            s1->y /= norm;
        }
        s1++, s2++;
    }

    if (!isClosed) {
        /* Third derivative is set to zero at both ends */
        A[0][1] += A[0][0];     /* A_{0,0}     */
        A[0][0] = 0.0;  /* A_{0,n-1}   */
        A[n - 1][1] += A[n - 1][2];     /* A_{n-1,n-1} */
        A[n - 1][2] = 0.0;      /* A_{n-1,0}   */
    }
    /* Solve linear systems for dxdt2[] and y[] */

    if (SolveCubic1(A, n)) {     /* Cholesky decomposition */
        SolveCubic2(A, spline, n);      /* A * dxdt2 = b_x */
    }
    else {   /* Should not happen, but who knows ... */
        ckfree((char *)A);
        ckfree((char *)spline);
        return NULL;
    }
    /* Shift all second derivatives one place right and update the ends. */
    s2 = spline + n, s1 = s2 - 1;
    for ( /* empty */; s2 > spline; s2--, s1--) {
        s2->x = s1->x;
        s2->y = s1->y;
    }
    if (isClosed) {
        spline[0].x = spline[n].x;
        spline[0].y = spline[n].y;
    }
    else {
        /* Third derivative is 0.0 for the first and last interval. */
        spline[0].x = spline[1].x;
        spline[0].y = spline[1].y;
        spline[n + 1].x = spline[n].x;
        spline[n + 1].y = spline[n].y;
    }
    ckfree((char *)A);
    return spline;
}

/*
*--------------------------------------------------------------
*
* CubicEval --
*
*      Calculate interpolated values of the spline function
*      (defined via p_cntr and the second derivatives
*      dxdt2[] and dydt2[]). The number of tabulated values
*      is n. On an equidistant grid n_intpol values are
*      calculated.
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
static int
CubicEval(
    RbcPoint2D origPts[],
    int nOrigPts,
    RbcPoint2D intpPts[],
    int nIntpPts,
    CubicSpline spline[])
{
    double t, tSkip, tMax;
    RbcPoint2D p, q;
    double d, hx, dx0, dx01, hy, dy0, dy01;
    register int i, j, count;

    /* Sum the lengths of all the segments (intervals). */
    tMax = 0.0;
    for (i = 0; i < nOrigPts - 1; i++) {
        tMax += spline[i].t;
    }

    /* Need a better way of doing this... */

    /* The distance between interpolated points */
    tSkip = (1. - 1e-7) * tMax / (nIntpPts - 1);

    t = 0.0;   /* Spline parameter value. */
    q = origPts[0];
    count = 0;

    intpPts[count++] = q;       /* First point. */
    t += tSkip;

    for (i = 0, j = 1; j < nOrigPts; i++, j++) {
        d = spline[i].t;        /* Interval length */
        p = q;
        q = origPts[i + 1];
        hx = (q.x - p.x) / d;
        hy = (q.y - p.y) / d;
        dx0 = (spline[j].x + 2 * spline[i].x) / 6.0;
        dy0 = (spline[j].y + 2 * spline[i].y) / 6.0;
        dx01 = (spline[j].x - spline[i].x) / (6.0 * d);
        dy01 = (spline[j].y - spline[i].y) / (6.0 * d);
        while (t <= spline[i].t) {       /* t in current interval ? */
            p.x += t * (hx + (t - d) * (dx0 + t * dx01));
            p.y += t * (hy + (t - d) * (dy0 + t * dy01));
            intpPts[count++] = p;
            t += tSkip;
        }
        /* Parameter t relative to start of next interval */
        t -= spline[i].t;
    }
    return count;
}

/*
*--------------------------------------------------------------
*
* MakeNaturalParametricSpline --
*
*      Generate a cubic spline curve through the points
*      (x_i,y_i) which are stored in the linked list
*      p_cntr.
*
*      The spline is defined as a 2d-function
*      s(t) = (x(t),y(t)), where the parameter t is the
*      length of the linear stroke.
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
static int
MakeNaturalParametricSpline(
    RbcPoint2D origPts[],
    int nOrigPts,
    RbcExtents2D * extsPtr,
    int isClosed,
    RbcPoint2D * intpPts,
    int nIntpPts)
{
    double unitX, unitY;       /* To define norm (x,y)-plane */
    CubicSpline *spline;
    int result;

    if (nOrigPts < 3) {
        return 0;
    }
    if (isClosed) {
        origPts[nOrigPts].x = origPts[0].x;
        origPts[nOrigPts].y = origPts[0].y;
        nOrigPts++;
    }
    /* Width and height of the grid is used at unit length (2d-norm) */
    unitX = extsPtr->right - extsPtr->left;
    unitY = extsPtr->bottom - extsPtr->top;

    if (unitX < FLT_EPSILON) {
        unitX = FLT_EPSILON;
    }
    if (unitY < FLT_EPSILON) {
        unitY = FLT_EPSILON;
    }
    /* Calculate parameters for cubic spline:
    *          t     = arc length of interval.
    *          dxdt2 = second derivatives of x with respect to t,
    *          dydt2 = second derivatives of y with respect to t,
    */
    spline = CubicSlopes(origPts, nOrigPts, isClosed, unitX, unitY);
    if (spline == NULL) {
        return 0;
    }
    result = CubicEval(origPts, nOrigPts, intpPts, nIntpPts, spline);
    ckfree((char *)spline);
    return result;
}

/*
*--------------------------------------------------------------
*
* CatromCoeffs --
*
*      TODO: Description
*
* Results:
*      TODO: Results
*
* Side effects:
*      TODO: Side Effects
*
*--------------------------------------------------------------
*/
static void
CatromCoeffs(
    RbcPoint2D * p,
    RbcPoint2D * a,
    RbcPoint2D * b,
    RbcPoint2D * c,
    RbcPoint2D * d)
{
    a->x = -p[0].x + 3.0 * p[1].x - 3.0 * p[2].x + p[3].x;
    b->x = 2.0 * p[0].x - 5.0 * p[1].x + 4.0 * p[2].x - p[3].x;
    c->x = -p[0].x + p[2].x;
    d->x = 2.0 * p[1].x;
    a->y = -p[0].y + 3.0 * p[1].y - 3.0 * p[2].y + p[3].y;
    b->y = 2.0 * p[0].y - 5.0 * p[1].y + 4.0 * p[2].y - p[3].y;
    c->y = -p[0].y + p[2].y;
    d->y = 2.0 * p[1].y;
}

/*
*----------------------------------------------------------------------
*
* MakeCatromParametricSpline --
*
*      Computes a spline based upon the data points, returning a new
*      (larger) coordinate array or points.
*
* Results:
*      None.
*
* Side effects:
*      TODO: Side Effects
*
*----------------------------------------------------------------------
*/
static int
MakeCatromParametricSpline(
    RbcPoint2D * points,
    int nPoints,
    RbcPoint2D * intpPts,
    int nIntpPts)
{
    register int i;
    RbcPoint2D *origPts;
    double t;
    int interval;
    RbcPoint2D a, b, c, d;

    assert(nPoints > 0);
    /*
    * 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.
    */
    origPts = (RbcPoint2D *)ckalloc((nPoints + 4) * sizeof(RbcPoint2D));
    memcpy(origPts + 1, points, sizeof(RbcPoint2D) * nPoints);

    origPts[0] = origPts[1];
    origPts[nPoints + 2] = origPts[nPoints + 1] = origPts[nPoints];

    for (i = 0; i < nIntpPts; i++) {
        interval = (int)intpPts[i].x;
        t = intpPts[i].y;
        assert(interval < nPoints);
        CatromCoeffs(origPts + interval, &a, &b, &c, &d);
        intpPts[i].x = (d.x + t * (c.x + t * (b.x + t * a.x))) / 2.0;
        intpPts[i].y = (d.y + t * (c.y + t * (b.y + t * a.y))) / 2.0;
    }
    ckfree((char *)origPts);
    return 1;
}

/* vim: set ts=4 sw=4 sts=4 ff=unix et : */
