diff options
Diffstat (limited to 'tkblt/generic/tkbltGrElemBar.C')
-rw-r--r-- | tkblt/generic/tkbltGrElemBar.C | 1315 |
1 files changed, 1315 insertions, 0 deletions
diff --git a/tkblt/generic/tkbltGrElemBar.C b/tkblt/generic/tkbltGrElemBar.C new file mode 100644 index 0000000..6698760 --- /dev/null +++ b/tkblt/generic/tkbltGrElemBar.C @@ -0,0 +1,1315 @@ +/* + * 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 1993-2004 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 "tkbltGraphBar.h" +#include "tkbltGrElemBar.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 CLAMP(x,l,h) ((x) = (((x)<(l))? (l) : ((x)>(h)) ? (h) : (x))) +#define MIN3(a,b,c) (((a)<(b))?(((a)<(c))?(a):(c)):(((b)<(c))?(b):(c))) + +#define PointInRectangle(r,x0,y0) \ + (((x0) <= (int)((r)->x + (r)->width - 1)) && ((x0) >= (int)(r)->x) && \ + ((y0) <= (int)((r)->y + (r)->height - 1)) && ((y0) >= (int)(r)->y)) + +// OptionSpecs + +static Tk_ObjCustomOption styleObjOption = + { + "style", StyleSetProc, StyleGetProc, StyleRestoreProc, StyleFreeProc, + (ClientData)sizeof(BarStyle) + }; + +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(BarElementOptions, activePenPtr), + TK_OPTION_NULL_OK, &penObjOption, LAYOUT}, + {TK_OPTION_SYNONYM, "-background", NULL, NULL, + NULL, 0, -1, 0, (ClientData)"-color", 0}, + {TK_OPTION_DOUBLE, "-barwidth", "barWidth", "BarWidth", + "0", -1, Tk_Offset(BarElementOptions, barWidth), 0, NULL, LAYOUT}, + {TK_OPTION_SYNONYM, "-bd", NULL, NULL, + NULL, 0, -1, 0, (ClientData)"-borderwidth", 0}, + {TK_OPTION_SYNONYM, "-bg", NULL, NULL, + NULL, 0, -1, 0, (ClientData)"-color", 0}, + {TK_OPTION_CUSTOM, "-bindtags", "bindTags", "BindTags", + "all", -1, Tk_Offset(BarElementOptions, tags), + TK_OPTION_NULL_OK, &listObjOption, 0}, + {TK_OPTION_PIXELS, "-borderwidth", "borderWidth", "BorderWidth", + STD_BORDERWIDTH, -1, Tk_Offset(BarElementOptions, builtinPen.borderWidth), + 0, NULL, CACHE}, + {TK_OPTION_BORDER, "-color", "color", "color", + STD_NORMAL_FOREGROUND, -1, Tk_Offset(BarElementOptions, builtinPen.fill), + 0, NULL, CACHE}, + {TK_OPTION_CUSTOM, "-data", "data", "Data", + NULL, -1, Tk_Offset(BarElementOptions, coords), + TK_OPTION_NULL_OK, &pairsObjOption, RESET}, + {TK_OPTION_COLOR, "-errorbarcolor", "errorBarColor", "ErrorBarColor", + NULL, -1, Tk_Offset(BarElementOptions, builtinPen.errorBarColor), + TK_OPTION_NULL_OK, NULL, CACHE}, + {TK_OPTION_PIXELS,"-errorbarwidth", "errorBarWidth", "ErrorBarWidth", + "1", -1, Tk_Offset(BarElementOptions, builtinPen.errorBarLineWidth), + 0, NULL, CACHE}, + {TK_OPTION_PIXELS, "-errorbarcap", "errorBarCap", "ErrorBarCap", + "0", -1, Tk_Offset(BarElementOptions, builtinPen.errorBarCapWidth), + 0, NULL, LAYOUT}, + {TK_OPTION_SYNONYM, "-fg", NULL, NULL, + NULL, 0, -1, 0, (ClientData)"-outline", 0}, + {TK_OPTION_SYNONYM, "-fill", NULL, NULL, + NULL, 0, -1, 0, (ClientData)"-color", 0}, + {TK_OPTION_SYNONYM, "-foreground", NULL, NULL, + NULL, 0, -1, 0, (ClientData)"-outline", 0}, + {TK_OPTION_BOOLEAN, "-hide", "hide", "Hide", + "no", -1, Tk_Offset(BarElementOptions, hide), 0, NULL, LAYOUT}, + {TK_OPTION_STRING, "-label", "label", "Label", + NULL, -1, Tk_Offset(BarElementOptions, label), + TK_OPTION_NULL_OK, NULL, LAYOUT}, + {TK_OPTION_RELIEF, "-legendrelief", "legendRelief", "LegendRelief", + "flat", -1, Tk_Offset(BarElementOptions, legendRelief), 0, NULL, LAYOUT}, + {TK_OPTION_CUSTOM, "-mapx", "mapX", "MapX", + "x", -1, Tk_Offset(BarElementOptions, xAxis), 0, &xAxisObjOption, RESET}, + {TK_OPTION_CUSTOM, "-mapy", "mapY", "MapY", + "y", -1, Tk_Offset(BarElementOptions, yAxis), 0, &yAxisObjOption, RESET}, + {TK_OPTION_COLOR, "-outline", "outline", "Outline", + NULL, -1, Tk_Offset(BarElementOptions, builtinPen.outlineColor), + TK_OPTION_NULL_OK, NULL, CACHE}, + {TK_OPTION_CUSTOM, "-pen", "pen", "Pen", + NULL, -1, Tk_Offset(BarElementOptions, normalPenPtr), + TK_OPTION_NULL_OK, &penObjOption, LAYOUT}, + {TK_OPTION_RELIEF, "-relief", "relief", "Relief", + "raised", -1, Tk_Offset(BarElementOptions, builtinPen.relief), + 0, NULL, LAYOUT}, + {TK_OPTION_STRING_TABLE, "-showerrorbars", "showErrorBars", "ShowErrorBars", + "both", -1, Tk_Offset(BarElementOptions, builtinPen.errorBarShow), + 0, &fillObjOption, LAYOUT}, + {TK_OPTION_STRING_TABLE, "-showvalues", "showValues", "ShowValues", + "none", -1, Tk_Offset(BarElementOptions, builtinPen.valueShow), + 0, &fillObjOption, CACHE}, + {TK_OPTION_STRING, "-stack", "stack", "Stack", + NULL, -1, Tk_Offset(BarElementOptions, groupName), + TK_OPTION_NULL_OK, NULL, RESET}, + {TK_OPTION_CUSTOM, "-styles", "styles", "Styles", + "", -1, Tk_Offset(BarElementOptions, stylePalette), + 0, &styleObjOption, RESET}, + {TK_OPTION_ANCHOR, "-valueanchor", "valueAnchor", "ValueAnchor", + "s", -1, Tk_Offset(BarElementOptions, builtinPen.valueStyle.anchor), + 0, NULL, CACHE}, + {TK_OPTION_COLOR, "-valuecolor", "valueColor", "ValueColor", + STD_NORMAL_FOREGROUND, -1, + Tk_Offset(BarElementOptions,builtinPen.valueStyle.color), 0, NULL, CACHE}, + {TK_OPTION_FONT, "-valuefont", "valueFont", "ValueFont", + STD_FONT_SMALL, -1, Tk_Offset(BarElementOptions, builtinPen.valueStyle.font), + 0, NULL, CACHE}, + {TK_OPTION_STRING, "-valueformat", "valueFormat", "ValueFormat", + "%g", -1, Tk_Offset(BarElementOptions, builtinPen.valueFormat), + TK_OPTION_NULL_OK, NULL, CACHE}, + {TK_OPTION_DOUBLE, "-valuerotate", "valueRotate", "ValueRotate", + "0", -1, Tk_Offset(BarElementOptions, builtinPen.valueStyle.angle), + 0, NULL, CACHE}, + {TK_OPTION_CUSTOM, "-weights", "weights", "Weights", + NULL, -1, Tk_Offset(BarElementOptions, 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(BarElementOptions, coords.x), + TK_OPTION_NULL_OK, &valuesObjOption, RESET}, + {TK_OPTION_CUSTOM, "-xerror", "xError", "XError", + NULL, -1, Tk_Offset(BarElementOptions, xError), + TK_OPTION_NULL_OK, &valuesObjOption, RESET}, + {TK_OPTION_CUSTOM, "-xhigh", "xHigh", "XHigh", + NULL, -1, Tk_Offset(BarElementOptions, xHigh), + TK_OPTION_NULL_OK, &valuesObjOption, RESET}, + {TK_OPTION_CUSTOM, "-xlow", "xLow", "XLow", + NULL, -1, Tk_Offset(BarElementOptions, 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(BarElementOptions, coords.y), + TK_OPTION_NULL_OK, &valuesObjOption, RESET}, + {TK_OPTION_CUSTOM, "-yerror", "yError", "YError", + NULL, -1, Tk_Offset(BarElementOptions, yError), + TK_OPTION_NULL_OK, &valuesObjOption, RESET}, + {TK_OPTION_CUSTOM, "-yhigh", "yHigh", "YHigh", + NULL, -1, Tk_Offset(BarElementOptions, yHigh), + TK_OPTION_NULL_OK, &valuesObjOption, RESET}, + {TK_OPTION_CUSTOM, "-ylow", "yLow", "YLow", + NULL, -1, Tk_Offset(BarElementOptions, yLow), + TK_OPTION_NULL_OK, &valuesObjOption, RESET}, + {TK_OPTION_END, NULL, NULL, NULL, NULL, 0, -1, 0, 0, 0} +}; + +BarElement::BarElement(Graph* graphPtr, const char* name, Tcl_HashEntry* hPtr) + : Element(graphPtr, name, hPtr) +{ + barToData_ =NULL; + bars_ =NULL; + activeToData_ =NULL; + activeRects_ =NULL; + nBars_ =0; + nActive_ =0; + + xeb_.segments =NULL; + xeb_.map =NULL; + xeb_.length =0; + yeb_.segments =NULL; + yeb_.map =NULL; + yeb_.length =0; + + ops_ = (BarElementOptions*)calloc(1, sizeof(BarElementOptions)); + BarElementOptions* ops = (BarElementOptions*)ops_; + ops->elemPtr = (Element*)this; + + builtinPenPtr = new BarPen(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_); +} + +BarElement::~BarElement() +{ + BarElementOptions* ops = (BarElementOptions*)ops_; + + delete builtinPenPtr; + + reset(); + + if (ops->stylePalette) { + freeStylePalette(ops->stylePalette); + delete ops->stylePalette; + } +} + +int BarElement::configure() +{ + BarElementOptions* ops = (BarElementOptions*)ops_; + + if (builtinPenPtr->configure() != TCL_OK) + return TCL_ERROR; + + // Point to the static normal pen if no external pens have been selected. + ChainLink* link = Chain_FirstLink(ops->stylePalette); + if (!link) { + link = new ChainLink(sizeof(BarStyle)); + ops->stylePalette->linkAfter(link, NULL); + } + BarStyle* stylePtr = (BarStyle*)Chain_GetValue(link); + stylePtr->penPtr = NORMALPEN(ops); + + return TCL_OK; +} + +void BarElement::map() +{ + BarGraph* barGraphPtr_ = (BarGraph*)graphPtr_; + BarElementOptions* ops = (BarElementOptions*)ops_; + BarGraphOptions* gops = (BarGraphOptions*)graphPtr_->ops_; + + if (!link) + return; + + reset(); + if (!ops->coords.x || !ops->coords.y || + !ops->coords.x->nValues() || !ops->coords.y->nValues()) + return; + int nPoints = NUMBEROFPOINTS(ops); + + double barWidth = (ops->barWidth > 0.0) ? ops->barWidth : gops->barWidth; + AxisOptions* axisyops = (AxisOptions*)ops->yAxis->ops(); + double baseline = (axisyops->logScale) ? 0.0 : gops->baseline; + double barOffset = barWidth * 0.5; + + // Create an array of bars representing the screen coordinates of all the + // segments in the bar. + Rectangle* bars = new Rectangle[nPoints]; + int* barToData = new int[nPoints]; + + double* x = ops->coords.x->values_; + double* y = ops->coords.y->values_; + int count = 0; + + int ii; + Rectangle* rp; + for (rp=bars, ii=0; ii<nPoints; ii++) { + // Two opposite corners of the rectangle in graph coordinates + Point2d c1, c2; + + // check Abscissa is out of range of the x-axis + if (((x[ii] - barWidth) > ops->xAxis->axisRange_.max) || + ((x[ii] + barWidth) < ops->xAxis->axisRange_.min)) + continue; + + c1.x = x[ii] - barOffset; + c1.y = y[ii]; + c2.x = c1.x + barWidth; + c2.y = baseline; + + // If the mode is "aligned" or "stacked" we need to adjust the x or y + // coordinates of the two corners. + if ((barGraphPtr_->nBarGroups_ > 0) && + ((BarGraph::BarMode)gops->barMode != BarGraph::INFRONT) && + (!gops->stackAxes)) { + + BarSetKey key; + key.value =x[ii]; + key.xAxis =ops->xAxis; + key.yAxis =NULL; + Tcl_HashEntry* hPtr = + Tcl_FindHashEntry(&barGraphPtr_->setTable_, (char*)&key); + + if (hPtr) { + Tcl_HashTable *tablePtr = (Tcl_HashTable*)Tcl_GetHashValue(hPtr); + const char *name = (ops->groupName) ? ops->groupName:ops->yAxis->name_; + Tcl_HashEntry* hPtr2 = Tcl_FindHashEntry(tablePtr, name); + if (hPtr2) { + BarGroup* groupPtr = (BarGroup*)Tcl_GetHashValue(hPtr2); + double slice = barWidth / (double)barGraphPtr_->maxBarSetSize_; + double offset = (slice * groupPtr->index); + if (barGraphPtr_->maxBarSetSize_ > 1) { + offset += slice * 0.05; + slice *= 0.90; + } + + switch ((BarGraph::BarMode)gops->barMode) { + case BarGraph::STACKED: + groupPtr->count++; + c2.y = groupPtr->lastY; + c1.y += c2.y; + groupPtr->lastY = c1.y; + c1.x += offset; + c2.x = c1.x + slice; + break; + + case BarGraph::ALIGNED: + slice /= groupPtr->nSegments; + c1.x += offset + (slice * groupPtr->count); + c2.x = c1.x + slice; + groupPtr->count++; + break; + + case BarGraph::OVERLAP: + { + slice /= (groupPtr->nSegments + 1); + double width = slice + slice; + groupPtr->count++; + c1.x += offset + + (slice * (groupPtr->nSegments - groupPtr->count)); + c2.x = c1.x + width; + } + break; + + case BarGraph::INFRONT: + break; + } + } + } + } + + int invertBar = 0; + if (c1.y < c2.y) { + // Handle negative bar values by swapping ordinates + double temp = c1.y; + c1.y = c2.y; + c2.y = temp; + invertBar = 1; + } + + // Get the two corners of the bar segment and compute the rectangle + double ybot = c2.y; + c1 = graphPtr_->map2D(c1.x, c1.y, ops->xAxis, ops->yAxis); + c2 = graphPtr_->map2D(c2.x, c2.y, ops->xAxis, ops->yAxis); + if ((ybot == 0.0) && (axisyops->logScale)) + c2.y = graphPtr_->bottom_; + + if (c2.y < c1.y) { + double t = c1.y; + c1.y = c2.y; + c2.y = t; + } + + if (c2.x < c1.x) { + double t = c1.x; + c1.x = c2.x; + c2.x = t; + } + + if ((c1.x > graphPtr_->right_) || (c2.x < graphPtr_->left_) || + (c1.y > graphPtr_->bottom_) || (c2.y < graphPtr_->top_)) + continue; + + // Bound the bars horizontally by the width of the graph window + // Bound the bars vertically by the position of the axis. + double right =0; + double left =0; + double top =0; + double bottom =0; + if (gops->stackAxes) { + top = ops->yAxis->screenMin_; + bottom = ops->yAxis->screenMin_ + ops->yAxis->screenRange_; + left = graphPtr_->left_; + right = graphPtr_->right_; + } + else { + bottom = right = 10000; + // Shouldn't really have a call to Tk_Width or Tk_Height in + // mapping routine. We only want to clamp the bar segment to the + // size of the window if we're actually mapped onscreen + if (Tk_Height(graphPtr_->tkwin_) > 1) + bottom = Tk_Height(graphPtr_->tkwin_); + if (Tk_Width(graphPtr_->tkwin_) > 1) + right = Tk_Width(graphPtr_->tkwin_); + } + + CLAMP(c1.y, top, bottom); + CLAMP(c2.y, top, bottom); + CLAMP(c1.x, left, right); + CLAMP(c2.x, left, right); + double dx = fabs(c1.x - c2.x); + double dy = fabs(c1.y - c2.y); + if ((dx == 0) || (dy == 0)) + continue; + + int height = (int)dy; + int width = (int)dx; + if (invertBar) + rp->y = (int)MIN(c1.y, c2.y); + else + rp->y = (int)(MAX(c1.y, c2.y)) - height; + + rp->x = (int)MIN(c1.x, c2.x); + + rp->width = width + 1; + rp->width |= 0x1; + if (rp->width < 1) + rp->width = 1; + + rp->height = height + 1; + if (rp->height < 1) + rp->height = 1; + + // Save the data index corresponding to the rectangle + barToData[count] = ii; + count++; + rp++; + } + nBars_ = count; + bars_ = bars; + barToData_ = barToData; + if (nActiveIndices_ > 0) + mapActive(); + + int size = 20; + if (count > 0) + size = bars->width; + + // Set the symbol size of all the pen styles + for (ChainLink* link = Chain_FirstLink(ops->stylePalette); link; + link = Chain_NextLink(link)) { + BarStyle *stylePtr = (BarStyle*)Chain_GetValue(link); + BarPen* penPtr = stylePtr->penPtr; + BarPenOptions* pops = (BarPenOptions*)penPtr->ops(); + stylePtr->symbolSize = size; + stylePtr->errorBarCapWidth = pops->errorBarCapWidth; + } + + BarStyle** dataToStyle = (BarStyle**)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(dataToStyle); + } + + mergePens(dataToStyle); + delete [] dataToStyle; +} + +void BarElement::extents(Region2d *regPtr) +{ + BarGraph* barGraphPtr_ = (BarGraph*)graphPtr_; + BarElementOptions* ops = (BarElementOptions*)ops_; + BarGraphOptions* gops = (BarGraphOptions*)graphPtr_->ops_; + + regPtr->top = regPtr->left = DBL_MAX; + regPtr->bottom = regPtr->right = -DBL_MAX; + + if (!ops->coords.x || !ops->coords.y || + !ops->coords.x->nValues() || !ops->coords.y->nValues()) + return; + + int nPoints = NUMBEROFPOINTS(ops); + + double middle = 0.5; + regPtr->left = ops->coords.x->min() - middle; + regPtr->right = ops->coords.x->max() + middle; + + regPtr->top = ops->coords.y->min(); + regPtr->bottom = ops->coords.y->max(); + if (regPtr->bottom < gops->baseline) + regPtr->bottom = gops->baseline; + + // Handle stacked bar elements specially. + // If element is stacked, the sum of its ordinates may be outside the + // minimum/maximum limits of the element's data points. + if (((BarGraph::BarMode)gops->barMode == BarGraph::STACKED) && + (barGraphPtr_->nBarGroups_ > 0)) + checkStacks(ops->xAxis, ops->yAxis, ®Ptr->top, ®Ptr->bottom); + + // Warning: You get what you deserve if the x-axis is logScale + AxisOptions* axisxops = (AxisOptions*)ops->xAxis->ops(); + AxisOptions* axisyops = (AxisOptions*)ops->yAxis->ops(); + if (axisxops->logScale) + regPtr->left = FindElemValuesMinimum(ops->coords.x, DBL_MIN) + middle; + + // Fix y-min limits for barchart + if (axisyops->logScale) { + if ((regPtr->top <= 0.0) || (regPtr->top > 1.0)) + regPtr->top = 1.0; + } + else { + if (regPtr->top > 0.0) + regPtr->top = 0.0; + } + + // Correct the extents for error bars if they exist + if (ops->xError && (ops->xError->nValues() > 0)) { + nPoints = MIN(ops->xError->nValues(), nPoints); + for (int ii=0; ii<nPoints; ii++) { + double x = ops->coords.x->values_[ii] + ops->xError->values_[ii]; + if (x > regPtr->right) + regPtr->right = x; + + x = ops->coords.x->values_[ii] - ops->xError->values_[ii]; + if (axisxops->logScale) { + // Mirror negative values, instead of ignoring them + if (x < 0.0) + x = -x; + + if ((x > DBL_MIN) && (x < regPtr->left)) + regPtr->left = x; + + } + else if (x < regPtr->left) + regPtr->left = x; + } + } + else { + if ((ops->xHigh) && + (ops->xHigh->nValues() > 0) && + (ops->xHigh->max() > regPtr->right)) + regPtr->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 < regPtr->left) + regPtr->left = left; + } + } + + if (ops->yError && (ops->yError->nValues() > 0)) { + nPoints = MIN(ops->yError->nValues(), nPoints); + + for (int ii=0; ii<nPoints; ii++) { + double y = ops->coords.y->values_[ii] + ops->yError->values_[ii]; + if (y > regPtr->bottom) + regPtr->bottom = y; + + y = ops->coords.y->values_[ii] - ops->yError->values_[ii]; + if (axisyops->logScale) { + // Mirror negative values, instead of ignoring them + if (y < 0.0) + y = -y; + + if ((y > DBL_MIN) && (y < regPtr->left)) + regPtr->top = y; + + } + else if (y < regPtr->top) + regPtr->top = y; + } + } + else { + if ((ops->yHigh) && + (ops->yHigh->nValues() > 0) && + (ops->yHigh->max() > regPtr->bottom)) + regPtr->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 < regPtr->top) + regPtr->top = top; + } + } +} + +void BarElement::closest() +{ + BarElementOptions* ops = (BarElementOptions*)ops_; + BarGraphOptions* gops = (BarGraphOptions*)graphPtr_->ops_; + + ClosestSearch* searchPtr = &gops->search; + double minDist = searchPtr->dist; + int imin = 0; + + int ii; + Rectangle* bp; + for (bp=bars_, ii=0; ii<nBars_; ii++, bp++) { + if (PointInRectangle(bp, searchPtr->x, searchPtr->y)) { + imin = barToData_[ii]; + minDist = 0.0; + break; + } + double left = bp->x; + double top = bp->y; + double right = (double)(bp->x + bp->width); + double bottom = (double)(bp->y + bp->height); + + Point2d outline[5]; + outline[4].x = outline[3].x = outline[0].x = left; + outline[4].y = outline[1].y = outline[0].y = top; + outline[2].x = outline[1].x = right; + outline[3].y = outline[2].y = bottom; + + Point2d *pp, *pend; + for (pp=outline, pend=outline+4; pp<pend; pp++) { + Point2d t = getProjection(searchPtr->x, searchPtr->y, pp, pp + 1); + 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; + + double dist = hypot((t.x - searchPtr->x), (t.y - searchPtr->y)); + if (dist < minDist) { + minDist = dist; + imin = barToData_[ii]; + } + } + } + if (minDist < searchPtr->dist) { + searchPtr->elemPtr = (Element*)this; + searchPtr->dist = minDist; + searchPtr->index = imin; + searchPtr->point.x = + ops->coords.x ? (double)ops->coords.x->values_[imin] : 0; + searchPtr->point.y = + ops->coords.y ? (double)ops->coords.y->values_[imin] : 0; + } +} + +void BarElement::draw(Drawable drawable) +{ + BarElementOptions* ops = (BarElementOptions*)ops_; + + if (ops->hide) + return; + + int count = 0; + for (ChainLink* link = Chain_FirstLink(ops->stylePalette); link; + link = Chain_NextLink(link)) { + + BarStyle* stylePtr = (BarStyle*)Chain_GetValue(link); + BarPen* penPtr = (BarPen*)stylePtr->penPtr; + BarPenOptions* pops = (BarPenOptions*)penPtr->ops(); + + if (stylePtr->nBars > 0) + drawSegments(drawable, penPtr, stylePtr->bars, stylePtr->nBars); + + if ((stylePtr->xeb.length > 0) && (pops->errorBarShow & SHOW_X)) + graphPtr_->drawSegments(drawable, penPtr->errorBarGC_, + stylePtr->xeb.segments, stylePtr->xeb.length); + + if ((stylePtr->yeb.length > 0) && (pops->errorBarShow & SHOW_Y)) + graphPtr_->drawSegments(drawable, penPtr->errorBarGC_, + stylePtr->yeb.segments, stylePtr->yeb.length); + + if (pops->valueShow != SHOW_NONE) + drawValues(drawable, penPtr, stylePtr->bars, stylePtr->nBars, + barToData_ + count); + + count += stylePtr->nBars; + } +} + +void BarElement::drawActive(Drawable drawable) +{ + BarElementOptions* ops = (BarElementOptions*)ops_; + + if (ops->hide || !active_) + return; + + BarPen* penPtr = (BarPen*)ops->activePenPtr; + if (!penPtr) + return; + BarPenOptions* pops = (BarPenOptions*)penPtr->ops(); + + if (nActiveIndices_ > 0) { + mapActive(); + + drawSegments(drawable, penPtr, activeRects_, nActive_); + if (pops->valueShow != SHOW_NONE) + drawValues(drawable, penPtr, activeRects_, nActive_, activeToData_); + } + else if (nActiveIndices_ < 0) { + drawSegments(drawable, penPtr, bars_, nBars_); + if (pops->valueShow != SHOW_NONE) + drawValues(drawable, penPtr, bars_, nBars_, barToData_); + } +} + +void BarElement::drawSymbol(Drawable drawable, int x, int y, int size) +{ + BarElementOptions* ops = (BarElementOptions*)ops_; + + BarPen* penPtr = NORMALPEN(ops); + BarPenOptions* pops = (BarPenOptions*)penPtr->ops(); + + int radius = (size / 2); + size--; + + x -= radius; + y -= radius; + + Tk_Fill3DRectangle(graphPtr_->tkwin_, drawable, + pops->fill, x, y, size, size, + pops->borderWidth, pops->relief); + + if (pops->outlineColor) + XDrawRectangle(graphPtr_->display_, drawable, penPtr->outlineGC_, + x, y, size, size); +} + +void BarElement::print(PSOutput* psPtr) +{ + BarElementOptions* ops = (BarElementOptions*)ops_; + + if (ops->hide) + return; + + psPtr->format("\n%% Element \"%s\"\n\n", name_); + + int count = 0; + for (ChainLink* link = Chain_FirstLink(ops->stylePalette); link; + link = Chain_NextLink(link)) { + + BarStyle *stylePtr = (BarStyle*)Chain_GetValue(link); + BarPen* penPtr = (BarPen*)stylePtr->penPtr; + BarPenOptions* pops = (BarPenOptions*)penPtr->ops(); + + if (stylePtr->nBars > 0) + printSegments(psPtr, penPtr, stylePtr->bars, stylePtr->nBars); + + XColor* colorPtr = pops->errorBarColor; + if (!colorPtr) + colorPtr = pops->outlineColor; + if (!colorPtr) + colorPtr = Tk_3DBorderColor(pops->fill); + + if ((stylePtr->xeb.length > 0) && (pops->errorBarShow & SHOW_X)) { + psPtr->setLineAttributes(colorPtr, pops->errorBarLineWidth, + NULL, CapButt, JoinMiter); + psPtr->printSegments(stylePtr->xeb.segments, stylePtr->xeb.length); + } + + if ((stylePtr->yeb.length > 0) && (pops->errorBarShow & SHOW_Y)) { + psPtr->setLineAttributes(colorPtr, pops->errorBarLineWidth, + NULL, CapButt, JoinMiter); + psPtr->printSegments(stylePtr->yeb.segments, stylePtr->yeb.length); + } + + if (pops->valueShow != SHOW_NONE) + printValues(psPtr, penPtr, stylePtr->bars, stylePtr->nBars, + barToData_ + count); + + count += stylePtr->nBars; + } +} + +void BarElement::printActive(PSOutput* psPtr) +{ + BarElementOptions* ops = (BarElementOptions*)ops_; + + if (ops->hide || !active_) + return; + + BarPen* penPtr = (BarPen*)ops->activePenPtr; + if (!penPtr) + return; + BarPenOptions* pops = (BarPenOptions*)penPtr->ops(); + + psPtr->format("\n%% Active Element \"%s\"\n\n", name_); + + if (nActiveIndices_ > 0) { + mapActive(); + + printSegments(psPtr, penPtr, activeRects_, nActive_); + if (pops->valueShow != SHOW_NONE) + printValues(psPtr, penPtr, activeRects_, nActive_,activeToData_); + } + else if (nActiveIndices_ < 0) { + printSegments(psPtr, penPtr, bars_, nBars_); + if (pops->valueShow != SHOW_NONE) + printValues(psPtr, penPtr, bars_, nBars_, barToData_); + } +} + +void BarElement::printSymbol(PSOutput* psPtr, double x, double y, int size) +{ + BarElementOptions* ops = (BarElementOptions*)ops_; + + BarPen* penPtr = NORMALPEN(ops); + BarPenOptions* pops = (BarPenOptions*)penPtr->ops(); + + x -= size/2.; + y -= size/2.; + + psPtr->fill3DRectangle(pops->fill, x, y, size, size, + pops->borderWidth, pops->relief); + + if (pops->outlineColor) { + psPtr->setForeground(pops->outlineColor); + psPtr->printRectangle(x, y, size, size); + } +} + +// Support + +void BarElement::ResetStylePalette(Chain* stylePalette) +{ + for (ChainLink* link = Chain_FirstLink(stylePalette); link; + link = Chain_NextLink(link)) { + BarStyle *stylePtr = (BarStyle*)Chain_GetValue(link); + stylePtr->xeb.length = 0; + stylePtr->yeb.length = 0; + stylePtr->nBars = 0; + } +} + +void BarElement::checkStacks(Axis* xAxis, Axis* yAxis, + double* minPtr, double* maxPtr) +{ + BarGraph* barGraphPtr_ = (BarGraph*)graphPtr_; + BarGraphOptions* gops = (BarGraphOptions*)graphPtr_->ops_; + if (((BarGraph::BarMode)gops->barMode != BarGraph::STACKED) || + barGraphPtr_->nBarGroups_ == 0) + return; + + for (BarGroup *gp = barGraphPtr_->barGroups_, + *gend = gp + barGraphPtr_->nBarGroups_; gp < gend; gp++) { + if ((gp->xAxis == xAxis) && (gp->yAxis == yAxis)) { + + // Check if any of the y-values (because of stacking) are greater + // than the current limits of the graph. + if (gp->sum < 0.0) { + if (*minPtr > gp->sum) + *minPtr = gp->sum; + } + else { + if (*maxPtr < gp->sum) + *maxPtr = gp->sum; + } + } + } +} + +void BarElement::mergePens(BarStyle** dataToStyle) +{ + BarElementOptions* ops = (BarElementOptions*)ops_; + + if (Chain_GetLength(ops->stylePalette) < 2) { + ChainLink* link = Chain_FirstLink(ops->stylePalette); + BarStyle *stylePtr = (BarStyle*)Chain_GetValue(link); + stylePtr->nBars = nBars_; + stylePtr->bars = bars_; + stylePtr->symbolSize = bars_->width / 2; + stylePtr->xeb.length = xeb_.length; + stylePtr->xeb.segments = xeb_.segments; + stylePtr->yeb.length = yeb_.length; + stylePtr->yeb.segments = yeb_.segments; + return; + } + + // We have more than one style. Group bar segments of like pen styles together + if (nBars_ > 0) { + Rectangle* bars = new Rectangle[nBars_]; + int* barToData = new int[nBars_]; + Rectangle* bp = bars; + int* ip = barToData; + for (ChainLink* link = Chain_FirstLink(ops->stylePalette); link; + link = Chain_NextLink(link)) { + BarStyle *stylePtr = (BarStyle*)Chain_GetValue(link); + stylePtr->symbolSize = bp->width / 2; + stylePtr->bars = bp; + for (int ii=0; ii<nBars_; ii++) { + int iData = barToData[ii]; + if (dataToStyle[iData] == stylePtr) { + *bp++ = bars[ii]; + *ip++ = iData; + } + } + stylePtr->nBars = bp - stylePtr->bars; + } + delete [] bars_; + bars_ = bars; + delete [] barToData_; + barToData_ = barToData; + } + + if (xeb_.length > 0) { + Segment2d* bars = new Segment2d[xeb_.length]; + Segment2d *sp = bars; + int* map = new int[xeb_.length]; + int* ip = map; + for (ChainLink* link = Chain_FirstLink(ops->stylePalette); link; + link = Chain_NextLink(link)) { + BarStyle *stylePtr = (BarStyle*)Chain_GetValue(link); + stylePtr->xeb.segments = sp; + for (int ii=0; ii<xeb_.length; ii++) { + int iData = xeb_.map[ii]; + if (dataToStyle[iData] == stylePtr) { + *sp++ = xeb_.segments[ii]; + *ip++ = iData; + } + } + stylePtr->xeb.length = sp - stylePtr->xeb.segments; + } + delete [] xeb_.segments; + xeb_.segments = bars; + delete [] xeb_.map; + xeb_.map = map; + } + + if (yeb_.length > 0) { + Segment2d* bars = new Segment2d[yeb_.length]; + Segment2d* sp = bars; + int* map = new int[yeb_.length]; + int* ip = map; + for (ChainLink* link = Chain_FirstLink(ops->stylePalette); link; + link = Chain_NextLink(link)) { + BarStyle *stylePtr = (BarStyle*)Chain_GetValue(link); + stylePtr->yeb.segments = sp; + for (int ii=0; ii<yeb_.length; ii++) { + int iData = yeb_.map[ii]; + if (dataToStyle[iData] == stylePtr) { + *sp++ = yeb_.segments[ii]; + *ip++ = iData; + } + } + stylePtr->yeb.length = sp - stylePtr->yeb.segments; + } + delete [] yeb_.segments; + yeb_.segments = bars; + delete [] yeb_.map; + yeb_.map = map; + } +} + +void BarElement::mapActive() +{ + delete [] activeRects_; + activeRects_ = NULL; + + delete [] activeToData_; + activeToData_ = NULL; + + nActive_ = 0; + + if (nActiveIndices_ > 0) { + Rectangle* activeRects = new Rectangle[nActiveIndices_]; + int* activeToData = new int[nActiveIndices_]; + int count = 0; + for (int ii=0; ii<nBars_; ii++) { + for (int *ip = activeIndices_, *iend = ip + nActiveIndices_; + ip < iend; ip++) { + if (barToData_[ii] == *ip) { + activeRects[count] = bars_[ii]; + activeToData[count] = ii; + count++; + } + } + } + nActive_ = count; + activeRects_ = activeRects; + activeToData_ = activeToData; + } +} + +void BarElement::reset() +{ + BarElementOptions* ops = (BarElementOptions*)ops_; + + ResetStylePalette(ops->stylePalette); + + delete [] activeRects_; + activeRects_ = NULL; + delete [] activeToData_; + activeToData_ = 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; + + delete [] bars_; + bars_ = NULL; + delete [] barToData_; + barToData_ = NULL; + + nActive_ = 0; + nBars_ = 0; +} + +void BarElement::mapErrorBars(BarStyle **dataToStyle) +{ + BarElementOptions* ops = (BarElementOptions*)ops_; + + Region2d reg; + graphPtr_->extents(®); + + int nPoints = NUMBEROFPOINTS(ops); + int nn =0; + if (ops->coords.x && ops->coords.y) { + if (ops->xError && (ops->xError->nValues() > 0)) + nn = MIN(ops->xError->nValues(), nPoints); + else + if (ops->xHigh && ops->xLow) + nn = MIN3(ops->xHigh->nValues(), ops->xLow->nValues(), nPoints); + } + + if (nn) { + Segment2d* bars = new Segment2d[nn * 3]; + Segment2d* segPtr = bars; + int* map = new int[nn * 3]; + int* indexPtr = map; + + for (int ii=0; ii<nn; ii++) { + double x = ops->coords.x->values_[ii]; + double y = ops->coords.y->values_[ii]; + BarStyle* stylePtr = dataToStyle[ii]; + + if ((isfinite(x)) && (isfinite(y))) { + double high, 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(®, &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(®, &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(®, &segPtr->p, &segPtr->q)) { + segPtr++; + *indexPtr++ = ii; + } + } + } + } + xeb_.segments = bars; + xeb_.length = segPtr - bars; + xeb_.map = map; + } + + nn =0; + if (ops->coords.x && ops->coords.y) { + if (ops->yError && (ops->yError->nValues() > 0)) + nn = MIN(ops->yError->nValues(), nPoints); + else + if (ops->yHigh && ops->yLow) + nn = MIN3(ops->yHigh->nValues(), ops->yLow->nValues(), nPoints); + } + + if (nn) { + Segment2d* bars = new Segment2d[nn * 3]; + Segment2d* segPtr = bars; + int* map = new int[nn * 3]; + int* indexPtr = map; + + for (int ii=0; ii<nn; ii++) { + double x = ops->coords.x->values_[ii]; + double y = ops->coords.y->values_[ii]; + BarStyle *stylePtr = dataToStyle[ii]; + + if ((isfinite(x)) && (isfinite(y))) { + double high, 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(®, &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(®, &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(®, &segPtr->p, &segPtr->q)) { + segPtr++; + *indexPtr++ = ii; + } + } + } + } + yeb_.segments = bars; + yeb_.length = segPtr - bars; + yeb_.map = map; + } +} + +void BarElement::drawSegments(Drawable drawable, BarPen* penPtr, + Rectangle *bars, int nBars) +{ + BarPenOptions* pops = (BarPenOptions*)penPtr->ops(); + for (Rectangle *rp = bars, *rend = rp + nBars; rp < rend; rp++) { + if ((rp->width < 1) || (rp->height < 1)) + continue; + + Tk_Fill3DRectangle(graphPtr_->tkwin_, drawable, + pops->fill, rp->x, rp->y, rp->width, rp->height, + pops->borderWidth, pops->relief); + + if (pops->outlineColor) + XDrawRectangle(graphPtr_->display_, drawable, penPtr->outlineGC_, + rp->x, rp->y, rp->width, rp->height); + } +} + +void BarElement::drawValues(Drawable drawable, BarPen* penPtr, + Rectangle *bars, int nBars, int *barToData) +{ + BarElementOptions* ops = (BarElementOptions*)ops_; + BarPenOptions* pops = (BarPenOptions*)penPtr->ops(); + BarGraphOptions* gops = (BarGraphOptions*)graphPtr_->ops_; + + const char *fmt = pops->valueFormat; + if (!fmt) + fmt = "%g"; + TextStyle ts(graphPtr_, &pops->valueStyle); + + int count = 0; + for (Rectangle *rp = bars, *rend = rp + nBars; rp < rend; rp++) { + Point2d anchorPos; + char string[TCL_DOUBLE_SPACE * 2 + 2]; + + double x = ops->coords.x->values_[barToData[count]]; + double y = ops->coords.y->values_[barToData[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); + } + + if (gops->inverted) { + anchorPos.y = rp->y + rp->height * 0.5; + anchorPos.x = rp->x + rp->width; + if (x < gops->baseline) + anchorPos.x -= rp->width; + } + else { + anchorPos.x = rp->x + rp->width * 0.5; + anchorPos.y = rp->y; + if (y < gops->baseline) + anchorPos.y += rp->height; + } + + ts.drawText(drawable, string, anchorPos.x, anchorPos.y); + } +} + +void BarElement::printSegments(PSOutput* psPtr, BarPen* penPtr, + Rectangle *bars, int nBars) +{ + BarPenOptions* pops = (BarPenOptions*)penPtr->ops(); + for (Rectangle *rp = bars, *rend = rp + nBars; rp < rend; rp++) { + if ((rp->width < 1) || (rp->height < 1)) + continue; + + psPtr->fill3DRectangle(pops->fill, (double)rp->x, (double)rp->y, + (int)rp->width, (int)rp->height, + pops->borderWidth, pops->relief); + + if (pops->outlineColor) { + psPtr->setForeground(pops->outlineColor); + psPtr->printRectangle((double)rp->x, (double)rp->y, + (int)rp->width, (int)rp->height); + } + } +} + +void BarElement::printValues(PSOutput* psPtr, BarPen* penPtr, + Rectangle *bars, int nBars, int *barToData) +{ + BarPenOptions* pops = (BarPenOptions*)penPtr->ops(); + BarElementOptions* ops = (BarElementOptions*)ops_; + BarGraphOptions* gops = (BarGraphOptions*)graphPtr_->ops_; + + int count = 0; + const char* fmt = pops->valueFormat; + if (!fmt) + fmt = "%g"; + TextStyle ts(graphPtr_, &pops->valueStyle); + + for (Rectangle *rp = bars, *rend = rp + nBars; rp < rend; rp++) { + double x = ops->coords.x->values_[barToData[count]]; + double y = ops->coords.y->values_[barToData[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); + } + + Point2d anchorPos; + if (gops->inverted) { + anchorPos.y = rp->y + rp->height * 0.5; + anchorPos.x = rp->x + rp->width; + if (x < gops->baseline) + anchorPos.x -= rp->width; + } + else { + anchorPos.x = rp->x + rp->width * 0.5; + anchorPos.y = rp->y; + if (y < gops->baseline) + anchorPos.y += rp->height; + } + + ts.printText(psPtr, string, anchorPos.x, anchorPos.y); + } +} + |