diff options
Diffstat (limited to 'tkblt/src/tkbltGrElemLine.C')
-rw-r--r-- | tkblt/src/tkbltGrElemLine.C | 2486 |
1 files changed, 2486 insertions, 0 deletions
diff --git a/tkblt/src/tkbltGrElemLine.C b/tkblt/src/tkbltGrElemLine.C new file mode 100644 index 0000000..8da4279 --- /dev/null +++ b/tkblt/src/tkbltGrElemLine.C @@ -0,0 +1,2486 @@ +/* + * 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" + +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_; + + if (builtinPenPtr) + delete builtinPenPtr; + + reset(); + + if (ops->stylePalette) { + freeStylePalette(ops->stylePalette); + delete ops->stylePalette; + } + + if (fillPts_) + 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 = pp->x; + points[count].y = 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 = 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. + int maxSize = (int)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_; + + if (activePts_.points) { + delete [] activePts_.points; + activePts_.points = NULL; + } + if (activePts_.map) { + 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; + } + + if (symbolPts_.points) { + delete [] symbolPts_.points; + symbolPts_.points = NULL; + } + + if (symbolPts_.map) + delete [] symbolPts_.map; + symbolPts_.map = NULL; + symbolPts_.length = 0; + + if (activePts_.points) + delete [] activePts_.points; + activePts_.points = NULL; + activePts_.length = 0; + + if (activePts_.map) + delete [] activePts_.map; + activePts_.map = NULL; + + if (xeb_.segments) + delete [] xeb_.segments; + xeb_.segments = NULL; + if (xeb_.map) + delete [] xeb_.map; + xeb_.map = NULL; + xeb_.length = 0; + + if (yeb_.segments) + delete [] yeb_.segments; + yeb_.segments = NULL; + if (yeb_.map) + 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 = pp->x - radius; + ap->y = pp->y - radius; + ap->width = (unsigned short)s; + ap->height = (unsigned 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; + XRectangle* rectangles = new XRectangle[nSymbolPts]; + XRectangle* rp=rectangles; + for (Point2d *pp=symbolPts, *pend=pp+nSymbolPts; pp<pend; pp++) { + if (DRAW_SYMBOL()) { + rp->x = pp->x - r; + rp->y = pp->y - r; + rp->width = (unsigned short)s; + rp->height = (unsigned short)s; + rp++; + count++; + } + symbolCounter_++; + } + + for (XRectangle *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(); + + XPoint pattern[4]; + if (penOps->symbol.type == SYMBOL_SCROSS) { + r2 = (double)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 = pp->x; + int rndy = 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); + XPoint 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 = dx - dy; + pattern[ii].y = 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 = pp->x; + int rndy = pp->y; + for (int ii=0; ii<13; ii++) { + xpp->x = pattern[ii].x + rndx; + xpp->y = 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. + */ + XPoint 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 = pp->x; + int rndy = pp->y; + for (int ii=0; ii<5; ii++) { + xpp->x = pattern[ii].x + rndx; + xpp->y = 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; + int b2 = b * 0.5; + int h2 = TAN30 * b2; + int h1 = 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. + */ + + XPoint 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 = pp->x; + int rndy = pp->y; + for (int ii=0; ii<4; ii++) { + xpp->x = pattern[ii].x + rndx; + xpp->y = 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, + pp->x, pp->y, pp->x+1, 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 = tracePtr->screenPts.points[ii].x; + xpp->y = 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); + } +} + + |