diff options
Diffstat (limited to 'tkblt/generic/tkbltGraph.C')
-rw-r--r-- | tkblt/generic/tkbltGraph.C | 1458 |
1 files changed, 1458 insertions, 0 deletions
diff --git a/tkblt/generic/tkbltGraph.C b/tkblt/generic/tkbltGraph.C new file mode 100644 index 0000000..f1453dc --- /dev/null +++ b/tkblt/generic/tkbltGraph.C @@ -0,0 +1,1458 @@ +/* + * 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 1991-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 <cfloat> +#include <cmath> + +#include <tkInt.h> + +#include "tkbltGraph.h" +#include "tkbltGraphOp.h" + +#include "tkbltGrBind.h" +#include "tkbltGrAxis.h" +#include "tkbltGrAxisOp.h" +#include "tkbltGrXAxisOp.h" +#include "tkbltGrPen.h" +#include "tkbltGrPenBar.h" +#include "tkbltGrPenLine.h" +#include "tkbltGrElem.h" +#include "tkbltGrElemBar.h" +#include "tkbltGrElemLine.h" +#include "tkbltGrMarker.h" +#include "tkbltGrLegd.h" +#include "tkbltGrHairs.h" +#include "tkbltGrDef.h" +#include "tkbltGrPostscript.h" +#include "tkbltGrPSOutput.h" +#include "tkbltInt.h" + +using namespace Blt; + +#define MARKER_ABOVE 0 +#define MARKER_UNDER 1 + +// OptionSpecs + +Graph::Graph(ClientData clientData, Tcl_Interp* interp, + int objc, Tcl_Obj* const objv[]) +{ + valid_ =1; + interp_ = interp; + tkwin_ = Tk_CreateWindowFromPath(interp_, Tk_MainWindow(interp_), + Tcl_GetString(objv[1]), NULL); + if (!tkwin_) { + valid_ =0; + return; + } + display_ = Tk_Display(tkwin_); + ((TkWindow*)tkwin_)->instanceData = this; + + cmdToken_ = Tcl_CreateObjCommand(interp_, Tk_PathName(tkwin_), + GraphInstCmdProc, this, + GraphInstCmdDeleteProc); + + flags = RESET; + nextMarkerId_ = 1; + + inset_ =0; + titleX_ =0; + titleY_ =0; + titleWidth_ =0; + titleHeight_ =0; + width_ =0; + height_ =0; + left_ =0; + right_ =0; + top_ =0; + bottom_ =0; + focusPtr_ =NULL; + halo_ =0; + drawGC_ =NULL; + vRange_ =0; + hRange_ =0; + vOffset_ =0; + hOffset_ =0; + vScale_ =0; + hScale_ =0; + cache_ =None; + cacheWidth_ =0; + cacheHeight_ =0; + + Tcl_InitHashTable(&axes_.table, TCL_STRING_KEYS); + Tcl_InitHashTable(&axes_.tagTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&elements_.table, TCL_STRING_KEYS); + Tcl_InitHashTable(&elements_.tagTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&markers_.table, TCL_STRING_KEYS); + Tcl_InitHashTable(&markers_.tagTable, TCL_STRING_KEYS); + Tcl_InitHashTable(&penTable_, TCL_STRING_KEYS); + + axes_.displayList = new Chain(); + elements_.displayList = new Chain(); + markers_.displayList = new Chain(); + bindTable_ = new BindTable(this, this); + + // Keep a hold of the associated tkwin until we destroy the graph, + // otherwise Tk might free it while we still need it. + Tcl_Preserve(tkwin_); + + Tk_CreateEventHandler(tkwin_, + ExposureMask|StructureNotifyMask|FocusChangeMask, + GraphEventProc, this); +} + +Graph::~Graph() +{ + // GraphOptions* ops = (GraphOptions*)ops_; + + destroyMarkers(); + destroyElements(); // must come before legend and others + + delete crosshairs_; + delete legend_; + delete postscript_; + + destroyAxes(); + destroyPens(); + + delete bindTable_; + + if (drawGC_) + Tk_FreeGC(display_, drawGC_); + + if (cache_ != None) + Tk_FreePixmap(display_, cache_); + + Tk_FreeConfigOptions((char*)ops_, optionTable_, tkwin_); + Tcl_Release(tkwin_); + tkwin_ = NULL; + + free (ops_); +} + +int Graph::configure() +{ + GraphOptions* ops = (GraphOptions*)ops_; + + inset_ = ops->borderWidth + ops->highlightWidth; + if ((ops->reqHeight != Tk_ReqHeight(tkwin_)) || + (ops->reqWidth != Tk_ReqWidth(tkwin_))) + Tk_GeometryRequest(tkwin_, ops->reqWidth, ops->reqHeight); + + Tk_SetInternalBorder(tkwin_, ops->borderWidth); + XColor* colorPtr = Tk_3DBorderColor(ops->normalBg); + + titleWidth_ =0; + titleHeight_ =0; + if (ops->title != NULL) { + int w, h; + TextStyle ts(this, &ops->titleTextStyle); + ts.getExtents(ops->title, &w, &h); + titleHeight_ = h; + } + + // Create GCs for interior and exterior regions, and a background GC for + // clearing the margins with XFillRectangle + // Margin + XGCValues gcValues; + gcValues.foreground = ops->titleTextStyle.color->pixel; + gcValues.background = colorPtr->pixel; + unsigned long gcMask = (GCForeground | GCBackground); + GC newGC = Tk_GetGC(tkwin_, gcMask, &gcValues); + if (drawGC_ != NULL) + Tk_FreeGC(display_, drawGC_); + drawGC_ = newGC; + + // If the -inverted option changed, we need to readjust the pointers + // to the axes and recompute the their scales. + adjustAxes(); + + // Free the pixmap if we're not buffering the display of elements anymore. + if (cache_ != None) { + Tk_FreePixmap(display_, cache_); + cache_ = None; + } + + return TCL_OK; +} + +void Graph::map() +{ + if (flags & RESET) { + resetAxes(); + flags &= ~RESET; + flags |= LAYOUT; + } + + if (flags & LAYOUT) { + layoutGraph(); + crosshairs_->map(); + mapAxes(); + mapElements(); + flags &= ~LAYOUT; + flags |= MAP_MARKERS | CACHE; + } + + mapMarkers(); +} + +void Graph::draw() +{ + GraphOptions* ops = (GraphOptions*)ops_; + + flags &= ~REDRAW_PENDING; + if ((flags & GRAPH_DELETED) || !Tk_IsMapped(tkwin_)) + return; + + // Don't bother computing the layout until the size of the window is + // something reasonable. + if ((Tk_Width(tkwin_) <= 1) || (Tk_Height(tkwin_) <= 1)) + return; + + width_ = Tk_Width(tkwin_); + height_ = Tk_Height(tkwin_); + + map(); + + // Create a pixmap the size of the window for double buffering + Pixmap drawable = Tk_GetPixmap(display_, Tk_WindowId(tkwin_), + width_, height_, Tk_Depth(tkwin_)); + + if (cache_ == None || cacheWidth_ != width_ || cacheHeight_ != height_) { + if (cache_ != None) + Tk_FreePixmap(display_, cache_); + cache_ = Tk_GetPixmap(display_, Tk_WindowId(tkwin_), width_, height_, + Tk_Depth(tkwin_)); + cacheWidth_ = width_; + cacheHeight_ = height_; + flags |= CACHE; + } + + // Update cache if needed + if (flags & CACHE) { + drawMargins(cache_); + + switch (legend_->position()) { + case Legend::TOP: + case Legend::BOTTOM: + case Legend::RIGHT: + case Legend::LEFT: + legend_->draw(cache_); + break; + default: + break; + } + + // Draw the background of the plotting area with 3D border + Tk_Fill3DRectangle(tkwin_, cache_, ops->plotBg, + left_-ops->plotBW, + top_-ops->plotBW, + right_-left_+1+2*ops->plotBW, + bottom_-top_+1+2*ops->plotBW, + ops->plotBW, ops->plotRelief); + + drawAxesGrids(cache_); + drawAxes(cache_); + drawAxesLimits(cache_); + + if (!legend_->isRaised()) { + switch (legend_->position()) { + case Legend::PLOT: + case Legend::XY: + legend_->draw(cache_); + break; + default: + break; + } + } + + drawMarkers(cache_, MARKER_UNDER); + drawElements(cache_); + drawActiveElements(cache_); + + if (legend_->isRaised()) { + switch (legend_->position()) { + case Legend::PLOT: + case Legend::XY: + legend_->draw(cache_); + break; + default: + break; + } + } + + flags &= ~CACHE; + } + + XCopyArea(display_, cache_, drawable, drawGC_, 0, 0, Tk_Width(tkwin_), + Tk_Height(tkwin_), 0, 0); + + drawMarkers(drawable, MARKER_ABOVE); + + // Draw 3D border just inside of the focus highlight ring + if ((ops->borderWidth > 0) && (ops->relief != TK_RELIEF_FLAT)) + Tk_Draw3DRectangle(tkwin_, drawable, ops->normalBg, + ops->highlightWidth, ops->highlightWidth, + width_ - 2*ops->highlightWidth, + height_ - 2*ops->highlightWidth, + ops->borderWidth, ops->relief); + + // Draw focus highlight ring + if ((ops->highlightWidth > 0) && (flags & FOCUS)) { + GC gc = Tk_GCForColor(ops->highlightColor, drawable); + Tk_DrawFocusHighlight(tkwin_, gc, ops->highlightWidth, drawable); + } + + // crosshairs + crosshairs_->draw(drawable); + + XCopyArea(display_, drawable, Tk_WindowId(tkwin_), drawGC_, + 0, 0, width_, height_, 0, 0); + + Tk_FreePixmap(display_, drawable); +} + +int Graph::print(const char* ident, PSOutput* psPtr) +{ + GraphOptions* ops = (GraphOptions*)ops_; + PostscriptOptions* pops = (PostscriptOptions*)postscript_->ops_; + + // be sure the window is realized so that relief colors are available + if (flags & REDRAW_PENDING) { + flags |= REDRAW_PENDING; + DisplayGraph(this); + } + + // We need to know how big a graph to print. If the graph hasn't been drawn + // yet, the width and height will be 1. Instead use the requested size of + // the widget. The user can still override this with the -width and -height + // postscript options. + if (pops->reqWidth > 0) + width_ = pops->reqWidth; + else if (width_ < 2) + width_ = Tk_ReqWidth(tkwin_); + + if (pops->reqHeight > 0) + height_ = pops->reqHeight; + else if (height_ < 2) + height_ = Tk_ReqHeight(tkwin_); + + psPtr->computeBBox(width_, height_); + flags |= RESET; + + // Turn on PostScript measurements when computing the graph's layout. + reconfigure(); + + map(); + + int x = left_ - ops->plotBW; + int y = top_ - ops->plotBW; + + int w = (right_ - left_ + 1) + (2*ops->plotBW); + int h = (bottom_ - top_ + 1) + (2*ops->plotBW); + + int result = psPtr->preamble(ident); + if (result != TCL_OK) + goto error; + + psPtr->setFont(ops->titleTextStyle.font); + if (pops->decorations) + psPtr->setBackground(Tk_3DBorderColor(ops->plotBg)); + else + psPtr->setClearBackground(); + + psPtr->fillRectangle(x, y, w, h); + psPtr->append("gsave\n\n"); + + // Start + printMargins(psPtr); + + switch (legend_->position()) { + case Legend::TOP: + case Legend::BOTTOM: + case Legend::RIGHT: + case Legend::LEFT: + legend_->print(psPtr); + break; + default: + break; + } + + printAxesGrids(psPtr); + printAxes(psPtr); + printAxesLimits(psPtr); + + if (!legend_->isRaised()) { + switch (legend_->position()) { + case Legend::PLOT: + case Legend::XY: + legend_->print(psPtr); + break; + default: + break; + } + } + + printMarkers(psPtr, MARKER_UNDER); + printElements(psPtr); + printActiveElements(psPtr); + + if (legend_->isRaised()) { + switch (legend_->position()) { + case Legend::PLOT: + case Legend::XY: + legend_->print(psPtr); + break; + default: + break; + } + } + printMarkers(psPtr, MARKER_ABOVE); + + psPtr->append("\n"); + psPtr->append("% Unset clipping\n"); + psPtr->append("grestore\n\n"); + psPtr->append("showpage\n"); + psPtr->append("%Trailer\n"); + psPtr->append("grestore\n"); + psPtr->append("end\n"); + psPtr->append("%EOF\n"); + + error: + width_ = Tk_Width(tkwin_); + height_ = Tk_Height(tkwin_); + reconfigure(); + + // Redraw the graph in order to re-calculate the layout as soon as + // possible. This is in the case the crosshairs are active. + flags |= LAYOUT; + eventuallyRedraw(); + + return result; +} + +void Graph::eventuallyRedraw() +{ + if (flags & GRAPH_DELETED) + return; + + if (!(flags & REDRAW_PENDING)) { + flags |= REDRAW_PENDING; + Tcl_DoWhenIdle(DisplayGraph, this); + } +} + +void Graph::extents(Region2d* regionPtr) +{ + GraphOptions* ops = (GraphOptions*)ops_; + + regionPtr->left = (double)(hOffset_ - ops->xPad); + regionPtr->top = (double)(vOffset_ - ops->yPad); + regionPtr->right = (double)(hOffset_ + hRange_ + ops->xPad); + regionPtr->bottom = (double)(vOffset_ + vRange_ + ops->yPad); +} + +int Graph::invoke(const Ensemble* ensemble, int cmdIndex, + int objc, Tcl_Obj* const objv[]) +{ + while (cmdIndex < objc) { + int index; + if (Tcl_GetIndexFromObjStruct(interp_, objv[cmdIndex], ensemble, sizeof(ensemble[0]), "command", 0, &index) != TCL_OK) + return TCL_ERROR; + + if (ensemble[index].proc) + return ensemble[index].proc(this, interp_, objc, objv); + + ensemble = ensemble[index].subensemble; + ++cmdIndex; + } + + Tcl_WrongNumArgs(interp_, cmdIndex, objv, "option ?arg ...?"); + return TCL_ERROR; +} + +void Graph::reconfigure() +{ + configure(); + legend_->configure(); + configureElements(); + configureAxes(); + configureMarkers(); +} + +// Margins + +void Graph::drawMargins(Drawable drawable) +{ + GraphOptions* ops = (GraphOptions*)ops_; + Rectangle rects[4]; + + // Draw the four outer rectangles which encompass the plotting + // surface. This clears the surrounding area and clips the plot. + rects[0].x = rects[0].y = rects[3].x = rects[1].x = 0; + rects[0].width = rects[3].width = width_; + rects[0].height = top_; + rects[3].y = bottom_; + rects[3].height = height_ - bottom_; + rects[2].y = rects[1].y = top_; + rects[1].width = left_; + rects[2].height = rects[1].height = bottom_ - top_; + rects[2].x = right_; + rects[2].width = width_ - right_; + + Tk_Fill3DRectangle(tkwin_, drawable, ops->normalBg, + rects[0].x, rects[0].y, rects[0].width, rects[0].height, + 0, TK_RELIEF_FLAT); + Tk_Fill3DRectangle(tkwin_, drawable, ops->normalBg, + rects[1].x, rects[1].y, rects[1].width, rects[1].height, + 0, TK_RELIEF_FLAT); + Tk_Fill3DRectangle(tkwin_, drawable, ops->normalBg, + rects[2].x, rects[2].y, rects[2].width, rects[2].height, + 0, TK_RELIEF_FLAT); + Tk_Fill3DRectangle(tkwin_, drawable, ops->normalBg, + rects[3].x, rects[3].y, rects[3].width, rects[3].height, + 0, TK_RELIEF_FLAT); + + // Draw 3D border around the plotting area + if (ops->plotBW > 0) { + int x = left_ - ops->plotBW; + int y = top_ - ops->plotBW; + int w = (right_ - left_) + (2*ops->plotBW); + int h = (bottom_ - top_) + (2*ops->plotBW); + Tk_Draw3DRectangle(tkwin_, drawable, ops->normalBg, + x, y, w, h, ops->plotBW, ops->plotRelief); + } + + if (ops->title) { + TextStyle ts(this, &ops->titleTextStyle); + ts.drawText(drawable, ops->title, titleX_, titleY_); + } +} + +void Graph::printMargins(PSOutput* psPtr) +{ + GraphOptions* ops = (GraphOptions*)ops_; + PostscriptOptions* pops = (PostscriptOptions*)postscript_->ops_; + Rectangle margin[4]; + + margin[0].x = margin[0].y = margin[3].x = margin[1].x = 0; + margin[0].width = margin[3].width = width_; + margin[0].height = top_; + margin[3].y = bottom_; + margin[3].height = height_ - bottom_; + margin[2].y = margin[1].y = top_; + margin[1].width = left_; + margin[2].height = margin[1].height = bottom_ - top_; + margin[2].x = right_; + margin[2].width = width_ - right_; + + // Clear the surrounding margins and clip the plotting surface + if (pops->decorations) + psPtr->setBackground(Tk_3DBorderColor(ops->normalBg)); + else + psPtr->setClearBackground(); + + psPtr->append("% Margins\n"); + psPtr->fillRectangles(margin, 4); + + if (pops->decorations) { + psPtr->append("% Interior 3D border\n"); + if (ops->plotBW > 0) { + int x = left_ - ops->plotBW; + int y = top_ - ops->plotBW; + int w = (right_ - left_) + (2*ops->plotBW); + int h = (bottom_ - top_) + (2*ops->plotBW); + psPtr->print3DRectangle(ops->normalBg, (double)x, (double)y, w, h, + ops->plotBW, ops->plotRelief); + } + } + + if (ops->title) { + psPtr->append("% Graph title\n"); + TextStyle ts(this, &ops->titleTextStyle); + ts.printText(psPtr, ops->title, titleX_, titleY_); + } +} + +// Pens + +void Graph::destroyPens() +{ + Tcl_HashSearch iter; + for (Tcl_HashEntry *hPtr = Tcl_FirstHashEntry(&penTable_, &iter); + hPtr; hPtr = Tcl_NextHashEntry(&iter)) { + Pen* penPtr = (Pen*)Tcl_GetHashValue(hPtr); + delete penPtr; + } + Tcl_DeleteHashTable(&penTable_); +} + +int Graph::getPen(Tcl_Obj* objPtr, Pen** penPtrPtr) +{ + *penPtrPtr = NULL; + const char *name = Tcl_GetString(objPtr); + if (!name || !name[0]) + return TCL_ERROR; + + Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&penTable_, name); + if (!hPtr) { + Tcl_AppendResult(interp_, "can't find pen \"", name, "\" in \"", + Tk_PathName(tkwin_), "\"", NULL); + return TCL_ERROR; + } + + *penPtrPtr = (Pen*)Tcl_GetHashValue(hPtr); + + return TCL_OK; +} + +// Elements + +void Graph::destroyElements() +{ + Tcl_HashSearch iter; + for (Tcl_HashEntry* hPtr=Tcl_FirstHashEntry(&elements_.table, &iter); + hPtr; hPtr = Tcl_NextHashEntry(&iter)) { + Element* elemPtr = (Element*)Tcl_GetHashValue(hPtr); + legend_->removeElement(elemPtr); + delete elemPtr; + } + + Tcl_DeleteHashTable(&elements_.table); + Tcl_DeleteHashTable(&elements_.tagTable); + delete elements_.displayList; +} + +void Graph::configureElements() +{ + for (ChainLink* link = Chain_FirstLink(elements_.displayList); link; + link = Chain_NextLink(link)) { + Element* elemPtr = (Element*)Chain_GetValue(link); + elemPtr->configure(); + } +} + +void Graph::mapElements() +{ + for (ChainLink* link = Chain_FirstLink(elements_.displayList); link; + link = Chain_NextLink(link)) { + Element* elemPtr = (Element*)Chain_GetValue(link); + elemPtr->map(); + } +} + +void Graph::drawElements(Drawable drawable) +{ + // Draw with respect to the stacking order + for (ChainLink* link=Chain_LastLink(elements_.displayList); link; + link = Chain_PrevLink(link)) { + Element* elemPtr = (Element*)Chain_GetValue(link); + elemPtr->draw(drawable); + } +} + +void Graph::drawActiveElements(Drawable drawable) +{ + for (ChainLink* link = Chain_LastLink(elements_.displayList); link; + link = Chain_PrevLink(link)) { + Element* elemPtr = (Element*)Chain_GetValue(link); + elemPtr->drawActive(drawable); + } +} + +void Graph::printElements(PSOutput* psPtr) +{ + for (ChainLink* link = Chain_LastLink(elements_.displayList); link; + link = Chain_PrevLink(link)) { + Element* elemPtr = (Element*)Chain_GetValue(link); + elemPtr->print(psPtr); + } +} + +void Graph::printActiveElements(PSOutput* psPtr) +{ + for (ChainLink* link = Chain_LastLink(elements_.displayList); link; + link = Chain_PrevLink(link)) { + Element* elemPtr = (Element*)Chain_GetValue(link); + elemPtr->printActive(psPtr); + } +} + +int Graph::getElement(Tcl_Obj *objPtr, Element **elemPtrPtr) +{ + *elemPtrPtr =NULL; + const char* name = Tcl_GetString(objPtr); + if (!name || !name[0]) + return TCL_ERROR; + + Tcl_HashEntry*hPtr = Tcl_FindHashEntry(&elements_.table, name); + if (!hPtr) { + Tcl_AppendResult(interp_, "can't find element \"", name, "\" in \"", + Tk_PathName(tkwin_), "\"", NULL); + return TCL_ERROR; + } + + *elemPtrPtr = (Element*)Tcl_GetHashValue(hPtr); + return TCL_OK; +} + +ClientData Graph::elementTag(const char *tagName) +{ + int isNew; + Tcl_HashEntry* hPtr = + Tcl_CreateHashEntry(&elements_.tagTable, tagName, &isNew); + return Tcl_GetHashKey(&elements_.tagTable, hPtr); +} + +// Markers + +void Graph::destroyMarkers() +{ + Tcl_HashSearch iter; + for (Tcl_HashEntry* hPtr=Tcl_FirstHashEntry(&markers_.table, &iter); + hPtr; hPtr=Tcl_NextHashEntry(&iter)) { + Marker* markerPtr = (Marker*)Tcl_GetHashValue(hPtr); + delete markerPtr; + } + Tcl_DeleteHashTable(&markers_.table); + Tcl_DeleteHashTable(&markers_.tagTable); + delete markers_.displayList; +} + + +void Graph::configureMarkers() +{ + for (ChainLink* link = Chain_FirstLink(markers_.displayList); link; + link = Chain_NextLink(link)) { + Marker* markerPtr = (Marker*)Chain_GetValue(link); + markerPtr->configure(); + } +} + +void Graph::mapMarkers() +{ + for (ChainLink* link = Chain_FirstLink(markers_.displayList); link; + link = Chain_NextLink(link)) { + Marker* markerPtr = (Marker*)Chain_GetValue(link); + MarkerOptions* mops = (MarkerOptions*)markerPtr->ops(); + + if (mops->hide) + continue; + + if ((flags & MAP_MARKERS) || (markerPtr->flags & MAP_ITEM)) { + markerPtr->map(); + markerPtr->flags &= ~MAP_ITEM; + } + } + + flags &= ~MAP_MARKERS; +} + +void Graph::drawMarkers(Drawable drawable, int under) +{ + for (ChainLink* link = Chain_LastLink(markers_.displayList); link; + link = Chain_PrevLink(link)) { + Marker* markerPtr = (Marker*)Chain_GetValue(link); + MarkerOptions* mops = (MarkerOptions*)markerPtr->ops(); + + if ((mops->drawUnder != under) || markerPtr->clipped_ || mops->hide) + continue; + + if (isElementHidden(markerPtr)) + continue; + + markerPtr->draw(drawable); + } +} + +void Graph::printMarkers(PSOutput* psPtr, int under) +{ + for (ChainLink* link = Chain_LastLink(markers_.displayList); link; + link = Chain_PrevLink(link)) { + Marker* markerPtr = (Marker*)Chain_GetValue(link); + MarkerOptions* mops = (MarkerOptions*)markerPtr->ops(); + if (mops->drawUnder != under) + continue; + + if (mops->hide) + continue; + + if (isElementHidden(markerPtr)) + continue; + + psPtr->format("%% Marker \"%s\" is a %s.\n", + markerPtr->name_, markerPtr->className()); + markerPtr->print(psPtr); + } +} + +ClientData Graph::markerTag(const char* tagName) +{ + int isNew; + Tcl_HashEntry* hPtr = Tcl_CreateHashEntry(&markers_.tagTable, tagName,&isNew); + return Tcl_GetHashKey(&markers_.tagTable, hPtr); +} + +Marker* Graph::nearestMarker(int x, int y, int under) +{ + Point2d point; + point.x = (double)x; + point.y = (double)y; + for (ChainLink* link = Chain_FirstLink(markers_.displayList); link; + link = Chain_NextLink(link)) { + Marker* markerPtr = (Marker*)Chain_GetValue(link); + MarkerOptions* mops = (MarkerOptions*)markerPtr->ops(); + + if ((markerPtr->flags & MAP_ITEM) || mops->hide) + continue; + + if (isElementHidden(markerPtr)) + continue; + + if (mops->drawUnder == under) + if (markerPtr->pointIn(&point)) + return markerPtr; + } + return NULL; +} + +int Graph::isElementHidden(Marker* markerPtr) +{ + MarkerOptions* mops = (MarkerOptions*)markerPtr->ops(); + + if (mops->elemName) { + Tcl_HashEntry *hPtr = Tcl_FindHashEntry(&elements_.table, mops->elemName); + if (hPtr) { + Element* elemPtr = (Element*)Tcl_GetHashValue(hPtr); + ElementOptions* eops = (ElementOptions*)elemPtr->ops(); + if (!elemPtr->link || eops->hide) + return 1; + } + } + return 0; +} + +// Axis + +int Graph::createAxes() +{ + for (int ii=0; ii<4; ii++) { + int isNew; + Tcl_HashEntry* hPtr = + Tcl_CreateHashEntry(&axes_.table, axisNames[ii].name, &isNew); + Chain* chain = new Chain(); + + Axis* axisPtr = new Axis(this, axisNames[ii].name, ii, hPtr); + if (!axisPtr) + return TCL_ERROR; + AxisOptions* ops = (AxisOptions*)axisPtr->ops(); + + Tcl_SetHashValue(hPtr, axisPtr); + + axisPtr->refCount_ = 1; + axisPtr->use_ =1; + + axisPtr->setClass(!(ii&1) ? CID_AXIS_X : CID_AXIS_Y); + + if (Tk_InitOptions(interp_, (char*)axisPtr->ops(), + axisPtr->optionTable(), tkwin_) != TCL_OK) + return TCL_ERROR; + + if (axisPtr->configure() != TCL_OK) + return TCL_ERROR; + + if ((axisPtr->margin_ == MARGIN_RIGHT) || (axisPtr->margin_ == MARGIN_TOP)) + ops->hide = 1; + + axisChain_[ii] = chain; + axisPtr->link = chain->append(axisPtr); + axisPtr->chain = chain; + } + return TCL_OK; +} + +int Graph::createAxis(int objc, Tcl_Obj* const objv[]) +{ + char *string = Tcl_GetString(objv[3]); + if (string[0] == '-') { + Tcl_AppendResult(interp_, "name of axis \"", string, + "\" can't start with a '-'", NULL); + return TCL_ERROR; + } + + int isNew; + Tcl_HashEntry* hPtr = Tcl_CreateHashEntry(&axes_.table, string, &isNew); + if (!isNew) { + Tcl_AppendResult(interp_, "axis \"", string, "\" already exists in \"", + Tcl_GetString(objv[0]), "\"", NULL); + return TCL_ERROR; + } + + Axis* axisPtr = new Axis(this, Tcl_GetString(objv[3]), MARGIN_NONE, hPtr); + if (!axisPtr) + return TCL_ERROR; + + Tcl_SetHashValue(hPtr, axisPtr); + + if ((Tk_InitOptions(interp_, (char*)axisPtr->ops(), axisPtr->optionTable(), tkwin_) != TCL_OK) || (AxisObjConfigure(axisPtr, interp_, objc-4, objv+4) != TCL_OK)) { + delete axisPtr; + return TCL_ERROR; + } + + return TCL_OK; +} + +void Graph::destroyAxes() +{ + Tcl_HashSearch cursor; + for (Tcl_HashEntry *hPtr=Tcl_FirstHashEntry(&axes_.table, &cursor); + hPtr; hPtr=Tcl_NextHashEntry(&cursor)) { + Axis *axisPtr = (Axis*)Tcl_GetHashValue(hPtr); + delete axisPtr; + } + Tcl_DeleteHashTable(&axes_.table); + + for (int ii=0; ii<4; ii++) + delete axisChain_[ii]; + + Tcl_DeleteHashTable(&axes_.tagTable); + delete axes_.displayList; +} + +void Graph::configureAxes() +{ + Tcl_HashSearch cursor; + for (Tcl_HashEntry *hPtr=Tcl_FirstHashEntry(&axes_.table, &cursor); + hPtr; hPtr = Tcl_NextHashEntry(&cursor)) { + Axis *axisPtr = (Axis*)Tcl_GetHashValue(hPtr); + axisPtr->configure(); + } +} + +void Graph::mapAxes() +{ + GraphOptions* ops = (GraphOptions*)ops_; + + for (int ii=0; ii<4; ii++) { + int count =0; + int offset =0; + + Chain* chain = ops->margins[ii].axes; + for (ChainLink* link=Chain_FirstLink(chain); link; + link = Chain_NextLink(link)) { + Axis *axisPtr = (Axis*)Chain_GetValue(link); + AxisOptions* aops = (AxisOptions*)axisPtr->ops(); + if (!axisPtr->use_) + continue; + + if (aops->reqNumMajorTicks <= 0) + aops->reqNumMajorTicks = 4; + + if (ops->stackAxes) + axisPtr->mapStacked(count, ii); + else + axisPtr->map(offset, ii); + + if (aops->showGrid) + axisPtr->mapGridlines(); + + offset += axisPtr->isHorizontal() ? axisPtr->height_ : axisPtr->width_; + count++; + } + } +} + +void Graph::drawAxes(Drawable drawable) +{ + GraphOptions* ops = (GraphOptions*)ops_; + + for (int ii=0; ii<4; ii++) { + for (ChainLink* link = Chain_LastLink(ops->margins[ii].axes); link; + link = Chain_PrevLink(link)) { + Axis *axisPtr = (Axis*)Chain_GetValue(link); + axisPtr->draw(drawable); + } + } +} + +void Graph::drawAxesLimits(Drawable drawable) +{ + Tcl_HashSearch cursor; + for (Tcl_HashEntry* hPtr=Tcl_FirstHashEntry(&axes_.table, &cursor); + hPtr; hPtr = Tcl_NextHashEntry(&cursor)) { + Axis *axisPtr = (Axis*)Tcl_GetHashValue(hPtr); + axisPtr->drawLimits(drawable); + } +} + +void Graph::drawAxesGrids(Drawable drawable) +{ + GraphOptions* ops = (GraphOptions*)ops_; + + for (int ii=0; ii<4; ii++) { + for (ChainLink* link = Chain_FirstLink(ops->margins[ii].axes); link; + link = Chain_NextLink(link)) { + Axis *axisPtr = (Axis*)Chain_GetValue(link); + axisPtr->drawGrids(drawable); + } + } +} + +void Graph::printAxes(PSOutput* psPtr) +{ + GraphOptions* ops = (GraphOptions*)ops_; + + for (Margin *mp = ops->margins, *mend = mp + 4; mp < mend; mp++) { + for (ChainLink* link = Chain_FirstLink(mp->axes); link; + link = Chain_NextLink(link)) { + Axis *axisPtr = (Axis*)Chain_GetValue(link); + axisPtr->print(psPtr); + } + } +} + +void Graph::printAxesGrids(PSOutput* psPtr) +{ + GraphOptions* ops = (GraphOptions*)ops_; + + for (int ii=0; ii<4; ii++) { + for (ChainLink* link = Chain_FirstLink(ops->margins[ii].axes); link; + link = Chain_NextLink(link)) { + Axis *axisPtr = (Axis*)Chain_GetValue(link); + axisPtr->printGrids(psPtr); + } + } +} + +void Graph::printAxesLimits(PSOutput* psPtr) +{ + Tcl_HashSearch cursor; + for (Tcl_HashEntry* hPtr=Tcl_FirstHashEntry(&axes_.table, &cursor); + hPtr; hPtr = Tcl_NextHashEntry(&cursor)) { + Axis *axisPtr = (Axis*)Tcl_GetHashValue(hPtr); + axisPtr->printLimits(psPtr); + } +} + +int Graph::getAxis(Tcl_Obj *objPtr, Axis **axisPtrPtr) +{ + *axisPtrPtr = NULL; + const char* name = Tcl_GetString(objPtr); + if (!name || !name[0]) + return TCL_ERROR; + + Tcl_HashEntry* hPtr = Tcl_FindHashEntry(&axes_.table, name); + if (!hPtr) { + Tcl_AppendResult(interp_, "can't find axis \"", name, "\" in \"", + Tk_PathName(tkwin_), "\"", NULL); + return TCL_ERROR; + } + + *axisPtrPtr = (Axis*)Tcl_GetHashValue(hPtr); + return TCL_OK; +} + +ClientData Graph::axisTag(const char *tagName) +{ + int isNew; + Tcl_HashEntry *hPtr = Tcl_CreateHashEntry(&axes_.tagTable, tagName, &isNew); + return Tcl_GetHashKey(&axes_.tagTable, hPtr); +} + +void Graph::adjustAxes() +{ + GraphOptions* ops = (GraphOptions*)ops_; + + if (ops->inverted) { + ops->leftMargin.axes = axisChain_[0]; + ops->bottomMargin.axes = axisChain_[1]; + ops->rightMargin.axes = axisChain_[2]; + ops->topMargin.axes = axisChain_[3]; + } + else { + ops->leftMargin.axes = axisChain_[1]; + ops->bottomMargin.axes = axisChain_[0]; + ops->rightMargin.axes = axisChain_[3]; + ops->topMargin.axes = axisChain_[2]; + } +} + +Point2d Graph::map2D(double x, double y, Axis* xAxis, Axis* yAxis) +{ + GraphOptions* ops = (GraphOptions*)ops_; + + Point2d point; + if (ops->inverted) { + point.x = yAxis->hMap(y); + point.y = xAxis->vMap(x); + } + else { + point.x = xAxis->hMap(x); + point.y = yAxis->vMap(y); + } + return point; +} + +Point2d Graph::invMap2D(double x, double y, Axis* xAxis, Axis* yAxis) +{ + GraphOptions* ops = (GraphOptions*)ops_; + + Point2d point; + if (ops->inverted) { + point.x = xAxis->invVMap(y); + point.y = yAxis->invHMap(x); + } + else { + point.x = xAxis->invHMap(x); + point.y = yAxis->invVMap(y); + } + return point; +} + +void Graph::resetAxes() +{ + // Step 1: Reset all axes. Initialize the data limits of the axis to + // impossible values. + Tcl_HashSearch cursor; + for (Tcl_HashEntry* hPtr = Tcl_FirstHashEntry(&axes_.table, &cursor); + hPtr; hPtr = Tcl_NextHashEntry(&cursor)) { + Axis *axisPtr = (Axis*)Tcl_GetHashValue(hPtr); + axisPtr->min_ = axisPtr->valueRange_.min = DBL_MAX; + axisPtr->max_ = axisPtr->valueRange_.max = -DBL_MAX; + } + + // Step 2: For each element that's to be displayed, get the smallest + // and largest data values mapped to each X and Y-axis. This + // will be the axis limits if the user doesn't override them + // with -min and -max options. + for (ChainLink* link = Chain_FirstLink(elements_.displayList); link; + link = Chain_NextLink(link)) { + Region2d exts; + + Element* elemPtr = (Element*)Chain_GetValue(link); + ElementOptions* elemops = (ElementOptions*)elemPtr->ops(); + elemPtr->extents(&exts); + elemops->xAxis->getDataLimits(exts.left, exts.right); + elemops->yAxis->getDataLimits(exts.top, exts.bottom); + } + + // Step 3: Now that we know the range of data values for each axis, + // set axis limits and compute a sweep to generate tick values. + for (Tcl_HashEntry* hPtr = Tcl_FirstHashEntry(&axes_.table, &cursor); + hPtr; hPtr = Tcl_NextHashEntry(&cursor)) { + Axis *axisPtr = (Axis*)Tcl_GetHashValue(hPtr); + AxisOptions* ops = (AxisOptions*)axisPtr->ops(); + axisPtr->fixRange(); + + double min = axisPtr->min_; + double max = axisPtr->max_; + if ((!isnan(axisPtr->scrollMin_)) && (min < axisPtr->scrollMin_)) + min = axisPtr->scrollMin_; + + if ((!isnan(axisPtr->scrollMax_)) && (max > axisPtr->scrollMax_)) + max = axisPtr->scrollMax_; + + if (ops->logScale) + axisPtr->logScale(min, max); + else + axisPtr->linearScale(min, max); + } +} + +Axis* Graph::nearestAxis(int x, int y) +{ + Tcl_HashSearch cursor; + for (Tcl_HashEntry* hPtr=Tcl_FirstHashEntry(&axes_.table, &cursor); + hPtr; hPtr = Tcl_NextHashEntry(&cursor)) { + Axis *axisPtr = (Axis*)Tcl_GetHashValue(hPtr); + AxisOptions* ops = (AxisOptions*)axisPtr->ops(); + if (ops->hide || !axisPtr->use_) + continue; + + if (ops->showTicks) { + for (ChainLink* link = Chain_FirstLink(axisPtr->tickLabels_); link; + link = Chain_NextLink(link)) { + TickLabel *labelPtr = (TickLabel*)Chain_GetValue(link); + double rw, rh; + Point2d bbox[5]; + getBoundingBox(labelPtr->width, labelPtr->height, ops->tickAngle, + &rw, &rh, bbox); + Point2d t; + t = anchorPoint(labelPtr->anchorPos.x, labelPtr->anchorPos.y, + rw, rh, axisPtr->tickAnchor_); + t.x = x - t.x - (rw * 0.5); + t.y = y - t.y - (rh * 0.5); + + bbox[4] = bbox[0]; + if (pointInPolygon(&t, bbox, 5)) { + return axisPtr; + } + } + } + + if (ops->title) { + int w, h; + double rw, rh; + Point2d bbox[5]; + getTextExtents(ops->titleFont, ops->title, -1, &w, &h); + getBoundingBox(w, h, axisPtr->titleAngle_, &rw, &rh, bbox); + Point2d t = anchorPoint(axisPtr->titlePos_.x, axisPtr->titlePos_.y, + rw, rh, axisPtr->titleAnchor_); + // Translate the point so that the 0,0 is the upper left + // corner of the bounding box + t.x = x - t.x - (rw * 0.5); + t.y = y - t.y - (rh * 0.5); + + bbox[4] = bbox[0]; + if (pointInPolygon(&t, bbox, 5)) { + return axisPtr; + } + } + if (ops->lineWidth > 0) { + if ((x <= axisPtr->right_) && (x >= axisPtr->left_) && + (y <= axisPtr->bottom_) && (y >= axisPtr->top_)) { + return axisPtr; + } + } + } + + return NULL; +} + +// Bind + +const char** Graph::getTags(ClientData object, ClassId classId, int* num) +{ + const char** tags =NULL; + + switch (classId) { + case CID_ELEM_BAR: + case CID_ELEM_LINE: + { + Element* ptr = (Element*)object; + ElementOptions* ops = (ElementOptions*)ptr->ops(); + int cnt =0; + for (const char** pp=ops->tags; *pp; pp++) + cnt++; + cnt +=2; + + tags = new const char*[cnt]; + tags[0] = (const char*)elementTag(ptr->name_); + tags[1] = (const char*)elementTag(ptr->className()); + int ii=2; + for (const char** pp = ops->tags; *pp; pp++, ii++) + tags[ii] = (const char*)elementTag(*pp); + + *num = cnt; + return tags; + } + break; + case CID_AXIS_X: + case CID_AXIS_Y: + { + Axis* ptr = (Axis*)object; + AxisOptions* ops = (AxisOptions*)ptr->ops(); + int cnt =0; + for (const char** pp=ops->tags; *pp; pp++) + cnt++; + cnt +=2; + + tags = new const char*[cnt]; + tags[0] = (const char*)axisTag(ptr->name_); + tags[1] = (const char*)axisTag(ptr->className()); + int ii=2; + for (const char** pp = ops->tags; *pp; pp++, ii++) + tags[ii] = (const char*)axisTag(*pp); + + *num = cnt; + return tags; + } + break; + case CID_MARKER_BITMAP: + case CID_MARKER_LINE: + case CID_MARKER_POLYGON: + case CID_MARKER_TEXT: + { + Marker* ptr = (Marker*)object; + MarkerOptions* ops = (MarkerOptions*)ptr->ops(); + int cnt =0; + for (const char** pp=ops->tags; *pp; pp++) + cnt++; + cnt +=2; + + tags = new const char*[cnt]; + tags[0] = (const char*)markerTag(ptr->name_); + tags[1] = (const char*)markerTag(ptr->className()); + int ii=2; + for (const char** pp = ops->tags; *pp; pp++, ii++) + tags[ii] = (const char*)markerTag(*pp); + + *num = cnt; + return tags; + } + break; + default: + break; + } + + return NULL; +} + +ClientData Graph::pickEntry(int xx, int yy, ClassId* classIdPtr) +{ + if (flags & (LAYOUT | MAP_MARKERS)) { + *classIdPtr = CID_NONE; + return NULL; + } + + // Sample coordinate is in one of the graph margins. Can only pick an axis. + Region2d exts; + extents(&exts); + if (xx>=exts.right || xx<exts.left || yy>=exts.bottom || yy<exts.top) { + Axis* axisPtr = nearestAxis(xx, yy); + if (axisPtr) { + *classIdPtr = axisPtr->classId(); + return axisPtr; + } + } + + // From top-to-bottom check: + // 1. markers drawn on top (-under false). + // 2. elements using its display list back to front. + // 3. markers drawn under element (-under true). + Marker* markerPtr = nearestMarker(xx, yy, 0); + if (markerPtr) { + *classIdPtr = markerPtr->classId(); + return markerPtr; + } + + GraphOptions* ops = (GraphOptions*)ops_; + ClosestSearch* searchPtr = &ops->search; + searchPtr->index = -1; + searchPtr->x = xx; + searchPtr->y = yy; + searchPtr->dist = (double)(searchPtr->halo + 1); + + for (ChainLink* link = Chain_LastLink(elements_.displayList); link; + link = Chain_PrevLink(link)) { + Element* elemPtr = (Element*)Chain_GetValue(link); + ElementOptions* eops = (ElementOptions*)elemPtr->ops(); + if (eops->hide) + continue; + elemPtr->closest(); + } + + // Found an element within the minimum halo distance. + if (searchPtr->dist <= (double)searchPtr->halo) { + *classIdPtr = searchPtr->elemPtr->classId(); + return searchPtr->elemPtr; + } + + markerPtr = nearestMarker(xx, yy, 1); + if (markerPtr) { + *classIdPtr = markerPtr->classId(); + return markerPtr; + } + + *classIdPtr = CID_NONE; + return NULL; +} + +int Graph::getXY(const char* string, int* xPtr, int* yPtr) +{ + if (!string || !*string) { + *xPtr = -SHRT_MAX; + *yPtr = -SHRT_MAX; + return TCL_OK; + } + + if (*string != '@') { + Tcl_AppendResult(interp_, "bad position \"", string, + "\": should be \"@x,y\"", (char *)NULL); + return TCL_ERROR; + } + + char* comma = (char*)strchr(string + 1, ','); + if (!comma) { + Tcl_AppendResult(interp_, "bad position \"", string, + "\": should be \"@x,y\"", (char *)NULL); + return TCL_ERROR; + } + + *comma = '\0'; + int x, y; + int result = ((Tk_GetPixels(interp_, tkwin_, string + 1, &x) == TCL_OK) && + (Tk_GetPixels(interp_, tkwin_, comma + 1, &y) == TCL_OK)); + *comma = ','; + if (!result) { + Tcl_AppendResult(interp_, ": can't parse position \"", string, "\"", + (char *)NULL); + return TCL_ERROR; + } + + *xPtr = x; + *yPtr = y; + return TCL_OK; +} + +// Graphics + +void Graph::drawSegments(Drawable drawable, GC gc, + Segment2d* segments, int nSegments) +{ + for (Segment2d *sp = segments, *send = sp + nSegments; sp < send; sp++) + XDrawLine(display_, drawable, gc, (int)sp->p.x, (int)sp->p.y, (int)sp->q.x, (int)sp->q.y); +} + +GC Graph::getPrivateGC(unsigned long gcMask, XGCValues *valuePtr) +{ + Pixmap pixmap = None; + Drawable drawable = Tk_WindowId(tkwin_); + Display* display = Tk_Display(tkwin_); + if (drawable == None) + drawable = RootWindow(Tk_Display(tkwin_),Tk_ScreenNumber(tkwin_)); + + GC gc = XCreateGC(display, drawable, gcMask, valuePtr); + if (pixmap != None) + Tk_FreePixmap(display, pixmap); + + return gc; +} + +void Graph::freePrivateGC(GC gc) +{ + Tk_FreeXId(display_, (XID)XGContextFromGC(gc)); + XFreeGC(display_, gc); +} + +void Graph::setDashes(GC gc, Dashes* dashesPtr) +{ + XSetDashes(display_, gc, dashesPtr->offset, (const char*)dashesPtr->values, + (int)strlen((char*)dashesPtr->values)); +} |