/*
 * Smithsonian Astrophysical Observatory, Cambridge, MA, USA
 * This code has been modified under the terms listed below and is made
 * available under the same terms.
 */

/*
 *	Copyright (c) 1993 George A Howlett.
 *
 *	Permission is hereby granted, free of charge, to any person obtaining
 *	a copy of this software and associated documentation files (the
 *	"Software"), to deal in the Software without restriction, including
 *	without limitation the rights to use, copy, modify, merge, publish,
 *	distribute, sublicense, and/or sell copies of the Software, and to
 *	permit persons to whom the Software is furnished to do so, subject to
 *	the following conditions:
 *
 *	The above copyright notice and this permission notice shall be
 *	included in all copies or substantial portions of the Software.
 *
 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 *	MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 *	LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 *	OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 *	WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include <float.h>
#include <stdlib.h>
#include <string.h>

#include <cmath>

#include "tkbltGraph.h"
#include "tkbltGrElemLine.h"
#include "tkbltGrElemOption.h"
#include "tkbltGrAxis.h"
#include "tkbltGrMisc.h"
#include "tkbltGrDef.h"
#include "tkbltConfig.h"
#include "tkbltGrPSOutput.h"
#include "tkbltInt.h"

using namespace Blt;

#define SEARCH_X	0
#define SEARCH_Y	1
#define SEARCH_BOTH	2

#define SEARCH_POINTS	0	// closest data point.
#define SEARCH_TRACES	1	// closest point on trace.
#define SEARCH_AUTO	2	// traces if linewidth is > 0 and more than one

#define MIN3(a,b,c)	(((a)<(b))?(((a)<(c))?(a):(c)):(((b)<(c))?(b):(c)))
#define PointInRegion(e,x,y) (((x) <= (e)->right) && ((x) >= (e)->left) && ((y) <= (e)->bottom) && ((y) >= (e)->top))

#define BROKEN_TRACE(dir,last,next) (((dir == INCREASING)&&(next < last)) || ((dir == DECREASING)&&(next > last)))
#define DRAW_SYMBOL() (symbolInterval_==0||(symbolCounter_%symbolInterval_)==0)

static const char* symbolMacros[] =
  {"Li", "Sq", "Ci", "Di", "Pl", "Cr", "Sp", "Sc", "Tr", "Ar", "Bm", NULL};

// OptionSpecs

static const char* smoothObjOption[] = 
  {"linear", "step", "cubic", "quadratic", "catrom", NULL};

static const char* penDirObjOption[] = 
  {"increasing", "decreasing", "both", NULL};

static Tk_ObjCustomOption styleObjOption =
  {
    "styles", StyleSetProc, StyleGetProc, StyleRestoreProc, StyleFreeProc, 
    (ClientData)sizeof(LineStyle)
  };

extern Tk_ObjCustomOption penObjOption;
extern Tk_ObjCustomOption pairsObjOption;
extern Tk_ObjCustomOption valuesObjOption;
extern Tk_ObjCustomOption xAxisObjOption;
extern Tk_ObjCustomOption yAxisObjOption;

static Tk_OptionSpec optionSpecs[] = {
  {TK_OPTION_CUSTOM, "-activepen", "activePen", "ActivePen",
   "active", -1, Tk_Offset(LineElementOptions, activePenPtr), 
   TK_OPTION_NULL_OK, &penObjOption, LAYOUT},
  {TK_OPTION_BORDER, "-areabackground", "areaBackground", "AreaBackground",
   NULL, -1, Tk_Offset(LineElementOptions, fillBg), 
   TK_OPTION_NULL_OK, NULL, LAYOUT},
  {TK_OPTION_CUSTOM, "-bindtags", "bindTags", "BindTags", 
   "all", -1, Tk_Offset(LineElementOptions, tags), 
   TK_OPTION_NULL_OK, &listObjOption, 0},
  {TK_OPTION_COLOR, "-color", "color", "Color", 
   STD_NORMAL_FOREGROUND, -1, 
   Tk_Offset(LineElementOptions, builtinPen.traceColor), 0, NULL, CACHE},
  {TK_OPTION_CUSTOM, "-dashes", "dashes", "Dashes", 
   NULL, -1, Tk_Offset(LineElementOptions, builtinPen.traceDashes), 
   TK_OPTION_NULL_OK, &dashesObjOption, CACHE},
  {TK_OPTION_CUSTOM, "-data", "data", "Data", 
   NULL, -1, Tk_Offset(LineElementOptions, coords),
   TK_OPTION_NULL_OK, &pairsObjOption, RESET},
  {TK_OPTION_COLOR, "-errorbarcolor", "errorBarColor", "ErrorBarColor",
   NULL, -1, Tk_Offset(LineElementOptions, builtinPen.errorBarColor), 
   TK_OPTION_NULL_OK, NULL, CACHE},
  {TK_OPTION_PIXELS,"-errorbarwidth", "errorBarWidth", "ErrorBarWidth",
   "1", -1, Tk_Offset(LineElementOptions, builtinPen.errorBarLineWidth),
   0, NULL, CACHE},
  {TK_OPTION_PIXELS, "-errorbarcap", "errorBarCap", "ErrorBarCap", 
   "0", -1, Tk_Offset(LineElementOptions, builtinPen.errorBarCapWidth),
   0, NULL, LAYOUT},
  {TK_OPTION_COLOR, "-fill", "fill", "Fill", 
   NULL, -1, Tk_Offset(LineElementOptions, builtinPen.symbol.fillColor), 
   TK_OPTION_NULL_OK, NULL, CACHE},
  {TK_OPTION_BOOLEAN, "-hide", "hide", "Hide", 
   "no", -1, Tk_Offset(LineElementOptions, hide), 0, NULL, LAYOUT},
  {TK_OPTION_STRING, "-label", "label", "Label", 
   NULL, -1, Tk_Offset(LineElementOptions, label), 
   TK_OPTION_NULL_OK | TK_OPTION_DONT_SET_DEFAULT, NULL, LAYOUT},
  {TK_OPTION_RELIEF, "-legendrelief", "legendRelief", "LegendRelief",
   "flat", -1, Tk_Offset(LineElementOptions, legendRelief), 0, NULL, LAYOUT},
  {TK_OPTION_PIXELS, "-linewidth", "lineWidth", "LineWidth",
   "1", -1, Tk_Offset(LineElementOptions, builtinPen.traceWidth),
   0, NULL, CACHE},
  {TK_OPTION_CUSTOM, "-mapx", "mapX", "MapX",
   "x", -1, Tk_Offset(LineElementOptions, xAxis), 0, &xAxisObjOption, RESET},
  {TK_OPTION_CUSTOM, "-mapy", "mapY", "MapY",
   "y", -1, Tk_Offset(LineElementOptions, yAxis), 0, &yAxisObjOption, RESET},
  {TK_OPTION_INT, "-maxsymbols", "maxSymbols", "MaxSymbols",
   "0", -1, Tk_Offset(LineElementOptions, reqMaxSymbols), 0, NULL, CACHE},
  {TK_OPTION_COLOR, "-offdash", "offDash", "OffDash", 
   NULL, -1, Tk_Offset(LineElementOptions, builtinPen.traceOffColor),
   TK_OPTION_NULL_OK, NULL, CACHE},
  {TK_OPTION_COLOR, "-outline", "outline", "Outline", 
   NULL, -1, Tk_Offset(LineElementOptions, builtinPen.symbol.outlineColor), 
   TK_OPTION_NULL_OK, NULL, CACHE},
  {TK_OPTION_PIXELS, "-outlinewidth", "outlineWidth", "OutlineWidth",
   "1", -1, Tk_Offset(LineElementOptions, builtinPen.symbol.outlineWidth),
   0, NULL, CACHE},
  {TK_OPTION_CUSTOM, "-pen", "pen", "Pen",
   NULL, -1, Tk_Offset(LineElementOptions, normalPenPtr), 
   TK_OPTION_NULL_OK, &penObjOption, LAYOUT},
  {TK_OPTION_PIXELS, "-pixels", "pixels", "Pixels", 
   "0.1i", -1, Tk_Offset(LineElementOptions, builtinPen.symbol.size), 
   0, NULL, LAYOUT},
  {TK_OPTION_DOUBLE, "-reduce", "reduce", "Reduce",
   "0", -1, Tk_Offset(LineElementOptions, rTolerance), 0, NULL, RESET},
  {TK_OPTION_BOOLEAN, "-scalesymbols", "scaleSymbols", "ScaleSymbols",
   "yes", -1, Tk_Offset(LineElementOptions, scaleSymbols), 0, NULL, LAYOUT},
  {TK_OPTION_STRING_TABLE, "-showerrorbars", "showErrorBars", "ShowErrorBars",
   "both", -1, Tk_Offset(LineElementOptions, builtinPen.errorBarShow), 
   0, &fillObjOption, LAYOUT},
  {TK_OPTION_STRING_TABLE, "-showvalues", "showValues", "ShowValues",
   "none", -1, Tk_Offset(LineElementOptions, builtinPen.valueShow), 
   0, &fillObjOption, CACHE},
  {TK_OPTION_STRING_TABLE, "-smooth", "smooth", "Smooth", 
   "linear", -1, Tk_Offset(LineElementOptions, reqSmooth), 
   0, &smoothObjOption, LAYOUT},
  {TK_OPTION_CUSTOM, "-styles", "styles", "Styles",
   "", -1, Tk_Offset(LineElementOptions, stylePalette), 
   0, &styleObjOption, RESET},
  {TK_OPTION_STRING_TABLE, "-symbol", "symbol", "Symbol",
   "none", -1, Tk_Offset(LineElementOptions, builtinPen.symbol), 
   0, &symbolObjOption, CACHE},
  {TK_OPTION_STRING_TABLE, "-trace", "trace", "Trace",
   "both", -1, Tk_Offset(LineElementOptions, penDir), 
   0, &penDirObjOption, RESET},
  {TK_OPTION_ANCHOR, "-valueanchor", "valueAnchor", "ValueAnchor",
   "s", -1, Tk_Offset(LineElementOptions, builtinPen.valueStyle.anchor),
   0, NULL, CACHE},
  {TK_OPTION_COLOR, "-valuecolor", "valueColor", "ValueColor",
   STD_NORMAL_FOREGROUND,-1,
   Tk_Offset(LineElementOptions, builtinPen.valueStyle.color),
   0, NULL, CACHE},
  {TK_OPTION_FONT, "-valuefont", "valueFont", "ValueFont",
   STD_FONT_SMALL, -1, 
   Tk_Offset(LineElementOptions, builtinPen.valueStyle.font),
   0, NULL, CACHE},
  {TK_OPTION_STRING, "-valueformat", "valueFormat", "ValueFormat",
   "%g", -1, Tk_Offset(LineElementOptions, builtinPen.valueFormat), 
   TK_OPTION_NULL_OK, NULL, CACHE},
  {TK_OPTION_DOUBLE, "-valuerotate", "valueRotate", "ValueRotate",
   "0", -1, Tk_Offset(LineElementOptions, builtinPen.valueStyle.angle),
   0, NULL, CACHE},
  {TK_OPTION_CUSTOM, "-weights", "weights", "Weights",
   NULL, -1, Tk_Offset(LineElementOptions, w), 
   TK_OPTION_NULL_OK, &valuesObjOption, RESET},
  {TK_OPTION_SYNONYM, "-x", NULL, NULL, 
   NULL, 0, -1, 0, (ClientData)"-xdata", 0},
  {TK_OPTION_CUSTOM, "-xdata", "xData", "XData", 
   NULL, -1, Tk_Offset(LineElementOptions, coords.x), 
   TK_OPTION_NULL_OK, &valuesObjOption, RESET},
  {TK_OPTION_CUSTOM, "-xerror", "xError", "XError", 
   NULL, -1, Tk_Offset(LineElementOptions, xError), 
   TK_OPTION_NULL_OK, &valuesObjOption, RESET},
  {TK_OPTION_CUSTOM, "-xhigh", "xHigh", "XHigh", 
   NULL, -1, Tk_Offset(LineElementOptions, xHigh), 
   TK_OPTION_NULL_OK, &valuesObjOption, RESET},
  {TK_OPTION_CUSTOM, "-xlow", "xLow", "XLow", 
   NULL, -1, Tk_Offset(LineElementOptions, xLow), 
   TK_OPTION_NULL_OK, &valuesObjOption, RESET},
  {TK_OPTION_SYNONYM, "-y", NULL, NULL, 
   NULL, 0, -1, 0, (ClientData)"-ydata", 0},
  {TK_OPTION_CUSTOM, "-ydata", "yData", "YData", 
   NULL, -1, Tk_Offset(LineElementOptions, coords.y), 
   TK_OPTION_NULL_OK, &valuesObjOption, RESET},
  {TK_OPTION_CUSTOM, "-yerror", "yError", "YError", 
   NULL, -1, Tk_Offset(LineElementOptions, yError), 
   TK_OPTION_NULL_OK, &valuesObjOption, RESET},
  {TK_OPTION_CUSTOM, "-yhigh", "yHigh", "YHigh",
   NULL, -1, Tk_Offset(LineElementOptions, yHigh), 
   TK_OPTION_NULL_OK, &valuesObjOption, RESET},
  {TK_OPTION_CUSTOM, "-ylow", "yLow", "YLow", 
   NULL, -1, Tk_Offset(LineElementOptions, yLow), 
   TK_OPTION_NULL_OK, &valuesObjOption, RESET},
  {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, -1, 0, 0, 0}
};

LineElement::LineElement(Graph* graphPtr, const char* name, Tcl_HashEntry* hPtr)
  : Element(graphPtr, name, hPtr)
{
  smooth_ = LINEAR;
  fillPts_ =NULL;
  nFillPts_ = 0;

  symbolPts_.points =NULL;
  symbolPts_.length =0;
  symbolPts_.map =NULL;
  activePts_.points =NULL;
  activePts_.length =0;
  activePts_.map =NULL;

  xeb_.segments =NULL;
  xeb_.map =NULL;
  xeb_.length =0;
  yeb_.segments =NULL;
  yeb_.map =NULL;
  yeb_.length =0;

  symbolInterval_ =0;
  symbolCounter_ =0;
  traces_ =NULL;

  ops_ = (LineElementOptions*)calloc(1, sizeof(LineElementOptions));
  LineElementOptions* ops = (LineElementOptions*)ops_;
  ops->elemPtr = (Element*)this;

  builtinPenPtr = new LinePen(graphPtr, "builtin", &ops->builtinPen);
  ops->builtinPenPtr = builtinPenPtr;

  optionTable_ = Tk_CreateOptionTable(graphPtr->interp_, optionSpecs);

  ops->stylePalette = new Chain();
  // this is an option and will be freed via Tk_FreeConfigOptions
  // By default an element's name and label are the same
  ops->label = Tcl_Alloc(strlen(name)+1);
  if (name)
    strcpy((char*)ops->label,(char*)name);

  Tk_InitOptions(graphPtr->interp_, (char*)&(ops->builtinPen),
		 builtinPenPtr->optionTable(), graphPtr->tkwin_);
}

LineElement::~LineElement()
{
  LineElementOptions* ops = (LineElementOptions*)ops_;

  delete builtinPenPtr;

  reset();

  if (ops->stylePalette) {
    freeStylePalette(ops->stylePalette);
    delete ops->stylePalette;
  }

  delete [] fillPts_;
}

int LineElement::configure()
{
  LineElementOptions* ops = (LineElementOptions*)ops_;

  if (builtinPenPtr->configure() != TCL_OK)
    return TCL_ERROR;

  // Point to the static normal/active pens if no external pens have been
  // selected.
  ChainLink* link = Chain_FirstLink(ops->stylePalette);
  if (!link) {
    link = new ChainLink(sizeof(LineStyle));
    ops->stylePalette->linkAfter(link, NULL);
  } 
  LineStyle* stylePtr = (LineStyle*)Chain_GetValue(link);
  stylePtr->penPtr = NORMALPEN(ops);

  return TCL_OK;
}

void LineElement::map()
{
  LineElementOptions* ops = (LineElementOptions*)ops_;
    
  if (!link)
    return;

  reset();
  if (!ops->coords.x || !ops->coords.y ||
      !ops->coords.x->nValues() || !ops->coords.y->nValues())
    return;
  int np = NUMBEROFPOINTS(ops);

  MapInfo mi;
  getScreenPoints(&mi);
  mapSymbols(&mi);

  if (nActiveIndices_ > 0)
    mapActiveSymbols();

  // Map connecting line segments if they are to be displayed.
  smooth_ = (Smoothing)ops->reqSmooth;
  if ((np > 1) && (ops->builtinPen.traceWidth > 0)) {
    // Do smoothing if necessary.  This can extend the coordinate array,
    // so both mi.points and mi.nPoints may change.
    switch (smooth_) {
    case STEP:
      generateSteps(&mi);
      break;

    case CUBIC:
    case QUADRATIC:
      // Can't interpolate with less than three points
      if (mi.nScreenPts < 3)
	smooth_ = LINEAR;
      else
	generateSpline(&mi);
      break;

    case CATROM:
      // Can't interpolate with less than three points
      if (mi.nScreenPts < 3)
	smooth_ = LINEAR;
      else
	generateParametricSpline(&mi);
      break;

    default:
      break;
    }
    if (ops->rTolerance > 0.0)
      reducePoints(&mi, ops->rTolerance);

    if (ops->fillBg)
      mapFillArea(&mi);

    mapTraces(&mi);
  }
  delete [] mi.screenPts;
  delete [] mi.map;

  // Set the symbol size of all the pen styles
  for (ChainLink* link = Chain_FirstLink(ops->stylePalette); link;
       link = Chain_NextLink(link)) {
    LineStyle* stylePtr = (LineStyle*)Chain_GetValue(link);
    LinePen* penPtr = (LinePen *)stylePtr->penPtr;
    LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();
    int size = scaleSymbol(penOps->symbol.size);
    stylePtr->symbolSize = size;
    stylePtr->errorBarCapWidth = penOps->errorBarCapWidth;
  }

  LineStyle** styleMap = (LineStyle**)StyleMap();
  if (((ops->yHigh && ops->yHigh->nValues() > 0) &&
       (ops->yLow && ops->yLow->nValues() > 0)) ||
      ((ops->xHigh && ops->xHigh->nValues() > 0) &&
       (ops->xLow && ops->xLow->nValues() > 0)) ||
      (ops->xError && ops->xError->nValues() > 0) ||
      (ops->yError && ops->yError->nValues() > 0)) {
    mapErrorBars(styleMap);
  }

  mergePens(styleMap);
  delete [] styleMap;
}

void LineElement::extents(Region2d *extsPtr)
{
  LineElementOptions* ops = (LineElementOptions*)ops_;

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

  if (!ops->coords.x || !ops->coords.y ||
      !ops->coords.x->nValues() || !ops->coords.y->nValues())
    return;
  int np = NUMBEROFPOINTS(ops);

  extsPtr->right = ops->coords.x->max();
  AxisOptions* axisxops = (AxisOptions*)ops->xAxis->ops();
  if ((ops->coords.x->min() <= 0.0) && (axisxops->logScale))
    extsPtr->left = FindElemValuesMinimum(ops->coords.x, DBL_MIN);
  else
    extsPtr->left = ops->coords.x->min();

  extsPtr->bottom = ops->coords.y->max();
  AxisOptions* axisyops = (AxisOptions*)ops->yAxis->ops();
  if ((ops->coords.y->min() <= 0.0) && (axisyops->logScale))
    extsPtr->top = FindElemValuesMinimum(ops->coords.y, DBL_MIN);
  else
    extsPtr->top = ops->coords.y->min();

  // Correct the data limits for error bars
  if (ops->xError && ops->xError->nValues() > 0) {
    np = MIN(ops->xError->nValues(), np);
    for (int ii=0; ii<np; ii++) {
      double x = ops->coords.x->values_[ii] + ops->xError->values_[ii];
      if (x > extsPtr->right)
	extsPtr->right = x;

      x = ops->coords.x->values_[ii] - ops->xError->values_[ii];
      AxisOptions* axisxops = (AxisOptions*)ops->xAxis->ops();
      if (axisxops->logScale) {
	// Mirror negative values, instead of ignoring them
	if (x < 0.0)
	  x = -x;
	if ((x > DBL_MIN) && (x < extsPtr->left))
	  extsPtr->left = x;
      } 
      else if (x < extsPtr->left)
	extsPtr->left = x;
    }		     
  }
  else {
    if (ops->xHigh && 
	(ops->xHigh->nValues() > 0) && 
	(ops->xHigh->max() > extsPtr->right)) {
      extsPtr->right = ops->xHigh->max();
    }
    if (ops->xLow && ops->xLow->nValues() > 0) {
      double left;
      if ((ops->xLow->min() <= 0.0) && (axisxops->logScale))
	left = FindElemValuesMinimum(ops->xLow, DBL_MIN);
      else
	left = ops->xLow->min();

      if (left < extsPtr->left)
	extsPtr->left = left;
    }
  }
    
  if (ops->yError && ops->yError->nValues() > 0) {
    np = MIN(ops->yError->nValues(), np);
    for (int ii=0; ii<np; ii++) {
      double y = ops->coords.y->values_[ii] + ops->yError->values_[ii];
      if (y > extsPtr->bottom)
	extsPtr->bottom = y;

      y = ops->coords.y->values_[ii] - ops->yError->values_[ii];
      AxisOptions* axisyops = (AxisOptions*)ops->yAxis->ops();
      if (axisyops->logScale) {
	// Mirror negative values, instead of ignoring them
	if (y < 0.0)
	  y = -y;
	if ((y > DBL_MIN) && (y < extsPtr->left))
	  extsPtr->top = y;
      }
      else if (y < extsPtr->top)
	extsPtr->top = y;
    }
  }
  else {
    if (ops->yHigh && (ops->yHigh->nValues() > 0) && 
	(ops->yHigh->max() > extsPtr->bottom))
      extsPtr->bottom = ops->yHigh->max();

    if (ops->yLow && ops->yLow->nValues() > 0) {
      double top;
      if ((ops->yLow->min() <= 0.0) && (axisyops->logScale))
	top = FindElemValuesMinimum(ops->yLow, DBL_MIN);
      else
	top = ops->yLow->min();

      if (top < extsPtr->top)
	extsPtr->top = top;
    }
  }
}

void LineElement::closest()
{
  LineElementOptions* ops = (LineElementOptions*)ops_;
  GraphOptions* gops = (GraphOptions*)graphPtr_->ops_;

  ClosestSearch* searchPtr = &gops->search;
  int mode = searchPtr->mode;
  if (mode == SEARCH_AUTO) {
    LinePen* penPtr = NORMALPEN(ops);
    LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();
    mode = SEARCH_POINTS;
    if ((NUMBEROFPOINTS(ops) > 1) && (penOps->traceWidth > 0))
      mode = SEARCH_TRACES;
  }
  if (mode == SEARCH_POINTS)
    closestPoint(searchPtr);
  else {
    int found = closestTrace();
    if ((!found) && (searchPtr->along != SEARCH_BOTH))
      closestPoint(searchPtr);
  }
}

void LineElement::draw(Drawable drawable)
{
  LineElementOptions* ops = (LineElementOptions*)ops_;
  LinePen* penPtr = NORMALPEN(ops);
  LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();

  if (ops->hide)
    return;

  // Fill area under the curve
  if (ops->fillBg && fillPts_) {
    XPoint*points = new XPoint[nFillPts_];

    unsigned int count =0;
    for (Point2d *pp = fillPts_, *endp = pp + nFillPts_; pp < endp; pp++) {
      points[count].x = (short)pp->x;
      points[count].y = (short)pp->y;
      count++;
    }
    Tk_Fill3DPolygon(graphPtr_->tkwin_, drawable, ops->fillBg, points, 
		     nFillPts_, 0, TK_RELIEF_FLAT);
    delete [] points;
  }

  // traces
  if ((Chain_GetLength(traces_) > 0) && (penOps->traceWidth > 0))
    drawTraces(drawable, penPtr);

  // Symbols, error bars, values
  if (ops->reqMaxSymbols > 0) {
    int total = 0;
    for (ChainLink* link = Chain_FirstLink(ops->stylePalette); link;
	 link = Chain_NextLink(link)) {
      LineStyle *stylePtr = (LineStyle*)Chain_GetValue(link);
      total += stylePtr->symbolPts.length;
    }
    symbolInterval_ = total / ops->reqMaxSymbols;
    symbolCounter_ = 0;
  }

  unsigned int count =0;
  for (ChainLink* link = Chain_FirstLink(ops->stylePalette); link;
       link = Chain_NextLink(link)) {
    LineStyle* stylePtr = (LineStyle*)Chain_GetValue(link);
    LinePen* penPtr = (LinePen *)stylePtr->penPtr;
    LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();

    if ((stylePtr->xeb.length > 0) && (penOps->errorBarShow & SHOW_X))
      graphPtr_->drawSegments(drawable, penPtr->errorBarGC_, 
			      stylePtr->xeb.segments, stylePtr->xeb.length);

    if ((stylePtr->yeb.length > 0) && (penOps->errorBarShow & SHOW_Y))
      graphPtr_->drawSegments(drawable, penPtr->errorBarGC_, 
			      stylePtr->yeb.segments, stylePtr->yeb.length);

    if ((stylePtr->symbolPts.length > 0) && 
	(penOps->symbol.type != SYMBOL_NONE))
      drawSymbols(drawable, penPtr, stylePtr->symbolSize,
		  stylePtr->symbolPts.length, stylePtr->symbolPts.points);

    if (penOps->valueShow != SHOW_NONE)
      drawValues(drawable, penPtr, stylePtr->symbolPts.length, 
		 stylePtr->symbolPts.points, symbolPts_.map + count);

    count += stylePtr->symbolPts.length;
  }

  symbolInterval_ = 0;
  symbolCounter_ = 0;
}

void LineElement::drawActive(Drawable drawable)
{
  LineElementOptions* ops = (LineElementOptions*)ops_;
  LinePen* penPtr = (LinePen*)ops->activePenPtr;
  if (!penPtr)
    return;
  LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();

  if (ops->hide || !active_)
    return;

  int symbolSize = scaleSymbol(penOps->symbol.size);

  if (nActiveIndices_ > 0) {
    mapActiveSymbols();

    if (penOps->symbol.type != SYMBOL_NONE)
      drawSymbols(drawable, penPtr, symbolSize, activePts_.length,
		  activePts_.points);
    if (penOps->valueShow != SHOW_NONE)
      drawValues(drawable, penPtr, activePts_.length, activePts_.points, 
		 activePts_.map);
  }
  else if (nActiveIndices_ < 0) { 
    if ((Chain_GetLength(traces_) > 0) && (penOps->traceWidth > 0))
      drawTraces(drawable, penPtr);

    if (penOps->symbol.type != SYMBOL_NONE)
      drawSymbols(drawable, penPtr, symbolSize, symbolPts_.length,
		  symbolPts_.points);

    if (penOps->valueShow != SHOW_NONE) {
      drawValues(drawable, penPtr, symbolPts_.length, symbolPts_.points, 
		 symbolPts_.map);
    }
  }
}

void LineElement::drawSymbol(Drawable drawable, int x, int y, int size)
{
  LineElementOptions* ops = (LineElementOptions*)ops_;

  LinePen* penPtr = NORMALPEN(ops);
  LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();

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

void LineElement::print(PSOutput* psPtr)
{
  LineElementOptions* ops = (LineElementOptions*)ops_;
  LinePen* penPtr = NORMALPEN(ops);
  LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();

  if (ops->hide)
    return;

  psPtr->format("\n%% Element \"%s\"\n\n", name_);

  // Draw fill area
  if (ops->fillBg && fillPts_) {
    psPtr->append("% start fill area\n");
    psPtr->setBackground(ops->fillBg);
    psPtr->printPolyline(fillPts_, nFillPts_);
    psPtr->append("gsave fill grestore\n");
    psPtr->append("% end fill area\n");
  }

  // traces
  if ((Chain_GetLength(traces_) > 0) && (penOps->traceWidth > 0))
    printTraces(psPtr, penPtr);

  // Symbols, error bars, values
  if (ops->reqMaxSymbols > 0) {
    int total = 0;
    for (ChainLink* link = Chain_FirstLink(ops->stylePalette); link;
	 link = Chain_NextLink(link)) {
      LineStyle *stylePtr = (LineStyle*)Chain_GetValue(link);
      total += stylePtr->symbolPts.length;
    }
    symbolInterval_ = total / ops->reqMaxSymbols;
    symbolCounter_ = 0;
  }

  unsigned int count =0;
  for (ChainLink* link = Chain_FirstLink(ops->stylePalette); link;
       link = Chain_NextLink(link)) {
    LineStyle *stylePtr = (LineStyle*)Chain_GetValue(link);
    LinePen* penPtr = (LinePen *)stylePtr->penPtr;
    LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();
    XColor* colorPtr = penOps->errorBarColor;
    if (!colorPtr)
      colorPtr = penOps->traceColor;

    if ((stylePtr->xeb.length > 0) && (penOps->errorBarShow & SHOW_X)) {
      psPtr->setLineAttributes(colorPtr, penOps->errorBarLineWidth, 
			       NULL, CapButt, JoinMiter);
      psPtr->printSegments(stylePtr->xeb.segments, stylePtr->xeb.length);
    }

    if ((stylePtr->yeb.length > 0) && (penOps->errorBarShow & SHOW_Y)) {
      psPtr->setLineAttributes(colorPtr, penOps->errorBarLineWidth, 
			       NULL, CapButt, JoinMiter);
      psPtr->printSegments(stylePtr->yeb.segments, stylePtr->yeb.length);
    }

    if ((stylePtr->symbolPts.length > 0) && 
	(penOps->symbol.type != SYMBOL_NONE))
      printSymbols(psPtr, penPtr, stylePtr->symbolSize, 
		   stylePtr->symbolPts.length, stylePtr->symbolPts.points);

    if (penOps->valueShow != SHOW_NONE)
      printValues(psPtr, penPtr, stylePtr->symbolPts.length, 
		  stylePtr->symbolPts.points, symbolPts_.map + count);

    count += stylePtr->symbolPts.length;
  }

  symbolInterval_ = 0;
  symbolCounter_ = 0;
}

void LineElement::printActive(PSOutput* psPtr)
{
  LineElementOptions* ops = (LineElementOptions*)ops_;
  LinePen* penPtr = (LinePen *)ops->activePenPtr;
  if (!penPtr)
    return;
  LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();

  if (ops->hide || !active_)
    return;

  psPtr->format("\n%% Active Element \"%s\"\n\n", name_);

  int symbolSize = scaleSymbol(penOps->symbol.size);
  if (nActiveIndices_ > 0) {
    mapActiveSymbols();

    if (penOps->symbol.type != SYMBOL_NONE)
      printSymbols(psPtr, penPtr, symbolSize, activePts_.length,
			  activePts_.points);

    if (penOps->valueShow != SHOW_NONE)
      printValues(psPtr, penPtr, activePts_.length, activePts_.points,
		  activePts_.map);
  }
  else if (nActiveIndices_ < 0) {
    if ((Chain_GetLength(traces_) > 0) && (penOps->traceWidth > 0))
      printTraces(psPtr, (LinePen*)penPtr);

    if (penOps->symbol.type != SYMBOL_NONE)
      printSymbols(psPtr, penPtr, symbolSize, symbolPts_.length, 
		   symbolPts_.points);
    if (penOps->valueShow != SHOW_NONE) {
      printValues(psPtr, penPtr, symbolPts_.length, symbolPts_.points,
		  symbolPts_.map);
    }
  }
}

void LineElement::printSymbol(PSOutput* psPtr, double x, double y, int size)
{
  LineElementOptions* ops = (LineElementOptions*)ops_;

  LinePen* penPtr = NORMALPEN(ops);
  LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();

  if (penOps->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.
    psPtr->setLineAttributes(penOps->traceColor, penOps->traceWidth, 
			      &penOps->traceDashes, CapButt, JoinMiter);
    psPtr->format("%g %g %d Li\n", x, y, size + size);
  }

  if (penOps->symbol.type != SYMBOL_NONE) {
    Point2d point;
    point.x = x;
    point.y = y;
    printSymbols(psPtr, penPtr, size, 1, &point);
  }
}

// Support

double LineElement::distanceToLine(int x, int y, Point2d *p, Point2d *q,
				   Point2d *t)
{
  double right, left, top, bottom;

  *t = getProjection(x, y, p, q);
  if (p->x > q->x)
    right = p->x, left = q->x;
  else
    left = p->x, right = q->x;

  if (p->y > q->y)
    bottom = p->y, top = q->y;
  else
    top = p->y, bottom = q->y;

  if (t->x > right)
    t->x = right;
  else if (t->x < left)
    t->x = left;

  if (t->y > bottom)
    t->y = bottom;
  else if (t->y < top)
    t->y = top;

  return hypot((t->x - x), (t->y - y));
}

double LineElement::distanceToX(int x, int y, Point2d *p, Point2d *q, 
				Point2d *t)
{
  double dx, dy;
  double d;

  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, d = d1;
    } else {
      t->y = q->y, d = d2;
    }
  }
  else if (fabs(dy) < DBL_EPSILON) {
    /* Horizontal line. */
    t->y = p->y, d = p->y - y;
  }
  else {
    double m = dy / dx;
    double b = p->y - (m * p->x);
    t->y = (x * m) + b;
    d = y - t->y;
  }

  return fabs(d);
}

double LineElement::distanceToY(int x, int y, Point2d *p, Point2d *q,
				Point2d *t)
{
  double dx, dy;
  double d;

  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, d = d1;
    }
    else {
      t->x = q->x, d = d2;
    }
  }
  else if (fabs(dx) < DBL_EPSILON) {
    /* Vertical line. */
    t->x = p->x, d = p->x - x;
  } 
  else {
    double m = dy / dx;
    double b = p->y - (m * p->x);
    t->x = (y - b) / m;
    d = x - t->x;
  }

  return fabs(d);
}

int LineElement::scaleSymbol(int normalSize)
{
  LineElementOptions* ops = (LineElementOptions*)ops_;

  double scale = 1.0;
  if (ops->scaleSymbols) {
    double xRange = (ops->xAxis->max_ - ops->xAxis->min_);
    double yRange = (ops->yAxis->max_ - ops->yAxis->min_);
    // Save the ranges as a baseline for future scaling
    if (!xRange_ || !yRange_) {
      xRange_ = xRange;
      yRange_ = yRange;
    }
    else {
      // Scale the symbol by the smallest change in the X or Y axes
      double xScale = xRange_ / xRange;
      double yScale = yRange_ / yRange;
      scale = MIN(xScale, yScale);
    }
  }
  int newSize = (int)(normalSize * scale);

  int maxSize = MIN(graphPtr_->hRange_, graphPtr_->vRange_);
  if (newSize > maxSize)
    newSize = maxSize;

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

  return newSize;
}

void LineElement::getScreenPoints(MapInfo* mapPtr)
{
  LineElementOptions* ops = (LineElementOptions*)ops_;
  GraphOptions* gops = (GraphOptions*)graphPtr_->ops_;

  if (!ops->coords.x || !ops->coords.y) {
    mapPtr->screenPts = NULL;
    mapPtr->nScreenPts = 0;
    mapPtr->map = NULL;
  }

  int np = NUMBEROFPOINTS(ops);
  double* x = ops->coords.x->values_;
  double* y = ops->coords.y->values_;
  Point2d* points = new Point2d[np];
  int* map = new int[np];

  int count = 0;
  if (gops->inverted) {
    for (int ii=0; ii<np; ii++) {
      if ((isfinite(x[ii])) && (isfinite(y[ii]))) {
	points[count].x = ops->yAxis->hMap(y[ii]);
	points[count].y = ops->xAxis->vMap(x[ii]);
	map[count] = ii;
	count++;
      }
    }
  }
  else {
    for (int ii=0; ii< np; ii++) {
      if ((isfinite(x[ii])) && (isfinite(y[ii]))) {
	points[count].x = ops->xAxis->hMap(x[ii]);
	points[count].y = ops->yAxis->vMap(y[ii]);
	map[count] = ii;
	count++;
      }
    }
  }
  mapPtr->screenPts = points;
  mapPtr->nScreenPts = count;
  mapPtr->map = map;
}

void LineElement::reducePoints(MapInfo *mapPtr, double tolerance)
{
  int* simple = new int[mapPtr->nScreenPts];
  int* map = new int[mapPtr->nScreenPts];
  Point2d* screenPts = new Point2d[mapPtr->nScreenPts];
  int np = simplify(mapPtr->screenPts, 0, mapPtr->nScreenPts - 1, 
		    tolerance, simple);
  for (int ii=0; ii<np; ii++) {
    int kk = simple[ii];
    screenPts[ii] = mapPtr->screenPts[kk];
    map[ii] = mapPtr->map[kk];
  }
  delete [] simple;

  delete [] mapPtr->screenPts;
  mapPtr->screenPts = screenPts;
  delete [] mapPtr->map;
  mapPtr->map = map;
  mapPtr->nScreenPts = np;
}

// Douglas-Peucker line simplification algorithm
int LineElement::simplify(Point2d *inputPts, int low, int high, 
			  double tolerance, int *indices)
{
#define StackPush(a)	s++, stack[s] = (a)
#define StackPop(a)	(a) = stack[s], s--
#define StackEmpty()	(s < 0)
#define StackTop()	stack[s]
    int split = -1; 
    double dist2, tolerance2;
    int s = -1;			/* Points to top stack item. */

    int* stack = new int[high - low + 1];
    StackPush(high);
    int count = 0;
    indices[count++] = 0;
    tolerance2 = tolerance * tolerance;
    while (!StackEmpty()) {
	dist2 = findSplit(inputPts, low, StackTop(), &split);
	if (dist2 > tolerance2)
	    StackPush(split);
	else {
	    indices[count++] = StackTop();
	    StackPop(low);
	}
    } 
    delete [] stack;
    return count;
}

double LineElement::findSplit(Point2d *points, int i, int j, int *split)	
{    
    double maxDist2 = -1.0;
    if ((i + 1) < j) {
	double a = points[i].y - points[j].y;
	double b = points[j].x - points[i].x;
	double c = (points[i].x * points[j].y) - (points[i].y * points[j].x);
	for (int kk = (i + 1); kk < j; kk++) {
	    double dist2 = (points[kk].x * a) + (points[kk].y * b) + c;
	    if (dist2 < 0.0)
		dist2 = -dist2;	

	    // Track the maximum.
	    if (dist2 > maxDist2) {
		maxDist2 = dist2;
		*split = kk;
	    }
	}
	// Correction for segment length---should be redone if can == 0
	maxDist2 *= maxDist2 / (a * a + b * b);
    } 
    return maxDist2;
}

void LineElement::generateSteps(MapInfo *mapPtr)
{
  int newSize = ((mapPtr->nScreenPts - 1) * 2) + 1;
  Point2d* screenPts = new Point2d[newSize];
  int* map = new int[newSize];
  screenPts[0] = mapPtr->screenPts[0];
  map[0] = 0;

  int count = 1;
  for (int 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
    map[count] = map[count + 1] = mapPtr->map[i];
    count += 2;
  }
  delete [] mapPtr->map;
  mapPtr->map = map;
  delete [] mapPtr->screenPts;
  mapPtr->screenPts = screenPts;
  mapPtr->nScreenPts = newSize;
}

void LineElement::generateSpline(MapInfo *mapPtr)
{
  int nOrigPts = mapPtr->nScreenPts;
  Point2d* origPts = mapPtr->screenPts;

  // check points are not monotonically increasing
  for (int ii=0, jj=1; jj<nOrigPts; ii++, jj++) {
    if (origPts[jj].x <= origPts[ii].x)
      return;
  }
  if (((origPts[0].x > (double)graphPtr_->right_)) ||
      ((origPts[mapPtr->nScreenPts - 1].x < (double)graphPtr_->left_)))
    return;

  // 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.
  int extra = (graphPtr_->right_ - graphPtr_->left_) + 1;
  if (extra < 1)
    return;

  int niPts = nOrigPts + extra + 1;
  Point2d* iPts = new Point2d[niPts];
  int* map = new int[niPts];

  // Populate the x2 array with both the original X-coordinates and extra
  // X-coordinates for each horizontal pixel that the line segment contains
  int count = 0;
  for (int ii=0, jj=1; jj<nOrigPts; ii++, jj++) {
    // Add the original x-coordinate
    iPts[count].x = origPts[ii].x;

    // Include the starting offset of the point in the offset array
    map[count] = mapPtr->map[ii];
    count++;

    // Is any part of the interval (line segment) in the plotting area? 
    if ((origPts[jj].x >= (double)graphPtr_->left_) || 
	(origPts[ii].x <= (double)graphPtr_->right_)) {
      double x = origPts[ii].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, (double)graphPtr_->left_);
      double last = MIN(origPts[jj].x, (double)graphPtr_->right_);

      // Add the extra x-coordinates to the interval
      while (x < last) {
	map[count] = mapPtr->map[ii];
	iPts[count++].x = x;
	x++;
      }
    }
  }
  niPts = count;
  int result = 0;
  if (smooth_ == CUBIC)
    result = naturalSpline(origPts, nOrigPts, iPts, niPts);
  else if (smooth_ == QUADRATIC)
    result = quadraticSpline(origPts, nOrigPts, iPts, niPts);

  // The spline interpolation failed.  We will fall back to the current
  // coordinates and do no smoothing (standard line segments)
  if (!result) {
    smooth_ = LINEAR;
    delete [] iPts;
    delete [] map;
  }
  else {
    delete [] mapPtr->map;
    mapPtr->map = map;
    delete [] mapPtr->screenPts;
    mapPtr->screenPts = iPts;
    mapPtr->nScreenPts = niPts;
  }
}

void LineElement::generateParametricSpline(MapInfo *mapPtr)
{
  int nOrigPts = mapPtr->nScreenPts;
  Point2d *origPts = mapPtr->screenPts;

  Region2d exts;
  graphPtr_->extents(&exts);

  /* 
   * Populate the x2 array with both the original X-coordinates and extra
   * X-coordinates for each horizontal pixel that the line segment contains.
   */
  int count = 1;
  for (int i = 0, j = 1; j < nOrigPts; i++, j++) {
    Point2d p = origPts[i];
    Point2d q = origPts[j];
    count++;
    if (lineRectClip(&exts, &p, &q))
      count += (int)(hypot(q.x - p.x, q.y - p.y) * 0.5);
  }
  int niPts = count;
  Point2d *iPts = new Point2d[niPts];
  int* map = new int[niPts];

  /* 
   * 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;
  int i,j;
  for (i = 0, j = 1; j < nOrigPts; i++, j++) {
    Point2d p = origPts[i];
    Point2d q = origPts[j];

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

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

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

    if (lineRectClip(&exts, &p, &q)) {
      double dp, dq;

      /* Distance of original point to p. */
      dp = hypot(p.x - origPts[i].x, p.y - origPts[i].y);
      /* Distance of original point to q. */
      dq = hypot(q.x - origPts[i].x, q.y - origPts[i].y);
      dp += 2.0;
      while(dp <= dq) {
	/* Point is indicated by its interval and parameter t. */
	iPts[count].x = (double)i;
	iPts[count].y =  dp / d;
	map[count] = mapPtr->map[i];
	count++;
	dp += 2.0;
      }
    }
  }
  iPts[count].x = (double)i;
  iPts[count].y = 0.0;
  map[count] = mapPtr->map[i];
  count++;
  niPts = count;
  int result = 0;
  if (smooth_ == CUBIC)
    result = naturalParametricSpline(origPts, nOrigPts, &exts, 0, iPts, niPts);
  else if (smooth_ == CATROM)
    result = catromParametricSpline(origPts, nOrigPts, iPts, niPts);

  // The spline interpolation failed.  We will fall back to the current
  // coordinates and do no smoothing (standard line segments)
  if (!result) {
    smooth_ = LINEAR;
    delete [] iPts;
    delete [] map;
  }
  else {
    delete [] mapPtr->map;
    mapPtr->map = map;
    delete [] mapPtr->screenPts;
    mapPtr->screenPts = iPts;
    mapPtr->nScreenPts = niPts;
  }
}

void LineElement::mapSymbols(MapInfo *mapPtr)
{
  Point2d* points = new Point2d[mapPtr->nScreenPts];
  int *map = new int[mapPtr->nScreenPts];

  Region2d exts;
  graphPtr_->extents(&exts);

  Point2d *pp;
  int count = 0;
  int i;
  for (pp=mapPtr->screenPts, i=0; i<mapPtr->nScreenPts; i++, pp++) {
    if (PointInRegion(&exts, pp->x, pp->y)) {
      points[count].x = pp->x;
      points[count].y = pp->y;
      map[count] = mapPtr->map[i];
      count++;
    }
  }
  symbolPts_.points = points;
  symbolPts_.length = count;
  symbolPts_.map = map;
}

void LineElement::mapActiveSymbols()
{
  LineElementOptions* ops = (LineElementOptions*)ops_;

  delete [] activePts_.points;
  activePts_.points = NULL;
  delete [] activePts_.map;
  activePts_.map = NULL;

  Region2d exts;
  graphPtr_->extents(&exts);

  Point2d *points = new Point2d[nActiveIndices_];
  int* map = new int[nActiveIndices_];
  int np = NUMBEROFPOINTS(ops);
  int count = 0;
  if (ops->coords.x && ops->coords.y) {
    for (int ii=0; ii<nActiveIndices_; ii++) {
      int iPoint = activeIndices_[ii];
      if (iPoint >= np)
	continue;

      double x = ops->coords.x->values_[iPoint];
      double y = ops->coords.y->values_[iPoint];
      points[count] = graphPtr_->map2D(x, y, ops->xAxis, ops->yAxis);
      map[count] = iPoint;
      if (PointInRegion(&exts, points[count].x, points[count].y)) {
	count++;
      }
    }
  }

  if (count > 0) {
    activePts_.points = points;
    activePts_.map = map;
  }
  else {
    delete [] points;
    delete [] map;	
  }
  activePts_.length = count;
}

void LineElement::mergePens(LineStyle **styleMap)
{
  LineElementOptions* ops = (LineElementOptions*)ops_;

  if (Chain_GetLength(ops->stylePalette) < 2) {
    ChainLink* link = Chain_FirstLink(ops->stylePalette);
    LineStyle *stylePtr = (LineStyle*)Chain_GetValue(link);
    stylePtr->symbolPts.length = symbolPts_.length;
    stylePtr->symbolPts.points = symbolPts_.points;
    stylePtr->xeb.length = xeb_.length;
    stylePtr->xeb.segments = xeb_.segments;
    stylePtr->yeb.length = yeb_.length;
    stylePtr->yeb.segments = yeb_.segments;
    return;
  }

  if (symbolPts_.length > 0) {
    Point2d* points = new Point2d[symbolPts_.length];
    int* map = new int[symbolPts_.length];
    Point2d *pp = points;
    int* ip = map;
    for (ChainLink* link = Chain_FirstLink(ops->stylePalette); link;
	 link = Chain_NextLink(link)) {
      LineStyle *stylePtr = (LineStyle*)Chain_GetValue(link);
      stylePtr->symbolPts.points = pp;
      for (int ii=0; ii<symbolPts_.length; ii++) {
	int iData = symbolPts_.map[ii];
	if (styleMap[iData] == stylePtr) {
	  *pp++ = symbolPts_.points[ii];
	  *ip++ = iData;
	}
      }
      stylePtr->symbolPts.length = pp - stylePtr->symbolPts.points;
    }
    delete [] symbolPts_.points;
    symbolPts_.points = points;
    delete [] symbolPts_.map;
    symbolPts_.map = map;
  }

  if (xeb_.length > 0) {
    Segment2d* segments = new Segment2d[xeb_.length];
    Segment2d *sp = segments;
    int* map = new int[xeb_.length];
    int* ip = map;
    for (ChainLink* link = Chain_FirstLink(ops->stylePalette); link;
	 link = Chain_NextLink(link)) {
      LineStyle *stylePtr = (LineStyle*)Chain_GetValue(link);
      stylePtr->xeb.segments = sp;
      for (int ii=0; ii<xeb_.length; ii++) {
	int iData = xeb_.map[ii];
	if (styleMap[iData] == stylePtr) {
	  *sp++ = xeb_.segments[ii];
	  *ip++ = iData;
	}
      }
      stylePtr->xeb.length = sp - stylePtr->xeb.segments;
    }
    delete [] xeb_.segments;
    xeb_.segments = segments;
    delete [] xeb_.map;
    xeb_.map = map;
  }

  if (yeb_.length > 0) {
    Segment2d* segments = new Segment2d[yeb_.length];
    Segment2d* sp = segments;
    int* map = new int [yeb_.length];
    int* ip = map;
    for (ChainLink* link = Chain_FirstLink(ops->stylePalette); link;
	 link = Chain_NextLink(link)) {
      LineStyle *stylePtr = (LineStyle*)Chain_GetValue(link);
      stylePtr->yeb.segments = sp;
      for (int ii=0; ii<yeb_.length; ii++) {
	int iData = yeb_.map[ii];
	if (styleMap[iData] == stylePtr) {
	  *sp++ = yeb_.segments[ii];
	  *ip++ = iData;
	}
      }
      stylePtr->yeb.length = sp - stylePtr->yeb.segments;
    }
    delete [] yeb_.segments;
    yeb_.segments = segments;
    delete [] yeb_.map;
    yeb_.map = map;
  }
}

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

int LineElement::outCode(Region2d *extsPtr, Point2d *p)
{
  int 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;
}

int LineElement::clipSegment(Region2d *extsPtr, int code1, int code2,
			     Point2d *p, Point2d *q)
{
  int inside = ((code1 | code2) == 0);
  int outside = ((code1 & code2) != 0);

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

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

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

void LineElement::saveTrace(int start, int length, MapInfo* mapPtr)
{
  bltTrace* tracePtr  = new bltTrace;
  Point2d* screenPts = new Point2d[length];
  int* map = new int[length];

  // Copy the screen coordinates of the trace into the point array
  if (mapPtr->map) {
    for (int ii=0, jj=start; ii<length; ii++, jj++) {
      screenPts[ii].x = mapPtr->screenPts[jj].x;
      screenPts[ii].y = mapPtr->screenPts[jj].y;
      map[ii] = mapPtr->map[jj];
    }
  } 
  else {
    for (int ii=0, jj=start; ii<length; ii++, jj++) {
      screenPts[ii].x = mapPtr->screenPts[jj].x;
      screenPts[ii].y = mapPtr->screenPts[jj].y;
      map[ii] = jj;
    }
  }
  tracePtr->screenPts.length = length;
  tracePtr->screenPts.points = screenPts;
  tracePtr->screenPts.map = map;
  tracePtr->start = start;
  if (traces_ == NULL)
    traces_ = new Chain();

  traces_->append(tracePtr);
}

void LineElement::freeTraces()
{
  for (ChainLink* link = Chain_FirstLink(traces_); link;
       link = Chain_NextLink(link)) {
    bltTrace* tracePtr = (bltTrace*)Chain_GetValue(link);
    delete [] tracePtr->screenPts.map;
    delete [] tracePtr->screenPts.points;
    delete tracePtr;
  }
  delete traces_;
  traces_ = NULL;
}

void LineElement::mapTraces(MapInfo *mapPtr)
{
  LineElementOptions* ops = (LineElementOptions*)ops_;

  Region2d exts;
  graphPtr_->extents(&exts);

  int count = 1;
  int code1 = outCode(&exts, mapPtr->screenPts);
  Point2d* p = mapPtr->screenPts;
  Point2d* q = p + 1;

  int start;
  int ii;
  for (ii=1; ii<mapPtr->nScreenPts; ii++, p++, q++) {
    Point2d s;
    s.x = 0;
    s.y = 0;
    int code2 = outCode(&exts, q);
    // Save the coordinates of the last point, before clipping
    if (code2 != 0)
      s = *q;

    int broken = BROKEN_TRACE(ops->penDir, p->x, q->x);
    int 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 = ii - count;
	saveTrace(start, count, mapPtr);
	count = 1;
      }
    }
    else {
      // Add the point to the trace
      count++;

      // 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.
      if (code2 != 0) {
	start = ii - (count - 1);
	saveTrace(start, count, mapPtr);
	mapPtr->screenPts[ii] = s;
	count = 1;
      }
    }
    code1 = code2;
  }
  if (count > 1) {
    start = ii - count;
    saveTrace(start, count, mapPtr);
  }
}

void LineElement::mapFillArea(MapInfo *mapPtr)
{
  LineElementOptions* ops = (LineElementOptions*)ops_;
  GraphOptions* gops = (GraphOptions*)graphPtr_->ops_;

  if (fillPts_) {
    delete [] fillPts_;
    fillPts_ = NULL;
    nFillPts_ = 0;
  }
  if (mapPtr->nScreenPts < 3)
    return;

  int np = mapPtr->nScreenPts + 3;
  Region2d exts;
  graphPtr_->extents(&exts);

  Point2d* origPts = new Point2d[np];
  if (gops->inverted) {
    int i;
    double minX = (double)ops->yAxis->screenMin_;
    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].x < minX) {
	minX = origPts[i].x;
      }
    }	
    // Add edges to make the polygon fill to the bottom of plotting window
    origPts[i].x = minX;
    origPts[i].y = origPts[i - 1].y;
    i++;
    origPts[i].x = minX;
    origPts[i].y = origPts[0].y; 
    i++;
    origPts[i] = origPts[0];
  }
  else {
    int i;
    double maxY = (double)ops->yAxis->bottom_;
    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 extend the fill polygon 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];
  }

  Point2d *clipPts = new Point2d[np * 3];
  np = polyRectClip(&exts, origPts, np - 1, clipPts);

  delete [] origPts;
  if (np < 3)
    delete [] clipPts;
  else {
    fillPts_ = clipPts;
    nFillPts_ = np;
  }
}

void LineElement::reset()
{
  LineElementOptions* ops = (LineElementOptions*)ops_;

  freeTraces();

  for (ChainLink* link = Chain_FirstLink(ops->stylePalette); link;
       link = Chain_NextLink(link)) {
    LineStyle *stylePtr = (LineStyle*)Chain_GetValue(link);
    stylePtr->symbolPts.length = 0;
    stylePtr->xeb.length = 0;
    stylePtr->yeb.length = 0;
  }

  delete [] symbolPts_.points;
  symbolPts_.points = NULL;

  delete [] symbolPts_.map;
  symbolPts_.map = NULL;
  symbolPts_.length = 0;

  delete [] activePts_.points;
  activePts_.points = NULL;
  activePts_.length = 0;

  delete [] activePts_.map;
  activePts_.map = NULL;

  delete [] xeb_.segments;
  xeb_.segments = NULL;
  delete [] xeb_.map;
  xeb_.map = NULL;
  xeb_.length = 0;

  delete [] yeb_.segments;
  yeb_.segments = NULL;
  delete [] yeb_.map;
  yeb_.map = NULL;
  yeb_.length = 0;
}

void LineElement::mapErrorBars(LineStyle **styleMap)
{
  LineElementOptions* ops = (LineElementOptions*)ops_;

  Region2d exts;
  graphPtr_->extents(&exts);

  int nn =0;
  int np = NUMBEROFPOINTS(ops);
  if (ops->coords.x && ops->coords.y) {
    if (ops->xError && (ops->xError->nValues() > 0))
      nn = MIN(ops->xError->nValues(), np);
    else
      if (ops->xHigh && ops->xLow)
	nn = MIN3(ops->xHigh->nValues(), ops->xLow->nValues(), np);
  }

  if (nn) {
    Segment2d* errorBars = new Segment2d[nn * 3];
    Segment2d* segPtr = errorBars;
    int* errorToData = new int[nn * 3];
    int* indexPtr = errorToData;

    for (int ii=0; ii<nn; ii++) {
      double x = ops->coords.x->values_[ii];
      double y = ops->coords.y->values_[ii];
      LineStyle* stylePtr = styleMap[ii];

      if ((isfinite(x)) && (isfinite(y))) {
	double high;
	double low;
	if (ops->xError->nValues() > 0) {
	  high = x + ops->xError->values_[ii];
	  low = x - ops->xError->values_[ii];
	} 
	else {
	  high = ops->xHigh ? ops->xHigh->values_[ii] : 0;
	  low  = ops->xLow ? ops->xLow->values_[ii] : 0;
	}

	if ((isfinite(high)) && (isfinite(low)))  {
	  Point2d p = graphPtr_->map2D(high, y, ops->xAxis, ops->yAxis);
	  Point2d q = graphPtr_->map2D(low, y, ops->xAxis, ops->yAxis);
	  segPtr->p = p;
	  segPtr->q = q;
	  if (lineRectClip(&exts, &segPtr->p, &segPtr->q)) {
	    segPtr++;
	    *indexPtr++ = ii;
	  }
	  // Left cap
	  segPtr->p.x = p.x;
	  segPtr->q.x = p.x;
	  segPtr->p.y = p.y - stylePtr->errorBarCapWidth;
	  segPtr->q.y = p.y + stylePtr->errorBarCapWidth;
	  if (lineRectClip(&exts, &segPtr->p, &segPtr->q)) {
	    segPtr++;
	    *indexPtr++ = ii;
	  }
	  // Right cap
	  segPtr->p.x = q.x;
	  segPtr->q.x = q.x;
	  segPtr->p.y = q.y - stylePtr->errorBarCapWidth;
	  segPtr->q.y = q.y + stylePtr->errorBarCapWidth;
	  if (lineRectClip(&exts, &segPtr->p, &segPtr->q)) {
	    segPtr++;
	    *indexPtr++ = ii;
	  }
	}
      }
    }
    xeb_.segments = errorBars;
    xeb_.length = segPtr - errorBars;
    xeb_.map = errorToData;
  }

  nn =0;
  if (ops->coords.x && ops->coords.y) {
    if (ops->yError && (ops->yError->nValues() > 0))
      nn = MIN(ops->yError->nValues(), np);
    else
      if (ops->yHigh && ops->yLow)
	nn = MIN3(ops->yHigh->nValues(), ops->yLow->nValues(), np);
  }

  if (nn) {
    Segment2d* errorBars = new Segment2d[nn * 3];
    Segment2d* segPtr = errorBars;
    int* errorToData = new int[nn * 3];
    int* indexPtr = errorToData;

    for (int ii=0; ii<nn; ii++) {
      double x = ops->coords.x->values_[ii];
      double y = ops->coords.y->values_[ii];
      LineStyle* stylePtr = styleMap[ii];

      if ((isfinite(x)) && (isfinite(y))) {
	double high;
	double low;
 	if (ops->yError->nValues() > 0) {
 	  high = y + ops->yError->values_[ii];
 	  low = y - ops->yError->values_[ii];
 	} 
	else {
 	  high = ops->yHigh->values_[ii];
 	  low = ops->yLow->values_[ii];
 	}

	if ((isfinite(high)) && (isfinite(low)))  {
	  Point2d p = graphPtr_->map2D(x, high, ops->xAxis, ops->yAxis);
	  Point2d q = graphPtr_->map2D(x, low, ops->xAxis, ops->yAxis);
	  segPtr->p = p;
	  segPtr->q = q;
	  if (lineRectClip(&exts, &segPtr->p, &segPtr->q)) {
	    segPtr++;
	    *indexPtr++ = ii;
	  }
	  // Top cap
	  segPtr->p.y = p.y;
	  segPtr->q.y = p.y;
	  segPtr->p.x = p.x - stylePtr->errorBarCapWidth;
	  segPtr->q.x = p.x + stylePtr->errorBarCapWidth;
	  if (lineRectClip(&exts, &segPtr->p, &segPtr->q)) {
	    segPtr++;
	    *indexPtr++ = ii;
	  }
	  // Bottom cap
	  segPtr->p.y = q.y;
	  segPtr->q.y = q.y;
	  segPtr->p.x = q.x - stylePtr->errorBarCapWidth;
	  segPtr->q.x = q.x + stylePtr->errorBarCapWidth;
	  if (lineRectClip(&exts, &segPtr->p, &segPtr->q)) {
	    segPtr++;
	    *indexPtr++ = ii;
	  }
	}
      }
    }
    yeb_.segments = errorBars;
    yeb_.length = segPtr - errorBars;
    yeb_.map = errorToData;
  }
}

int LineElement::closestTrace()
{
  LineElementOptions* ops = (LineElementOptions*)ops_;
  GraphOptions* gops = (GraphOptions*)graphPtr_->ops_;
  ClosestSearch* searchPtr = &gops->search;

  Point2d closest;

  int iClose = -1;
  double dMin = searchPtr->dist;
  closest.x = closest.y = 0;
  for (ChainLink *link=Chain_FirstLink(traces_); link; 
       link = Chain_NextLink(link)) {
    bltTrace *tracePtr = (bltTrace*)Chain_GetValue(link);
    for (Point2d *p=tracePtr->screenPts.points, 
	   *pend=p + (tracePtr->screenPts.length - 1); p<pend; p++) {
      Point2d b;
      double d;
      if (searchPtr->along == SEARCH_X)
	d = distanceToX(searchPtr->x, searchPtr->y, p, p + 1, &b);
      else if (searchPtr->along == SEARCH_Y)
	d = distanceToY(searchPtr->x, searchPtr->y, p, p + 1, &b);
      else
	d = distanceToLine(searchPtr->x, searchPtr->y, p, p + 1, &b);

      if (d < dMin) {
	closest = b;
	iClose = tracePtr->screenPts.map[p-tracePtr->screenPts.points];
	dMin = d;
      }
    }
  }
  if (dMin < searchPtr->dist) {
    searchPtr->dist = dMin;
    searchPtr->elemPtr = (Element*)this;
    searchPtr->index = iClose;
    searchPtr->point = graphPtr_->invMap2D(closest.x, closest.y, 
					   ops->xAxis, ops->yAxis);
    return 1;
  }

  return 0;
}

void LineElement::closestPoint(ClosestSearch *searchPtr)
{
  LineElementOptions* ops = (LineElementOptions*)ops_;

  double dMin = searchPtr->dist;
  int iClose = 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.
  int count =0;
  for (Point2d *pp = symbolPts_.points; count < symbolPts_.length;
       count++, pp++) {
    double dx = (double)(searchPtr->x - pp->x);
    double dy = (double)(searchPtr->y - pp->y);
    double d;
    if (searchPtr->along == SEARCH_BOTH)
      d = hypot(dx, dy);
    else if (searchPtr->along == SEARCH_X)
      d = dx;
    else if (searchPtr->along == SEARCH_Y)
      d = dy;
    else
      continue;

    if (d < dMin) {
      iClose = symbolPts_.map[count];
      dMin = d;
    }
  }
  if (dMin < searchPtr->dist) {
    searchPtr->elemPtr = (Element*)this;
    searchPtr->dist = dMin;
    searchPtr->index = iClose;
    searchPtr->point.x = ops->coords.x->values_[iClose];
    searchPtr->point.y = ops->coords.y->values_[iClose];
  }
}

void LineElement::drawCircle(Display *display, Drawable drawable, 
			     LinePen* penPtr, 
			     int nSymbolPts, Point2d *symbolPts, int radius)
{
  LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();

  int count = 0;
  int s = radius + radius;
  XArc* arcs = new XArc[nSymbolPts];
  XArc *ap = arcs;
  for (Point2d *pp=symbolPts, *pend=pp+nSymbolPts; pp<pend; pp++) {
    if (DRAW_SYMBOL()) {
      ap->x = (short)(pp->x - radius);
      ap->y = (short)(pp->y - radius);
      ap->width = (short)s;
      ap->height = (short)s;
      ap->angle1 = 0;
      ap->angle2 = 23040;
      ap++;
      count++;
    }
    symbolCounter_++;
  }

  for (XArc *ap=arcs, *aend=ap+count; ap<aend; ap++) {
    if (penOps->symbol.fillGC)
      XFillArc(display, drawable, penOps->symbol.fillGC, 
	       ap->x, ap->y, ap->width, ap->height, ap->angle1, ap->angle2);

    if (penOps->symbol.outlineWidth > 0)
      XDrawArc(display, drawable, penOps->symbol.outlineGC,
	       ap->x, ap->y, ap->width, ap->height, ap->angle1, ap->angle2);
  }

  delete [] arcs;
}

void LineElement::drawSquare(Display *display, Drawable drawable, 
			     LinePen* penPtr, 
			     int nSymbolPts, Point2d *symbolPts, int r)
{
  LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();

  int s = r + r;
  int count =0;
  Rectangle* rectangles = new Rectangle[nSymbolPts];
  Rectangle* rp=rectangles;
  for (Point2d *pp=symbolPts, *pend=pp+nSymbolPts; pp<pend; pp++) {
    if (DRAW_SYMBOL()) {
      rp->x = (int)pp->x - r;
      rp->y = (int)pp->y - r;
      rp->width = s;
      rp->height = s;
      rp++;
      count++;
    }
    symbolCounter_++;
  }

  for (Rectangle *rp=rectangles, *rend=rp+count; rp<rend; rp ++) {
    if (penOps->symbol.fillGC)
      XFillRectangle(display, drawable, penOps->symbol.fillGC,
		     rp->x, rp->y, rp->width, rp->height);

    if (penOps->symbol.outlineWidth > 0)
      XDrawRectangle(display, drawable, penOps->symbol.outlineGC,
		     rp->x, rp->y, rp->width, rp->height);
  }

  delete [] rectangles;
}

void LineElement::drawSCross(Display* display, Drawable drawable, 
			     LinePen* penPtr, 
			     int nSymbolPts, Point2d* symbolPts, int r2)
{
  LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();

  Point pattern[4];
  if (penOps->symbol.type == SYMBOL_SCROSS) {
    r2 = (int)(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;
  }

  for (Point2d *pp=symbolPts, *endp=pp+nSymbolPts; pp<endp; pp++) {
    if (DRAW_SYMBOL()) {
      int rndx = (int)pp->x;
      int rndy = (int)pp->y;
      XDrawLine(graphPtr_->display_, drawable, penOps->symbol.outlineGC,
		pattern[0].x + rndx, pattern[0].y + rndy,
		pattern[1].x + rndx, pattern[1].y + rndy);
      XDrawLine(graphPtr_->display_, drawable, penOps->symbol.outlineGC,
		pattern[2].x + rndx, pattern[2].y + rndy,
		pattern[3].x + rndx, pattern[3].y + rndy);
    }
  }
}

void LineElement::drawCross(Display *display, Drawable drawable, 
			    LinePen* penPtr, 
			    int nSymbolPts, Point2d *symbolPts, int r2)
{
  LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();

  /*
   *          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
   */
  int d = (r2 / 3);
  Point pattern[13];
  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 (penOps->symbol.type == SYMBOL_CROSS) {
    // For the cross symbol, rotate the points by 45 degrees
    for (int ii=0; ii<12; ii++) {
      double dx = (double)pattern[ii].x * M_SQRT1_2;
      double dy = (double)pattern[ii].y * M_SQRT1_2;
      pattern[ii].x = (int)(dx - dy);
      pattern[ii].y = (int)(dx + dy);
    }
    pattern[12] = pattern[0];
  }

  int count = 0;
  XPoint* polygon = new XPoint[nSymbolPts*13];
  XPoint* xpp = polygon;
  for (Point2d *pp = symbolPts, *endp = pp + nSymbolPts; pp < endp; pp++) {
    if (DRAW_SYMBOL()) {
      int rndx = (int)pp->x;
      int rndy = (int)pp->y;
      for (int ii=0; ii<13; ii++) {
	xpp->x = (short)(pattern[ii].x + rndx);
	xpp->y = (short)(pattern[ii].y + rndy);
	xpp++;
      }
      count++;
    }
    symbolCounter_++;
  }

  if (penOps->symbol.fillGC) {
    XPoint* xpp = polygon;
    for (int ii=0; ii<count; ii++, xpp += 13)
      XFillPolygon(graphPtr_->display_, drawable, 
		   penOps->symbol.fillGC, xpp, 13, Complex, 
		   CoordModeOrigin);
  }

  if (penOps->symbol.outlineWidth > 0) {
    XPoint*xpp = polygon;
    for (int ii=0; ii<count; ii++, xpp += 13)
      XDrawLines(graphPtr_->display_, drawable, 
		 penOps->symbol.outlineGC, xpp, 13, CoordModeOrigin);
  }

  delete [] polygon;
}

void LineElement::drawDiamond(Display *display, Drawable drawable, 
			      LinePen* penPtr, 
			      int nSymbolPts, Point2d *symbolPts, int r1)
{
  LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();

  /*
   *                      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.
   */
  Point pattern[5];
  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];

  int count = 0;
  XPoint* polygon = new XPoint[nSymbolPts*5];
  XPoint* xpp = polygon;
  for (Point2d *pp = symbolPts, *endp = pp + nSymbolPts; pp < endp; pp++) {
    if (DRAW_SYMBOL()) {
      int rndx = (int)pp->x;
      int rndy = (int)pp->y;
      for (int ii=0; ii<5; ii++) {
	xpp->x = (short)(pattern[ii].x + rndx);
	xpp->y = (short)(pattern[ii].y + rndy);
	xpp++;
      }
      count++;
    }
    symbolCounter_++;
  }

  if (penOps->symbol.fillGC) {
    XPoint* xpp = polygon;
    for (int ii=0; ii<count; ii++, xpp += 5)
      XFillPolygon(graphPtr_->display_, drawable, 
		   penOps->symbol.fillGC, xpp, 5, Convex, CoordModeOrigin);
  }

  if (penOps->symbol.outlineWidth > 0) {
    XPoint* xpp = polygon;
    for (int ii=0; ii<count; ii++, xpp += 5)
      XDrawLines(graphPtr_->display_, drawable, 
		 penOps->symbol.outlineGC, xpp, 5, CoordModeOrigin);
  }

  delete [] polygon;
}

#define B_RATIO		1.3467736870885982
#define TAN30		0.57735026918962573
#define COS30		0.86602540378443871
void LineElement::drawArrow(Display *display, Drawable drawable, 
			    LinePen* penPtr, 
			    int nSymbolPts, Point2d *symbolPts, int size)
{
  LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();

  double b = size * B_RATIO * 0.7 * 0.5;
  short b2 = (short)b;
  short h2 = (short)(TAN30 * b);
  short h1 = (short)(b / 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.
   */

  Point pattern[4];
  if (penOps->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;
  }

  int count = 0;
  XPoint* polygon = new XPoint[nSymbolPts*4];
  XPoint* xpp = polygon;
  for (Point2d *pp = symbolPts, *endp = pp + nSymbolPts; pp < endp; pp++) {
    if (DRAW_SYMBOL()) {
      int rndx = (int)pp->x;
      int rndy = (int)pp->y;
      for (int ii=0; ii<4; ii++) {
	xpp->x = (short)(pattern[ii].x + rndx);
	xpp->y = (short)(pattern[ii].y + rndy);
	xpp++;
      }
      count++;
    }
    symbolCounter_++;
  }

  if (penOps->symbol.fillGC) {
    XPoint* xpp = polygon;
    for (int ii=0; ii<count; ii++, xpp += 4)
      XFillPolygon(graphPtr_->display_, drawable, 
		   penOps->symbol.fillGC, xpp, 4, Convex, CoordModeOrigin);
  }

  if (penOps->symbol.outlineWidth > 0) {
    XPoint* xpp = polygon;
    for (int ii=0; ii<count; ii++, xpp += 4)
      XDrawLines(graphPtr_->display_, drawable, 
		 penOps->symbol.outlineGC, xpp, 4, CoordModeOrigin);
  }

  delete [] polygon;
}

#define S_RATIO		0.886226925452758
void LineElement::drawSymbols(Drawable drawable, LinePen* penPtr, int size,
			      int nSymbolPts, Point2d* symbolPts)
{
  LinePenOptions* penOps = (LinePenOptions*)penPtr->ops();

  if (size < 3) {
    if (penOps->symbol.fillGC) {
      for (Point2d *pp = symbolPts, *endp = pp + nSymbolPts; pp < endp; pp++)
	XDrawLine(graphPtr_->display_, drawable, penOps->symbol.fillGC, 
		  (int)pp->x, (int)pp->y, (int)pp->x+1, (int)pp->y+1);
    }
    return;
  }

  int r1 = (int)ceil(size * 0.5);
  int r2 = (int)ceil(size * S_RATIO * 0.5);

  switch (penOps->symbol.type) {
  case SYMBOL_NONE:
    break;
  case SYMBOL_SQUARE:
    drawSquare(graphPtr_->display_, drawable, penPtr, nSymbolPts,symbolPts,r2);
    break;
  case SYMBOL_CIRCLE:
    drawCircle(graphPtr_->display_, drawable, penPtr, nSymbolPts,symbolPts,r1);
    break;
  case SYMBOL_SPLUS:
  case SYMBOL_SCROSS:
    drawSCross(graphPtr_->display_, drawable, penPtr, nSymbolPts,symbolPts,r2);
    break;
  case SYMBOL_PLUS:
  case SYMBOL_CROSS:
    drawCross(graphPtr_->display_, drawable, penPtr, nSymbolPts,symbolPts,r2);
    break;
  case SYMBOL_DIAMOND:
    drawDiamond(graphPtr_->display_, drawable, penPtr, nSymbolPts,symbolPts,r1);
    break;
  case SYMBOL_TRIANGLE:
  case SYMBOL_ARROW:
    drawArrow(graphPtr_->display_, drawable, penPtr, nSymbolPts,symbolPts,size);
    break;
  }
}

void LineElement::drawTraces(Drawable drawable, LinePen* penPtr)
{
  for (ChainLink* link = Chain_FirstLink(traces_); link;
       link = Chain_NextLink(link)) {
    bltTrace* tracePtr = (bltTrace*)Chain_GetValue(link);

    int count = tracePtr->screenPts.length; 
    XPoint* points = new XPoint[count];
    XPoint*xpp = points;
    for (int ii=0; ii<count; ii++, xpp++) {
      xpp->x = (short)tracePtr->screenPts.points[ii].x;
      xpp->y = (short)tracePtr->screenPts.points[ii].y;
    }
    XDrawLines(graphPtr_->display_, drawable, penPtr->traceGC_, points, 
	       count, CoordModeOrigin);
    delete [] points;
  }
}

void LineElement::drawValues(Drawable drawable, LinePen* penPtr, 
			     int length, Point2d *points, int *map)
{
  LineElementOptions* ops = (LineElementOptions*)ops_;
  LinePenOptions* pops = (LinePenOptions*)penPtr->ops();

  char string[TCL_DOUBLE_SPACE * 2 + 2];
  const char* fmt = pops->valueFormat;
  if (fmt == NULL)
    fmt = "%g";
  TextStyle ts(graphPtr_, &pops->valueStyle);

  double* xval = ops->coords.x->values_;
  double* yval = ops->coords.y->values_;
  int count = 0;

  for (Point2d *pp = points, *endp = points + length; pp < endp; pp++) {
    double x = xval[map[count]];
    double y = yval[map[count]];
    count++;
    if (pops->valueShow == SHOW_X)
      snprintf(string, TCL_DOUBLE_SPACE, fmt, x); 
    else if (pops->valueShow == SHOW_Y)
      snprintf(string, TCL_DOUBLE_SPACE, fmt, y); 
    else if (pops->valueShow == SHOW_BOTH) {
      snprintf(string, TCL_DOUBLE_SPACE, fmt, x);
      strcat(string, ",");
      snprintf(string + strlen(string), TCL_DOUBLE_SPACE, fmt, y);
    }

    ts.drawText(drawable, string, pp->x, pp->y);
  } 
}

void LineElement::printSymbols(PSOutput* psPtr, LinePen* penPtr, int size,
			       int nSymbolPts, Point2d *symbolPts)
{
  LinePenOptions* pops = (LinePenOptions*)penPtr->ops();

  double symbolSize;

  // Set line and foreground attributes
  XColor* fillColor = pops->symbol.fillColor;
  if (!fillColor)
    fillColor = pops->traceColor;

  XColor* outlineColor = pops->symbol.outlineColor;
  if (!outlineColor)
    outlineColor = pops->traceColor;

  if (pops->symbol.type == SYMBOL_NONE)
    psPtr->setLineAttributes(pops->traceColor, pops->traceWidth + 2,
			     &pops->traceDashes, CapButt, JoinMiter);
  else {
    psPtr->setLineWidth(pops->symbol.outlineWidth);
    psPtr->setDashes(NULL);
  }

  // build DrawSymbolProc
  psPtr->append("\n/DrawSymbolProc {\n");
  switch (pops->symbol.type) {
  case SYMBOL_NONE:
    break;
  default:
    psPtr->append("  ");
    psPtr->setBackground(fillColor);
    psPtr->append("  gsave fill grestore\n");

    if (pops->symbol.outlineWidth > 0) {
      psPtr->append("  ");
      psPtr->setForeground(outlineColor);
      psPtr->append("  stroke\n");
    }
    break;
  }
  psPtr->append("} def\n\n");

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

  default:
    break;
  }

  int count =0;
  for (Point2d *pp=symbolPts, *endp=symbolPts + nSymbolPts; pp < endp; pp++) {
    if (DRAW_SYMBOL()) {
      psPtr->format("%g %g %g %s\n", pp->x, pp->y, symbolSize, 
		    symbolMacros[pops->symbol.type]);
      count++;
    }
    symbolCounter_++;
  }
}

void LineElement::setLineAttributes(PSOutput* psPtr, LinePen* penPtr)
{
  LinePenOptions* pops = (LinePenOptions*)penPtr->ops();

  psPtr->setLineAttributes(pops->traceColor, pops->traceWidth, 
			   &pops->traceDashes, CapButt, JoinMiter);

  if ((LineIsDashed(pops->traceDashes)) && 
      (pops->traceOffColor)) {
    psPtr->append("/DashesProc {\n  gsave\n    ");
    psPtr->setBackground(pops->traceOffColor);
    psPtr->append("    ");
    psPtr->setDashes(NULL);
    psPtr->append("stroke\n  grestore\n} def\n");
  } else {
    psPtr->append("/DashesProc {} def\n");
  }
}

void LineElement::printTraces(PSOutput* psPtr, LinePen* penPtr)
{
  setLineAttributes(psPtr, penPtr);
  for (ChainLink* link = Chain_FirstLink(traces_); link; 
       link = Chain_NextLink(link)) {
    bltTrace *tracePtr = (bltTrace*)Chain_GetValue(link);
    if (tracePtr->screenPts.length > 0) {
      psPtr->append("% start trace\n");
      psPtr->printMaxPolyline(tracePtr->screenPts.points, 
			     tracePtr->screenPts.length);
      psPtr->append("% end trace\n");
    }
  }
}

void LineElement::printValues(PSOutput* psPtr, LinePen* penPtr, 
			      int nSymbolPts, Point2d *symbolPts, 
			      int *pointToData)
{
  LineElementOptions* ops = (LineElementOptions*)ops_;
  LinePenOptions* pops = (LinePenOptions*)penPtr->ops();

  const char* fmt = pops->valueFormat;
  if (fmt == NULL)
    fmt = "%g";
  TextStyle ts(graphPtr_, &pops->valueStyle);

  int count = 0;
  for (Point2d *pp=symbolPts, *endp=symbolPts + nSymbolPts; pp < endp; pp++) {
    double x = ops->coords.x->values_[pointToData[count]];
    double y = ops->coords.y->values_[pointToData[count]];
    count++;

    char string[TCL_DOUBLE_SPACE * 2 + 2];
    if (pops->valueShow == SHOW_X)
      snprintf(string, TCL_DOUBLE_SPACE, fmt, x); 
    else if (pops->valueShow == SHOW_Y)
      snprintf(string, TCL_DOUBLE_SPACE, fmt, y); 
    else if (pops->valueShow == SHOW_BOTH) {
      snprintf(string, TCL_DOUBLE_SPACE, fmt, x);
      strcat(string, ",");
      snprintf(string + strlen(string), TCL_DOUBLE_SPACE, fmt, y);
    }

    ts.printText(psPtr, string, pp->x, pp->y);
  } 
}