summaryrefslogtreecommitdiffstats
path: root/src/bltGrXAxisOp.C
diff options
context:
space:
mode:
authorjoye <joye>2014-04-14 16:06:44 (GMT)
committerjoye <joye>2014-04-14 16:06:44 (GMT)
commit2ab311bc0a34895d031f3bf3cebae947b3ffa226 (patch)
treeba83b146a066b904c41b4affde1f0cec8d2f9cb8 /src/bltGrXAxisOp.C
parented23d5ba3752387a1b8416f54dd8948f09a7c3fb (diff)
downloadblt-2ab311bc0a34895d031f3bf3cebae947b3ffa226.zip
blt-2ab311bc0a34895d031f3bf3cebae947b3ffa226.tar.gz
blt-2ab311bc0a34895d031f3bf3cebae947b3ffa226.tar.bz2
*** empty log message ***
Diffstat (limited to 'src/bltGrXAxisOp.C')
-rw-r--r--src/bltGrXAxisOp.C1951
1 files changed, 1951 insertions, 0 deletions
diff --git a/src/bltGrXAxisOp.C b/src/bltGrXAxisOp.C
new file mode 100644
index 0000000..3c0f62a
--- /dev/null
+++ b/src/bltGrXAxisOp.C
@@ -0,0 +1,1951 @@
+/*
+ * 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 "bltMath.h"
+
+extern "C" {
+#include "bltGraph.h"
+#include "bltOp.h"
+}
+
+#include "bltGrAxis.h"
+#include "bltGrAxisOp.h"
+#include "bltGrElem.h"
+#include "bltGrLegd.h"
+
+#define AXIS_PAD_TITLE 2
+#define HORIZMARGIN(m) (!((m)->site & 0x1)) /* Even sites are horizontal */
+
+typedef int (GraphXAxisProc)(Tcl_Interp* interp, Axis *axisPtr,
+ int objc, Tcl_Obj* const objv[]);
+
+static void GetAxisGeometry(Graph* graphPtr, Axis *axisPtr);
+static int GetMarginGeometry(Graph* graphPtr, Margin *marginPtr);
+static int GetAxisScrollInfo(Tcl_Interp* interp,
+ int objc, Tcl_Obj* const objv[],
+ double *offsetPtr, double windowSize,
+ double scrollUnits, double scale);
+extern Tcl_FreeProc FreeAxis;
+extern double AdjustViewport(double offset, double windowSize);
+extern int GetAxisFromObj(Tcl_Interp* interp, Graph* graphPtr, Tcl_Obj *objPtr,
+ Axis **axisPtrPtr);
+extern int CreateAxis(Tcl_Interp* interp, Graph* graphPtr,
+ int objc, Tcl_Obj* const objv[]);
+
+extern int AxisObjConfigure(Tcl_Interp* interp, Axis* axis,
+ int objc, Tcl_Obj* const objv[]);
+
+static double Clamp(double x)
+{
+ return (x < 0.0) ? 0.0 : (x > 1.0) ? 1.0 : x;
+}
+
+static int lastMargin;
+
+// Default
+
+int Blt_CreateAxes(Graph* graphPtr)
+{
+ for (int ii=0; ii<4; ii++) {
+ int isNew;
+ Tcl_HashEntry* hPtr =
+ Tcl_CreateHashEntry(&graphPtr->axes.table, axisNames[ii].name, &isNew);
+ Blt_Chain chain = Blt_Chain_Create();
+
+ Axis* axisPtr = new Axis(graphPtr, axisNames[ii].name, ii);
+ if (!axisPtr)
+ return TCL_ERROR;
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+
+ axisPtr->hashPtr_ = hPtr;
+ Tcl_SetHashValue(hPtr, axisPtr);
+
+ axisPtr->refCount_ = 1;
+ axisPtr->margin_ = ii;
+ axisPtr->use_ =1;
+
+ axisPtr->setClass(!(ii&1) ? CID_AXIS_X : CID_AXIS_Y);
+
+ if (Tk_InitOptions(graphPtr->interp, (char*)axisPtr->ops(),
+ axisPtr->optionTable(), graphPtr->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;
+
+ graphPtr->axisChain[ii] = chain;
+ axisPtr->link = Blt_Chain_Append(chain, axisPtr);
+ axisPtr->chain = chain;
+ }
+ return TCL_OK;
+}
+
+void Blt_DestroyAxes(Graph* graphPtr)
+{
+ Tcl_HashSearch cursor;
+ for (Tcl_HashEntry *hPtr = Tcl_FirstHashEntry(&graphPtr->axes.table, &cursor); hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
+ Axis *axisPtr = (Axis*)Tcl_GetHashValue(hPtr);
+ axisPtr->hashPtr_ = NULL;
+ delete axisPtr;
+ }
+ Tcl_DeleteHashTable(&graphPtr->axes.table);
+
+ for (int ii=0; ii<4; ii++)
+ Blt_Chain_Destroy(graphPtr->axisChain[ii]);
+
+ Tcl_DeleteHashTable(&graphPtr->axes.tagTable);
+ Blt_Chain_Destroy(graphPtr->axes.displayList);
+}
+
+// Configure
+
+static int CgetOp(Tcl_Interp* interp, Axis *axisPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Graph* graphPtr = axisPtr->graphPtr_;
+
+ if (objc != 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "cget option");
+ return TCL_ERROR;
+ }
+
+ Tcl_Obj* objPtr = Tk_GetOptionValue(interp, (char*)axisPtr->ops(),
+ axisPtr->optionTable(),
+ objv[3], graphPtr->tkwin);
+ if (!objPtr)
+ return TCL_ERROR;
+ else
+ Tcl_SetObjResult(interp, objPtr);
+ return TCL_OK;
+}
+
+static int ConfigureOp(Tcl_Interp* interp, Axis *axisPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Graph* graphPtr = axisPtr->graphPtr_;
+
+ if (objc <= 4) {
+ Tcl_Obj* objPtr = Tk_GetOptionInfo(graphPtr->interp, (char*)axisPtr->ops(),
+ axisPtr->optionTable(),
+ (objc == 4) ? objv[3] : NULL,
+ graphPtr->tkwin);
+ if (!objPtr)
+ return TCL_ERROR;
+ else
+ Tcl_SetObjResult(interp, objPtr);
+ return TCL_OK;
+ }
+ else
+ return AxisObjConfigure(interp, axisPtr, objc-3, objv+3);
+}
+
+// Ops
+
+static int ActivateOp(Tcl_Interp* interp, Axis *axisPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+ Graph* graphPtr = axisPtr->graphPtr_;
+ const char *string;
+
+ string = Tcl_GetString(objv[2]);
+ if (string[0] == 'a')
+ axisPtr->flags |= ACTIVE;
+ else
+ axisPtr->flags &= ~ACTIVE;
+
+ if (!ops->hide && axisPtr->use_) {
+ graphPtr->flags |= DRAW_MARGINS | CACHE_DIRTY;
+ Blt_EventuallyRedrawGraph(graphPtr);
+ }
+
+ return TCL_OK;
+}
+
+static int BindOp(Tcl_Interp* interp, Axis *axisPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Graph* graphPtr = axisPtr->graphPtr_;
+
+ return Blt_ConfigureBindingsFromObj(interp, graphPtr->bindTable, Blt_MakeAxisTag(graphPtr, axisPtr->name()), objc-3, objv+3);
+}
+
+static int InvTransformOp(Tcl_Interp* interp, Axis *axisPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Graph* graphPtr = axisPtr->graphPtr_;
+
+ if (graphPtr->flags & RESET_AXES)
+ Blt_ResetAxes(graphPtr);
+
+ int sy;
+ if (Tcl_GetIntFromObj(interp, objv[3], &sy) != TCL_OK)
+ return TCL_ERROR;
+
+ // Is the axis vertical or horizontal?
+ // Check the site where the axis was positioned. If the axis is
+ // virtual, all we have to go on is how it was mapped to an
+ // element (using either -mapx or -mapy options).
+ double y = axisPtr->isHorizontal() ?
+ axisPtr->invHMap(sy) : axisPtr->invVMap(sy);
+
+ Tcl_SetDoubleObj(Tcl_GetObjResult(interp), y);
+ return TCL_OK;
+}
+
+static int LimitsOp(Tcl_Interp* interp, Axis *axisPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+ Graph* graphPtr = axisPtr->graphPtr_;
+
+ if (graphPtr->flags & RESET_AXES)
+ Blt_ResetAxes(graphPtr);
+
+ double min, max;
+ if (ops->logScale) {
+ min = EXP10(axisPtr->axisRange_.min);
+ max = EXP10(axisPtr->axisRange_.max);
+ }
+ else {
+ min = axisPtr->axisRange_.min;
+ max = axisPtr->axisRange_.max;
+ }
+
+ Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
+ Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(min));
+ Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(max));
+
+ Tcl_SetObjResult(interp, listObjPtr);
+ return TCL_OK;
+}
+
+static int MarginOp(Tcl_Interp* interp, Axis *axisPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ const char *marginName = "";
+ if (axisPtr->use_)
+ marginName = axisNames[axisPtr->margin_].name;
+
+ Tcl_SetStringObj(Tcl_GetObjResult(interp), marginName, -1);
+ return TCL_OK;
+}
+
+static int TransformOp(Tcl_Interp* interp, Axis *axisPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Graph* graphPtr = axisPtr->graphPtr_;
+
+ if (graphPtr->flags & RESET_AXES)
+ Blt_ResetAxes(graphPtr);
+
+ double x;
+ if (Blt_ExprDoubleFromObj(interp, objv[3], &x) != TCL_OK)
+ return TCL_ERROR;
+
+ if (axisPtr->isHorizontal())
+ x = axisPtr->hMap(x);
+ else
+ x = axisPtr->vMap(x);
+
+ Tcl_SetIntObj(Tcl_GetObjResult(interp), (int)x);
+ return TCL_OK;
+}
+
+static int TypeOp(Tcl_Interp* interp, Axis *axisPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ const char *typeName;
+
+ typeName = "";
+ if (axisPtr->use_) {
+ if (axisNames[axisPtr->margin_].classId == CID_AXIS_X)
+ typeName = "x";
+ else if (axisNames[axisPtr->margin_].classId == CID_AXIS_Y)
+ typeName = "y";
+ }
+ Tcl_SetStringObj(Tcl_GetObjResult(interp), typeName, -1);
+ return TCL_OK;
+}
+
+static int UseOp(Tcl_Interp* interp, Axis *axisPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Graph* graphPtr = (Graph *)axisPtr;
+
+ Blt_Chain chain = graphPtr->margins[lastMargin].axes;
+ if (objc == 3) {
+ Tcl_Obj *listObjPtr;
+
+ listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
+ for (Blt_ChainLink link = Blt_Chain_FirstLink(chain); link != NULL;
+ link = Blt_Chain_NextLink(link)) {
+ Axis *axisPtr = (Axis*)Blt_Chain_GetValue(link);
+ Tcl_ListObjAppendElement(interp, listObjPtr,
+ Tcl_NewStringObj(axisPtr->name(), -1));
+ }
+ Tcl_SetObjResult(interp, listObjPtr);
+ return TCL_OK;
+ }
+ ClassId classId;
+ if ((lastMargin == MARGIN_BOTTOM) || (lastMargin == MARGIN_TOP))
+ classId = (graphPtr->inverted) ? CID_AXIS_Y : CID_AXIS_X;
+ else
+ classId = (graphPtr->inverted) ? CID_AXIS_X : CID_AXIS_Y;
+
+ int axisObjc;
+ Tcl_Obj **axisObjv;
+ if (Tcl_ListObjGetElements(interp, objv[3], &axisObjc, &axisObjv)
+ != TCL_OK) {
+ return TCL_ERROR;
+ }
+ for (Blt_ChainLink link = Blt_Chain_FirstLink(chain); link!= NULL;
+ link = Blt_Chain_NextLink(link)) {
+ Axis *axisPtr;
+
+ axisPtr = (Axis*)Blt_Chain_GetValue(link);
+ axisPtr->link = NULL;
+ axisPtr->use_ =0;
+ /* Clear the axis type if it's not currently used.*/
+ if (axisPtr->refCount_ == 0)
+ axisPtr->setClass(CID_NONE);
+ }
+ Blt_Chain_Reset(chain);
+ for (int i=0; i<axisObjc; i++) {
+ Axis *axisPtr;
+ if (GetAxisFromObj(interp, graphPtr, axisObjv[i], &axisPtr) != TCL_OK)
+ return TCL_ERROR;
+
+ if (axisPtr->classId() == CID_NONE)
+ axisPtr->setClass(classId);
+ else if (axisPtr->classId() != classId) {
+ Tcl_AppendResult(interp, "wrong type axis \"",
+ axisPtr->name(), "\": can't use ",
+ axisPtr->className(), " type axis.", NULL);
+ return TCL_ERROR;
+ }
+ if (axisPtr->link) {
+ /* Move the axis from the old margin's "use" list to the new. */
+ Blt_Chain_UnlinkLink(axisPtr->chain, axisPtr->link);
+ Blt_Chain_AppendLink(chain, axisPtr->link);
+ } else {
+ axisPtr->link = Blt_Chain_Append(chain, axisPtr);
+ }
+ axisPtr->chain = chain;
+ axisPtr->use_ =1;
+ }
+ graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED | RESET_AXES);
+ /* When any axis changes, we need to layout the entire graph. */
+ graphPtr->flags |= (MAP_WORLD | REDRAW_WORLD);
+ Blt_EventuallyRedrawGraph(graphPtr);
+
+ return TCL_OK;
+}
+
+static int ViewOp(Tcl_Interp* interp, Axis *axisPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+ Graph* graphPtr = axisPtr->graphPtr_;
+ double worldMin = axisPtr->valueRange_.min;
+ double worldMax = axisPtr->valueRange_.max;
+ /* Override data dimensions with user-selected limits. */
+ if (!isnan(axisPtr->scrollMin_))
+ worldMin = axisPtr->scrollMin_;
+
+ if (!isnan(axisPtr->scrollMax_))
+ worldMax = axisPtr->scrollMax_;
+
+ double viewMin = axisPtr->min_;
+ double viewMax = axisPtr->max_;
+ /* Bound the view within scroll region. */
+ if (viewMin < worldMin)
+ viewMin = worldMin;
+
+ if (viewMax > worldMax)
+ viewMax = worldMax;
+
+ if (ops->logScale) {
+ worldMin = log10(worldMin);
+ worldMax = log10(worldMax);
+ viewMin = log10(viewMin);
+ viewMax = log10(viewMax);
+ }
+ double worldWidth = worldMax - worldMin;
+ double viewWidth = viewMax - viewMin;
+
+ /* Unlike horizontal axes, vertical axis values run opposite of the
+ * scrollbar first/last values. So instead of pushing the axis minimum
+ * around, we move the maximum instead. */
+ double axisOffset;
+ double axisScale;
+ if (axisPtr->isHorizontal() != ops->descending) {
+ axisOffset = viewMin - worldMin;
+ axisScale = graphPtr->hScale;
+ } else {
+ axisOffset = worldMax - viewMax;
+ axisScale = graphPtr->vScale;
+ }
+ if (objc == 4) {
+ double first = Clamp(axisOffset / worldWidth);
+ double last = Clamp((axisOffset + viewWidth) / worldWidth);
+ Tcl_Obj *listObjPtr = Tcl_NewListObj(0, NULL);
+ Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(first));
+ Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewDoubleObj(last));
+ Tcl_SetObjResult(interp, listObjPtr);
+ return TCL_OK;
+ }
+ double fract = axisOffset / worldWidth;
+ if (GetAxisScrollInfo(interp, objc, objv, &fract, viewWidth / worldWidth,
+ ops->scrollUnits, axisScale) != TCL_OK)
+ return TCL_ERROR;
+
+ if (axisPtr->isHorizontal() != ops->descending) {
+ ops->reqMin = (fract * worldWidth) + worldMin;
+ ops->reqMax = ops->reqMin + viewWidth;
+ }
+ else {
+ ops->reqMax = worldMax - (fract * worldWidth);
+ ops->reqMin = ops->reqMax - viewWidth;
+ }
+ if (ops->logScale) {
+ ops->reqMin = EXP10(ops->reqMin);
+ ops->reqMax = EXP10(ops->reqMax);
+ }
+ graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED | RESET_AXES);
+ Blt_EventuallyRedrawGraph(graphPtr);
+
+ return TCL_OK;
+}
+
+static Blt_OpSpec defAxisOps[] = {
+ {"activate", 1, (void*)ActivateOp, 3, 3, "",},
+ {"bind", 1, (void*)BindOp, 2, 5, "sequence command",},
+ {"cget", 2, (void*)CgetOp, 4, 4, "option",},
+ {"configure", 2, (void*)ConfigureOp, 3, 0, "?option value?...",},
+ {"deactivate", 1, (void*)ActivateOp, 3, 3, "",},
+ {"invtransform", 1, (void*)InvTransformOp, 4, 4, "value",},
+ {"limits", 1, (void*)LimitsOp, 3, 3, "",},
+ {"transform", 1, (void*)TransformOp, 4, 4, "value",},
+ {"use", 1, (void*)UseOp, 3, 4, "?axisName?",},
+ {"view", 1, (void*)ViewOp, 3, 6, "?moveto fract? ",},
+};
+
+static int nDefAxisOps = sizeof(defAxisOps) / sizeof(Blt_OpSpec);
+
+int Blt_DefAxisOp(Tcl_Interp* interp, Graph* graphPtr, int margin,
+ int objc, Tcl_Obj* const objv[])
+{
+ GraphDefAxisProc* proc = (GraphDefAxisProc*)Blt_GetOpFromObj(interp, nDefAxisOps, defAxisOps, BLT_OP_ARG2, objc, objv, 0);
+ if (!proc)
+ return TCL_ERROR;
+
+ if (proc == UseOp) {
+ // Set global variable to the margin in the argument list
+ lastMargin = margin;
+ return (*proc)(interp, (Axis*)graphPtr, objc, objv);
+ }
+ else {
+ Axis* axisPtr = Blt_GetFirstAxis(graphPtr->margins[margin].axes);
+ if (!axisPtr)
+ return TCL_OK;
+ return (*proc)(interp, axisPtr, objc, objv);
+ }
+}
+
+// Axis
+
+int CreateAxis(Tcl_Interp* interp, Graph* graphPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ char *string = Tcl_GetString(objv[3]);
+ if (string[0] == '-') {
+ Tcl_AppendResult(graphPtr->interp, "name of axis \"", string,
+ "\" can't start with a '-'", NULL);
+ return TCL_ERROR;
+ }
+
+ int isNew;
+ Tcl_HashEntry* hPtr =
+ Tcl_CreateHashEntry(&graphPtr->axes.table, string, &isNew);
+ if (!isNew) {
+ Tcl_AppendResult(graphPtr->interp, "axis \"", string,
+ "\" already exists in \"", Tcl_GetString(objv[0]),
+ "\"", NULL);
+ return TCL_ERROR;
+ }
+
+ Axis* axisPtr = new Axis(graphPtr, Tcl_GetString(objv[3]), MARGIN_NONE);
+ if (!axisPtr)
+ return TCL_ERROR;
+ axisPtr->hashPtr_ = hPtr;
+ Tcl_SetHashValue(hPtr, axisPtr);
+
+ if ((Tk_InitOptions(graphPtr->interp, (char*)axisPtr->ops(), axisPtr->optionTable(), graphPtr->tkwin) != TCL_OK) || (AxisObjConfigure(interp, axisPtr, objc-4, objv+4) != TCL_OK)) {
+ delete axisPtr;
+ return TCL_ERROR;
+ }
+
+ return TCL_OK;
+}
+
+static int AxisCgetOp(Tcl_Interp* interp, Graph* graphPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Axis *axisPtr;
+ if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK)
+ return TCL_ERROR;
+
+ return CgetOp(interp, axisPtr, objc-1, objv+1);
+}
+
+static int AxisConfigureOp(Tcl_Interp* interp, Graph* graphPtr, int objc,
+ Tcl_Obj* const objv[])
+{
+ Axis *axisPtr;
+ if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK)
+ return TCL_ERROR;
+
+ return ConfigureOp(interp, axisPtr, objc-1, objv+1);
+}
+
+int AxisObjConfigure(Tcl_Interp* interp, Axis* axisPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Graph* graphPtr = axisPtr->graphPtr_;
+ Tk_SavedOptions savedOptions;
+ int mask =0;
+ int error;
+ Tcl_Obj* errorResult;
+
+ for (error=0; error<=1; error++) {
+ if (!error) {
+ if (Tk_SetOptions(interp, (char*)axisPtr->ops(), axisPtr->optionTable(),
+ objc, objv, graphPtr->tkwin, &savedOptions, &mask)
+ != TCL_OK)
+ continue;
+ }
+ else {
+ errorResult = Tcl_GetObjResult(interp);
+ Tcl_IncrRefCount(errorResult);
+ Tk_RestoreSavedOptions(&savedOptions);
+ }
+
+ axisPtr->flags |= mask;
+ axisPtr->flags |= DIRTY;
+ graphPtr->flags |= REDRAW_WORLD | MAP_WORLD | RESET_AXES | CACHE_DIRTY;
+ if (axisPtr->configure() != TCL_OK)
+ return TCL_ERROR;
+ Blt_EventuallyRedrawGraph(graphPtr);
+
+ break;
+ }
+
+ if (!error) {
+ Tk_FreeSavedOptions(&savedOptions);
+ return TCL_OK;
+ }
+ else {
+ Tcl_SetObjResult(interp, errorResult);
+ Tcl_DecrRefCount(errorResult);
+ return TCL_ERROR;
+ }
+}
+
+
+static int AxisActivateOp(Tcl_Interp* interp, Graph* graphPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Axis *axisPtr;
+ if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK)
+ return TCL_ERROR;
+
+ return ActivateOp(interp, axisPtr, objc, objv);
+}
+
+static int AxisBindOp(Tcl_Interp* interp, Graph* graphPtr, int objc,
+ Tcl_Obj* const objv[])
+{
+ if (objc == 3) {
+ Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
+ Tcl_HashSearch cursor;
+ for (Tcl_HashEntry *hPtr = Tcl_FirstHashEntry(&graphPtr->axes.tagTable, &cursor); hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
+ const char *tagName = (const char*)
+ Tcl_GetHashKey(&graphPtr->axes.tagTable, hPtr);
+ Tcl_Obj *objPtr = Tcl_NewStringObj(tagName, -1);
+ Tcl_ListObjAppendElement(interp, listObjPtr, objPtr);
+ }
+ Tcl_SetObjResult(interp, listObjPtr);
+ return TCL_OK;
+ }
+ else
+ return Blt_ConfigureBindingsFromObj(interp, graphPtr->bindTable, Blt_MakeAxisTag(graphPtr, Tcl_GetString(objv[3])), objc-4, objv+4);
+}
+
+static int AxisCreateOp(Tcl_Interp* interp, Graph* graphPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ if (CreateAxis(interp, graphPtr, objc, objv) != TCL_OK)
+ return TCL_ERROR;
+ Tcl_SetObjResult(interp, objv[3]);
+
+ return TCL_OK;
+}
+
+static int AxisDeleteOp(Tcl_Interp* interp, Graph* graphPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ if (objc<4)
+ return TCL_ERROR;
+
+ Axis *axisPtr;
+ if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK) {
+ Tcl_AppendResult(interp, "can't find axis \"",
+ Tcl_GetString(objv[3]), "\" in \"",
+ Tk_PathName(graphPtr->tkwin), "\"", NULL);
+ return TCL_ERROR;
+ }
+
+ axisPtr->flags |= DELETE_PENDING;
+ if (axisPtr->refCount_ == 0) {
+ Tcl_EventuallyFree(axisPtr, FreeAxis);
+ Blt_EventuallyRedrawGraph(graphPtr);
+ }
+
+ return TCL_OK;
+}
+
+static int AxisFocusOp(Tcl_Interp* interp, Graph* graphPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ graphPtr->focusPtr = NULL;
+ if (objc == 4) {
+ Axis *axisPtr;
+ if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK)
+ return TCL_ERROR;
+
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+ if (axisPtr && !ops->hide && axisPtr->use_)
+ graphPtr->focusPtr = axisPtr;
+ }
+
+ Blt_SetFocusItem(graphPtr->bindTable, graphPtr->focusPtr, NULL);
+
+ if (graphPtr->focusPtr)
+ Tcl_SetStringObj(Tcl_GetObjResult(interp), graphPtr->focusPtr->name(),-1);
+
+ return TCL_OK;
+}
+
+static int AxisGetOp(Tcl_Interp* interp, Graph* graphPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Axis *axisPtr = (Axis*)Blt_GetCurrentItem(graphPtr->bindTable);
+
+ // Report only on axes
+ if ((axisPtr) &&
+ ((axisPtr->classId() == CID_AXIS_X) ||
+ (axisPtr->classId() == CID_AXIS_Y) ||
+ (axisPtr->classId() == CID_NONE))) {
+
+ char *string = Tcl_GetString(objv[3]);
+ if (!strcmp(string, "current"))
+ Tcl_SetStringObj(Tcl_GetObjResult(interp), axisPtr->name(),-1);
+ else if (!strcmp(string, "detail"))
+ Tcl_SetStringObj(Tcl_GetObjResult(interp), axisPtr->detail_, -1);
+ }
+
+ return TCL_OK;
+}
+
+static int AxisInvTransformOp(Tcl_Interp* interp, Graph* graphPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Axis *axisPtr;
+ if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK)
+ return TCL_ERROR;
+
+ return InvTransformOp(interp, axisPtr, objc-1, objv+1);
+}
+
+static int AxisLimitsOp(Tcl_Interp* interp, Graph* graphPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Axis *axisPtr;
+ if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK)
+ return TCL_ERROR;
+
+ return LimitsOp(interp, axisPtr, objc-1, objv+1);
+}
+
+static int AxisMarginOp(Tcl_Interp* interp, Graph* graphPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Axis *axisPtr;
+ if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK)
+ return TCL_ERROR;
+
+ return MarginOp(interp, axisPtr, objc-1, objv+1);
+}
+
+static int AxisNamesOp(Tcl_Interp* interp, Graph* graphPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Tcl_Obj *listObjPtr = Tcl_NewListObj(0, (Tcl_Obj **)NULL);
+ if (objc == 3) {
+ Tcl_HashSearch cursor;
+ for (Tcl_HashEntry *hPtr = Tcl_FirstHashEntry(&graphPtr->axes.table, &cursor); hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
+ Axis *axisPtr = (Axis*)Tcl_GetHashValue(hPtr);
+ if (axisPtr->flags & DELETE_PENDING)
+ continue;
+
+ Tcl_ListObjAppendElement(interp, listObjPtr, Tcl_NewStringObj(axisPtr->name(), -1));
+ }
+ }
+ else {
+ Tcl_HashSearch cursor;
+ for (Tcl_HashEntry *hPtr = Tcl_FirstHashEntry(&graphPtr->axes.table, &cursor); hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
+ Axis *axisPtr = (Axis*)Tcl_GetHashValue(hPtr);
+ for (int ii=3; ii<objc; ii++) {
+ const char *pattern = (const char*)Tcl_GetString(objv[ii]);
+ if (Tcl_StringMatch(axisPtr->name(), pattern)) {
+ Tcl_ListObjAppendElement(interp, listObjPtr,
+ Tcl_NewStringObj(axisPtr->name(), -1));
+ break;
+ }
+ }
+ }
+ }
+ Tcl_SetObjResult(interp, listObjPtr);
+
+ return TCL_OK;
+}
+
+static int AxisTransformOp(Tcl_Interp* interp, Graph* graphPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Axis *axisPtr;
+ if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK)
+ return TCL_ERROR;
+
+ return TransformOp(interp, axisPtr, objc-1, objv+1);
+}
+
+static int AxisTypeOp(Tcl_Interp* interp, Graph* graphPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Axis *axisPtr;
+ if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK)
+ return TCL_ERROR;
+
+ return TypeOp(interp, axisPtr, objc-1, objv+1);
+}
+
+static int AxisViewOp(Tcl_Interp* interp, Graph* graphPtr,
+ int objc, Tcl_Obj* const objv[])
+{
+ Axis *axisPtr;
+ if (GetAxisFromObj(interp, graphPtr, objv[3], &axisPtr) != TCL_OK)
+ return TCL_ERROR;
+
+ return ViewOp(interp, axisPtr, objc-1, objv+1);
+}
+
+static Blt_OpSpec axisOps[] = {
+ {"activate", 1, (void*)AxisActivateOp, 4, 4, "axisName"},
+ {"bind", 1, (void*)AxisBindOp, 3, 6, "axisName sequence command"},
+ {"cget", 2, (void*)AxisCgetOp, 5, 5, "axisName option"},
+ {"configure", 2, (void*)AxisConfigureOp, 4, 0, "axisName ?axisName?... "
+ "?option value?..."},
+ {"create", 2, (void*)AxisCreateOp, 4, 0, "axisName ?option value?..."},
+ {"deactivate", 3, (void*)AxisActivateOp, 4, 4, "axisName"},
+ {"delete", 3, (void*)AxisDeleteOp, 3, 0, "?axisName?..."},
+ {"focus", 1, (void*)AxisFocusOp, 3, 4, "?axisName?"},
+ {"get", 1, (void*)AxisGetOp, 4, 4, "name"},
+ {"invtransform", 1, (void*)AxisInvTransformOp, 5, 5, "axisName value"},
+ {"limits", 1, (void*)AxisLimitsOp, 4, 4, "axisName"},
+ {"margin", 1, (void*)AxisMarginOp, 4, 4, "axisName"},
+ {"names", 1, (void*)AxisNamesOp, 3, 0, "?pattern?..."},
+ {"transform", 2, (void*)AxisTransformOp, 5, 5, "axisName value"},
+ {"type", 2, (void*)AxisTypeOp, 4, 4, "axisName"},
+ {"view", 1, (void*)AxisViewOp, 4, 7, "axisName ?moveto fract? "
+ "?scroll number what?"},
+};
+static int nAxisOps = sizeof(axisOps) / sizeof(Blt_OpSpec);
+
+int Blt_AxisOp(Graph* graphPtr, Tcl_Interp* interp,
+ int objc, Tcl_Obj* const objv[])
+{
+ GraphAxisProc* proc = (GraphAxisProc*)Blt_GetOpFromObj(interp, nAxisOps, axisOps, BLT_OP_ARG2, objc, objv, 0);
+ if (!proc)
+ return TCL_ERROR;
+
+ return (*proc)(interp, graphPtr, objc, objv);
+}
+
+// Support
+
+int GetAxisFromObj(Tcl_Interp* interp, Graph* graphPtr, Tcl_Obj *objPtr,
+ Axis **axisPtrPtr)
+{
+ Tcl_HashEntry *hPtr;
+ const char *name;
+
+ *axisPtrPtr = NULL;
+ name = Tcl_GetString(objPtr);
+ hPtr = Tcl_FindHashEntry(&graphPtr->axes.table, name);
+ if (hPtr) {
+ Axis *axisPtr = (Axis*)Tcl_GetHashValue(hPtr);
+ if ((axisPtr->flags & DELETE_PENDING) == 0) {
+ *axisPtrPtr = axisPtr;
+ return TCL_OK;
+ }
+ }
+ if (interp)
+ Tcl_AppendResult(interp, "can't find axis \"", name, "\" in \"",
+ Tk_PathName(graphPtr->tkwin), "\"", NULL);
+
+ return TCL_ERROR;
+}
+
+void FreeAxis(char* data)
+{
+ Axis* axisPtr = (Axis*)data;
+ delete axisPtr;
+}
+
+void Blt_ResetAxes(Graph* graphPtr)
+{
+ Blt_ChainLink link;
+ Tcl_HashEntry *hPtr;
+ Tcl_HashSearch cursor;
+
+ /* FIXME: This should be called whenever the display list of
+ * elements change. Maybe yet another flag INIT_STACKS to
+ * indicate that the element display list has changed.
+ * Needs to be done before the axis limits are set.
+ */
+ Blt_InitBarSetTable(graphPtr);
+ if ((graphPtr->barMode == BARS_STACKED) && (graphPtr->nBarGroups > 0))
+ Blt_ComputeBarStacks(graphPtr);
+
+ /*
+ * Step 1: Reset all axes. Initialize the data limits of the axis to
+ * impossible values.
+ */
+ for (hPtr = Tcl_FirstHashEntry(&graphPtr->axes.table, &cursor);
+ hPtr != NULL; 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 (link = Blt_Chain_FirstLink(graphPtr->elements.displayList);
+ link != NULL; link = Blt_Chain_NextLink(link)) {
+ Region2d exts;
+
+ Element* elemPtr = (Element*)Blt_Chain_GetValue(link);
+ ElementOptions* elemops = (ElementOptions*)elemPtr->ops();
+ elemPtr->extents(&exts);
+ elemops->axes.x->getDataLimits(exts.left, exts.right);
+ elemops->axes.y->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 (hPtr = Tcl_FirstHashEntry(&graphPtr->axes.table, &cursor);
+ hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
+ double min, max;
+
+ Axis *axisPtr = (Axis*)Tcl_GetHashValue(hPtr);
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+ axisPtr->fixRange();
+
+ /* Calculate min/max tick (major/minor) layouts */
+ min = axisPtr->min_;
+ 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);
+
+ if (axisPtr->use_ && (axisPtr->flags & DIRTY))
+ graphPtr->flags |= CACHE_DIRTY;
+ }
+
+ graphPtr->flags &= ~RESET_AXES;
+
+ // When any axis changes, we need to layout the entire graph.
+ graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED | MAP_ALL |
+ REDRAW_WORLD);
+}
+
+Point2d Blt_Map2D(Graph* graphPtr, double x, double y, Axis2d *axesPtr)
+{
+ Point2d point;
+
+ if (graphPtr->inverted) {
+ point.x = axesPtr->y->hMap(y);
+ point.y = axesPtr->x->vMap(x);
+ }
+ else {
+ point.x = axesPtr->x->hMap(x);
+ point.y = axesPtr->y->vMap(y);
+ }
+ return point;
+}
+
+Point2d Blt_InvMap2D(Graph* graphPtr, double x, double y, Axis2d *axesPtr)
+{
+ Point2d point;
+
+ if (graphPtr->inverted) {
+ point.x = axesPtr->x->invVMap(y);
+ point.y = axesPtr->y->invHMap(x);
+ }
+ else {
+ point.x = axesPtr->x->invHMap(x);
+ point.y = axesPtr->y->invVMap(y);
+ }
+ return point;
+}
+
+void Blt_MapAxes(Graph* graphPtr)
+{
+ for (int margin = 0; margin < 4; margin++) {
+ int count =0;
+ int offset =0;
+ Blt_Chain chain = graphPtr->margins[margin].axes;
+ for (Blt_ChainLink link=Blt_Chain_FirstLink(chain); link;
+ link = Blt_Chain_NextLink(link)) {
+ Axis *axisPtr = (Axis*)Blt_Chain_GetValue(link);
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+ if (!axisPtr->use_ || (axisPtr->flags & DELETE_PENDING))
+ continue;
+
+ if (graphPtr->stackAxes) {
+ if (ops->reqNumMajorTicks <= 0)
+ ops->reqNumMajorTicks = 4;
+
+ axisPtr->mapStacked(count, margin);
+ }
+ else {
+ if (ops->reqNumMajorTicks <= 0)
+ ops->reqNumMajorTicks = 4;
+
+ axisPtr->map(offset, margin);
+ }
+
+ if (ops->showGrid)
+ axisPtr->mapGridlines();
+
+ offset += axisPtr->isHorizontal() ? axisPtr->height_ : axisPtr->width_;
+ count++;
+ }
+ }
+}
+
+void Blt_DrawAxes(Graph* graphPtr, Drawable drawable)
+{
+ for (int i = 0; i < 4; i++) {
+ for (Blt_ChainLink link = Blt_Chain_LastLink(graphPtr->margins[i].axes);
+ link != NULL; link = Blt_Chain_PrevLink(link)) {
+ Axis *axisPtr = (Axis*)Blt_Chain_GetValue(link);
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+ if (!ops->hide && axisPtr->use_ && !(axisPtr->flags & DELETE_PENDING))
+ axisPtr->draw(drawable);
+ }
+ }
+}
+
+void Blt_DrawAxisLimits(Graph* graphPtr, Drawable drawable)
+{
+ Tcl_HashEntry *hPtr;
+ Tcl_HashSearch cursor;
+ char minString[200], maxString[200];
+ int vMin, hMin, vMax, hMax;
+
+#define SPACING 8
+ vMin = vMax = graphPtr->left + graphPtr->xPad + 2;
+ hMin = hMax = graphPtr->bottom - graphPtr->yPad - 2; /* Offsets */
+
+ for (hPtr = Tcl_FirstHashEntry(&graphPtr->axes.table, &cursor);
+ hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
+ Dim2D textDim;
+ const char *minFmt, *maxFmt;
+ char *minPtr, *maxPtr;
+ int isHoriz;
+
+ Axis *axisPtr = (Axis*)Tcl_GetHashValue(hPtr);
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+ ops->limitsTextStyle.flags |= UPDATE_GC;
+
+ if (axisPtr->flags & DELETE_PENDING)
+ continue;
+
+ if (!ops->limitsFormat)
+ continue;
+
+ isHoriz = axisPtr->isHorizontal();
+ minPtr = maxPtr = NULL;
+ minFmt = maxFmt = ops->limitsFormat;
+ if (minFmt[0] != '\0') {
+ minPtr = minString;
+ sprintf_s(minString, 200, minFmt, axisPtr->axisRange_.min);
+ }
+ if (maxFmt[0] != '\0') {
+ maxPtr = maxString;
+ sprintf_s(maxString, 200, maxFmt, axisPtr->axisRange_.max);
+ }
+ if (ops->descending) {
+ char *tmp;
+
+ tmp = minPtr, minPtr = maxPtr, maxPtr = tmp;
+ }
+ if (maxPtr) {
+ if (isHoriz) {
+ ops->limitsTextStyle.angle = 90.0;
+ ops->limitsTextStyle.anchor = TK_ANCHOR_SE;
+
+ Blt_DrawText2(graphPtr->tkwin, drawable, maxPtr,
+ &ops->limitsTextStyle, graphPtr->right,
+ hMax, &textDim);
+ hMax -= (textDim.height + SPACING);
+ }
+ else {
+ ops->limitsTextStyle.angle = 0.0;
+ ops->limitsTextStyle.anchor = TK_ANCHOR_NW;
+
+ Blt_DrawText2(graphPtr->tkwin, drawable, maxPtr,
+ &ops->limitsTextStyle, vMax,
+ graphPtr->top, &textDim);
+ vMax += (textDim.width + SPACING);
+ }
+ }
+ if (minPtr) {
+ ops->limitsTextStyle.anchor = TK_ANCHOR_SW;
+
+ if (isHoriz) {
+ ops->limitsTextStyle.angle = 90.0;
+
+ Blt_DrawText2(graphPtr->tkwin, drawable, minPtr,
+ &ops->limitsTextStyle, graphPtr->left,
+ hMin, &textDim);
+ hMin -= (textDim.height + SPACING);
+ }
+ else {
+ ops->limitsTextStyle.angle = 0.0;
+
+ Blt_DrawText2(graphPtr->tkwin, drawable, minPtr,
+ &ops->limitsTextStyle, vMin,
+ graphPtr->bottom, &textDim);
+ vMin += (textDim.width + SPACING);
+ }
+ }
+ }
+}
+
+void Blt_AxisLimitsToPostScript(Graph* graphPtr, Blt_Ps ps)
+{
+ Tcl_HashEntry *hPtr;
+ Tcl_HashSearch cursor;
+ double vMin, hMin, vMax, hMax;
+ char string[200];
+
+#define SPACING 8
+ vMin = vMax = graphPtr->left + graphPtr->xPad + 2;
+ hMin = hMax = graphPtr->bottom - graphPtr->yPad - 2; /* Offsets */
+ for (hPtr = Tcl_FirstHashEntry(&graphPtr->axes.table, &cursor);
+ hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
+ const char *minFmt, *maxFmt;
+ unsigned int textWidth, textHeight;
+
+ Axis *axisPtr = (Axis*)Tcl_GetHashValue(hPtr);
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+
+ if (axisPtr->flags & DELETE_PENDING)
+ continue;
+
+ if (!ops->limitsFormat)
+ continue;
+
+ minFmt = maxFmt = ops->limitsFormat;
+ if (*maxFmt != '\0') {
+ sprintf_s(string, 200, maxFmt, axisPtr->axisRange_.max);
+ Blt_GetTextExtents(ops->tickFont, 0, string, -1, &textWidth,
+ &textHeight);
+ if ((textWidth > 0) && (textHeight > 0)) {
+ if (axisPtr->classId() == CID_AXIS_X) {
+ ops->limitsTextStyle.angle = 90.0;
+ ops->limitsTextStyle.anchor = TK_ANCHOR_SE;
+
+ Blt_Ps_DrawText(ps, string, &ops->limitsTextStyle,
+ (double)graphPtr->right, hMax);
+ hMax -= (textWidth + SPACING);
+ }
+ else {
+ ops->limitsTextStyle.angle = 0.0;
+ ops->limitsTextStyle.anchor = TK_ANCHOR_NW;
+
+ Blt_Ps_DrawText(ps, string, &ops->limitsTextStyle,
+ vMax, (double)graphPtr->top);
+ vMax += (textWidth + SPACING);
+ }
+ }
+ }
+ if (*minFmt != '\0') {
+ sprintf_s(string, 200, minFmt, axisPtr->axisRange_.min);
+ Blt_GetTextExtents(ops->tickFont, 0, string, -1, &textWidth,
+ &textHeight);
+ if ((textWidth > 0) && (textHeight > 0)) {
+ ops->limitsTextStyle.anchor = TK_ANCHOR_SW;
+ if (axisPtr->classId() == CID_AXIS_X) {
+ ops->limitsTextStyle.angle = 90.0;
+
+ Blt_Ps_DrawText(ps, string, &ops->limitsTextStyle,
+ (double)graphPtr->left, hMin);
+ hMin -= (textWidth + SPACING);
+ }
+ else {
+ ops->limitsTextStyle.angle = 0.0;
+
+ Blt_Ps_DrawText(ps, string, &ops->limitsTextStyle,
+ vMin, (double)graphPtr->bottom);
+ vMin += (textWidth + SPACING);
+ }
+ }
+ }
+ }
+}
+
+int GetAxisScrollInfo(Tcl_Interp* interp,
+ int objc, Tcl_Obj* const objv[],
+ double *offsetPtr, double windowSize,
+ double scrollUnits, double scale)
+{
+ const char *string;
+ char c;
+ double offset;
+ int length;
+
+ offset = *offsetPtr;
+ string = Tcl_GetStringFromObj(objv[0], &length);
+ c = string[0];
+ scrollUnits *= scale;
+ if ((c == 's') && (strncmp(string, "scroll", length) == 0)) {
+ int count;
+ double fract;
+
+ /* Scroll number unit/page */
+ if (Tcl_GetIntFromObj(interp, objv[1], &count) != TCL_OK)
+ return TCL_ERROR;
+
+ string = Tcl_GetStringFromObj(objv[2], &length);
+ c = string[0];
+ if ((c == 'u') && (strncmp(string, "units", length) == 0))
+ fract = count * scrollUnits;
+ else if ((c == 'p') && (strncmp(string, "pages", length) == 0))
+ /* A page is 90% of the view-able window. */
+ fract = (int)(count * windowSize * 0.9 + 0.5);
+ else if ((c == 'p') && (strncmp(string, "pixels", length) == 0))
+ fract = count * scale;
+ else {
+ Tcl_AppendResult(interp, "unknown \"scroll\" units \"", string,
+ "\"", NULL);
+ return TCL_ERROR;
+ }
+ offset += fract;
+ }
+ else if ((c == 'm') && (strncmp(string, "moveto", length) == 0)) {
+ double fract;
+
+ /* moveto fraction */
+ if (Tcl_GetDoubleFromObj(interp, objv[1], &fract) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ offset = fract;
+ }
+ else {
+ int count;
+ double fract;
+
+ /* Treat like "scroll units" */
+ if (Tcl_GetIntFromObj(interp, objv[0], &count) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ fract = (double)count * scrollUnits;
+ offset += fract;
+ /* CHECK THIS: return TCL_OK; */
+ }
+ *offsetPtr = AdjustViewport(offset, windowSize);
+ return TCL_OK;
+}
+
+void Blt_ConfigureAxes(Graph* graphPtr)
+{
+ Tcl_HashSearch cursor;
+ for (Tcl_HashEntry *hPtr = Tcl_FirstHashEntry(&graphPtr->axes.table, &cursor);
+ hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
+ Axis *axisPtr = (Axis*)Tcl_GetHashValue(hPtr);
+ axisPtr->configure();
+ }
+}
+
+void Blt_DrawGrids(Graph* graphPtr, Drawable drawable)
+{
+ for (int i = 0; i < 4; i++) {
+ for (Blt_ChainLink link = Blt_Chain_FirstLink(graphPtr->margins[i].axes); link != NULL;
+ link = Blt_Chain_NextLink(link)) {
+ Axis *axisPtr = (Axis*)Blt_Chain_GetValue(link);
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+ if (ops->hide || (axisPtr->flags & DELETE_PENDING))
+ continue;
+
+ if (axisPtr->use_ && ops->showGrid) {
+ Blt_Draw2DSegments(graphPtr->display, drawable,
+ ops->major.gc, ops->major.segments,
+ ops->major.nUsed);
+
+ if (ops->showGridMinor)
+ Blt_Draw2DSegments(graphPtr->display, drawable,
+ ops->minor.gc, ops->minor.segments,
+ ops->minor.nUsed);
+ }
+ }
+ }
+}
+
+void Blt_AxesToPostScript(Graph* graphPtr, Blt_Ps ps)
+{
+ Margin *mp, *mend;
+
+ for (mp = graphPtr->margins, mend = mp + 4; mp < mend; mp++) {
+ Blt_ChainLink link;
+
+ for (link = Blt_Chain_FirstLink(mp->axes); link != NULL;
+ link = Blt_Chain_NextLink(link)) {
+ Axis *axisPtr = (Axis*)Blt_Chain_GetValue(link);
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+ if (!ops->hide && axisPtr->use_ && !(axisPtr->flags & DELETE_PENDING))
+ axisPtr->print(ps);
+ }
+ }
+}
+
+void Blt_GridsToPostScript(Graph* graphPtr, Blt_Ps ps)
+{
+ for (int i = 0; i < 4; i++) {
+ for (Blt_ChainLink link = Blt_Chain_FirstLink(graphPtr->margins[i].axes); link != NULL; link = Blt_Chain_NextLink(link)) {
+ Axis *axisPtr = (Axis*)Blt_Chain_GetValue(link);
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+ if (ops->hide || !ops->showGrid || !axisPtr->use_ ||
+ (axisPtr->flags & DELETE_PENDING))
+ continue;
+
+ Blt_Ps_Format(ps, "%% Axis %s: grid line attributes\n",
+ axisPtr->name());
+ Blt_Ps_XSetLineAttributes(ps, ops->major.color,
+ ops->major.lineWidth,
+ &ops->major.dashes, CapButt,
+ JoinMiter);
+ Blt_Ps_Format(ps, "%% Axis %s: major grid line segments\n",
+ axisPtr->name());
+ Blt_Ps_Draw2DSegments(ps, ops->major.segments,
+ ops->major.nUsed);
+ if (ops->showGridMinor) {
+ Blt_Ps_XSetLineAttributes(ps, ops->minor.color,
+ ops->minor.lineWidth,
+ &ops->minor.dashes, CapButt,
+ JoinMiter);
+ Blt_Ps_Format(ps, "%% Axis %s: minor grid line segments\n",
+ axisPtr->name());
+ Blt_Ps_Draw2DSegments(ps, ops->minor.segments,
+ ops->minor.nUsed);
+ }
+ }
+ }
+}
+
+Axis *Blt_GetFirstAxis(Blt_Chain chain)
+{
+ Blt_ChainLink link = Blt_Chain_FirstLink(chain);
+ if (!link)
+ return NULL;
+
+ return (Axis*)Blt_Chain_GetValue(link);
+}
+
+Axis *Blt_NearestAxis(Graph* graphPtr, int x, int y)
+{
+ Tcl_HashEntry *hPtr;
+ Tcl_HashSearch cursor;
+
+ for (hPtr = Tcl_FirstHashEntry(&graphPtr->axes.table, &cursor);
+ hPtr != NULL; hPtr = Tcl_NextHashEntry(&cursor)) {
+ Axis *axisPtr = (Axis*)Tcl_GetHashValue(hPtr);
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+ if (ops->hide || !axisPtr->use_ || (axisPtr->flags & DELETE_PENDING))
+ continue;
+
+ if (ops->showTicks) {
+ Blt_ChainLink link;
+
+ for (link = Blt_Chain_FirstLink(axisPtr->tickLabels_); link != NULL;
+ link = Blt_Chain_NextLink(link)) {
+ Point2d t;
+ double rw, rh;
+ Point2d bbox[5];
+
+ TickLabel *labelPtr = (TickLabel*)Blt_Chain_GetValue(link);
+ Blt_GetBoundingBox(labelPtr->width, labelPtr->height,
+ ops->tickAngle, &rw, &rh, bbox);
+ t = Blt_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 (Blt_PointInPolygon(&t, bbox, 5)) {
+ axisPtr->detail_ = "label";
+ return axisPtr;
+ }
+ }
+ }
+ if (ops->title) { /* and then the title string. */
+ Point2d bbox[5];
+ Point2d t;
+ double rw, rh;
+ unsigned int w, h;
+
+ Blt_GetTextExtents(ops->titleFont, 0, ops->title,-1,&w,&h);
+ Blt_GetBoundingBox(w, h, axisPtr->titleAngle_, &rw, &rh, bbox);
+ t = Blt_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 (Blt_PointInPolygon(&t, bbox, 5)) {
+ axisPtr->detail_ = "title";
+ return axisPtr;
+ }
+ }
+ if (ops->lineWidth > 0) { /* Check for the axis region */
+ if ((x <= axisPtr->right_) && (x >= axisPtr->left_) &&
+ (y <= axisPtr->bottom_) && (y >= axisPtr->top_)) {
+ axisPtr->detail_ = "line";
+ return axisPtr;
+ }
+ }
+ }
+ return NULL;
+}
+
+ClientData Blt_MakeAxisTag(Graph* graphPtr, const char *tagName)
+{
+ int isNew;
+ Tcl_HashEntry *hPtr =
+ Tcl_CreateHashEntry(&graphPtr->axes.tagTable, tagName, &isNew);
+ return Tcl_GetHashKey(&graphPtr->axes.tagTable, hPtr);
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Blt_LayoutGraph --
+ *
+ * Calculate the layout of the graph. Based upon the data, axis limits,
+ * X and Y titles, and title height, determine the cavity left which is
+ * the plotting surface. The first step get the data and axis limits for
+ * calculating the space needed for the top, bottom, left, and right
+ * margins.
+ *
+ * 1) The LEFT margin is the area from the left border to the Y axis
+ * (not including ticks). It composes the border width, the width an
+ * optional Y axis label and its padding, and the tick numeric labels.
+ * The Y axis label is rotated 90 degrees so that the width is the
+ * font height.
+ *
+ * 2) The RIGHT margin is the area from the end of the graph
+ * to the right window border. It composes the border width,
+ * some padding, the font height (this may be dubious. It
+ * appears to provide a more even border), the max of the
+ * legend width and 1/2 max X tick number. This last part is
+ * so that the last tick label is not clipped.
+ *
+ * Window Width
+ * ___________________________________________________________
+ * | | | |
+ * | | TOP height of title | |
+ * | | | |
+ * | | x2 title | |
+ * | | | |
+ * | | height of x2-axis | |
+ * |__________|_______________________________|_______________| W
+ * | | -plotpady | | i
+ * |__________|_______________________________|_______________| n
+ * | | top right | | d
+ * | | | | o
+ * | LEFT | | RIGHT | w
+ * | | | |
+ * | y | Free area = 104% | y2 | H
+ * | | Plotting surface = 100% | | e
+ * | t | Tick length = 2 + 2% | t | i
+ * | i | | i | g
+ * | t | | t legend| h
+ * | l | | l width| t
+ * | e | | e |
+ * | height| |height |
+ * | of | | of |
+ * | y-axis| |y2-axis |
+ * | | | |
+ * | |origin 0,0 | |
+ * |__________|_left_________________bottom___|_______________|
+ * | |-plotpady | |
+ * |__________|_______________________________|_______________|
+ * | | (xoffset, yoffset) | |
+ * | | | |
+ * | | height of x-axis | |
+ * | | | |
+ * | | BOTTOM x title | |
+ * |__________|_______________________________|_______________|
+ *
+ * 3) The TOP margin is the area from the top window border to the top
+ * of the graph. It composes the border width, twice the height of
+ * the title font (if one is given) and some padding between the
+ * title.
+ *
+ * 4) The BOTTOM margin is area from the bottom window border to the
+ * X axis (not including ticks). It composes the border width, the height
+ * an optional X axis label and its padding, the height of the font
+ * of the tick labels.
+ *
+ * The plotting area is between the margins which includes the X and Y axes
+ * including the ticks but not the tick numeric labels. The length of the
+ * ticks and its padding is 5% of the entire plotting area. Hence the entire
+ * plotting area is scaled as 105% of the width and height of the area.
+ *
+ * The axis labels, ticks labels, title, and legend may or may not be
+ * displayed which must be taken into account.
+ *
+ * if reqWidth > 0 : set outer size
+ * if reqPlotWidth > 0 : set plot size
+ *---------------------------------------------------------------------------
+ */
+
+void Blt_LayoutGraph(Graph* graphPtr)
+{
+ int titleY;
+ int plotWidth, plotHeight;
+ int inset, inset2;
+
+ int width = graphPtr->width;
+ int height = graphPtr->height;
+
+ /*
+ * Step 1: Compute the amount of space needed to display the axes
+ * associated with each margin. They can be overridden by
+ * -leftmargin, -rightmargin, -bottommargin, and -topmargin
+ * graph options, respectively.
+ */
+ int left = GetMarginGeometry(graphPtr, &graphPtr->leftMargin);
+ int right = GetMarginGeometry(graphPtr, &graphPtr->rightMargin);
+ int top = GetMarginGeometry(graphPtr, &graphPtr->topMargin);
+ int bottom = GetMarginGeometry(graphPtr, &graphPtr->bottomMargin);
+
+ int pad = graphPtr->bottomMargin.maxTickWidth;
+ if (pad < graphPtr->topMargin.maxTickWidth)
+ pad = graphPtr->topMargin.maxTickWidth;
+
+ pad = pad / 2 + 3;
+ if (right < pad)
+ right = pad;
+
+ if (left < pad)
+ left = pad;
+
+ pad = graphPtr->leftMargin.maxTickHeight;
+ if (pad < graphPtr->rightMargin.maxTickHeight)
+ pad = graphPtr->rightMargin.maxTickHeight;
+
+ pad = pad / 2;
+ if (top < pad)
+ top = pad;
+
+ if (bottom < pad)
+ bottom = pad;
+
+ if (graphPtr->leftMargin.reqSize > 0)
+ left = graphPtr->leftMargin.reqSize;
+
+ if (graphPtr->rightMargin.reqSize > 0)
+ right = graphPtr->rightMargin.reqSize;
+
+ if (graphPtr->topMargin.reqSize > 0)
+ top = graphPtr->topMargin.reqSize;
+
+ if (graphPtr->bottomMargin.reqSize > 0)
+ bottom = graphPtr->bottomMargin.reqSize;
+
+ /*
+ * Step 2: Add the graph title height to the top margin.
+ */
+ if (graphPtr->title)
+ top += graphPtr->titleHeight + 6;
+
+ inset = (graphPtr->inset + graphPtr->plotBW);
+ inset2 = 2 * inset;
+
+ /*
+ * Step 3: Estimate the size of the plot area from the remaining
+ * space. This may be overridden by the -plotwidth and
+ * -plotheight graph options. We use this to compute the
+ * size of the legend.
+ */
+ if (width == 0)
+ width = 400;
+
+ if (height == 0)
+ height = 400;
+
+ plotWidth = (graphPtr->reqPlotWidth > 0) ? graphPtr->reqPlotWidth :
+ width - (inset2 + left + right); /* Plot width. */
+ plotHeight = (graphPtr->reqPlotHeight > 0) ? graphPtr->reqPlotHeight :
+ height - (inset2 + top + bottom); /* Plot height. */
+ graphPtr->legend->map(plotWidth, plotHeight);
+
+ /*
+ * Step 2: Add the legend to the appropiate margin.
+ */
+ if (!graphPtr->legend->isHidden()) {
+ switch (graphPtr->legend->position()) {
+ case Legend::RIGHT:
+ right += graphPtr->legend->width() + 2;
+ break;
+ case Legend::LEFT:
+ left += graphPtr->legend->width() + 2;
+ break;
+ case Legend::TOP:
+ top += graphPtr->legend->height() + 2;
+ break;
+ case Legend::BOTTOM:
+ bottom += graphPtr->legend->height() + 2;
+ break;
+ case Legend::XY:
+ case Legend::PLOT:
+ /* Do nothing. */
+ break;
+ }
+ }
+
+ /*
+ * Recompute the plotarea or graph size, now accounting for the legend.
+ */
+ if (graphPtr->reqPlotWidth == 0) {
+ plotWidth = width - (inset2 + left + right);
+ if (plotWidth < 1)
+ plotWidth = 1;
+ }
+ if (graphPtr->reqPlotHeight == 0) {
+ plotHeight = height - (inset2 + top + bottom);
+ if (plotHeight < 1)
+ plotHeight = 1;
+ }
+
+ /*
+ * Step 5: If necessary, correct for the requested plot area aspect
+ * ratio.
+ */
+ if ((graphPtr->reqPlotWidth == 0) && (graphPtr->reqPlotHeight == 0) &&
+ (graphPtr->aspect > 0.0f)) {
+ float ratio;
+
+ /*
+ * Shrink one dimension of the plotarea to fit the requested
+ * width/height aspect ratio.
+ */
+ ratio = (float)plotWidth / (float)plotHeight;
+ if (ratio > graphPtr->aspect) {
+ // Shrink the width
+ int scaledWidth = (int)(plotHeight * graphPtr->aspect);
+ if (scaledWidth < 1)
+ scaledWidth = 1;
+
+ // Add the difference to the right margin.
+ // CHECK THIS: w = scaledWidth
+ right += (plotWidth - scaledWidth);
+ }
+ else {
+ // Shrink the height
+ int scaledHeight = (int)(plotWidth / graphPtr->aspect);
+ if (scaledHeight < 1)
+ scaledHeight = 1;
+
+ // Add the difference to the top margin
+ // CHECK THIS: h = scaledHeight;
+ top += (plotHeight - scaledHeight);
+ }
+ }
+
+ /*
+ * Step 6: If there's multiple axes in a margin, the axis titles will be
+ * displayed in the adjoining margins. Make sure there's room
+ * for the longest axis titles.
+ */
+
+ if (top < graphPtr->leftMargin.axesTitleLength)
+ top = graphPtr->leftMargin.axesTitleLength;
+
+ if (right < graphPtr->bottomMargin.axesTitleLength)
+ right = graphPtr->bottomMargin.axesTitleLength;
+
+ if (top < graphPtr->rightMargin.axesTitleLength)
+ top = graphPtr->rightMargin.axesTitleLength;
+
+ if (right < graphPtr->topMargin.axesTitleLength)
+ right = graphPtr->topMargin.axesTitleLength;
+
+ /*
+ * Step 7: Override calculated values with requested margin sizes.
+ */
+ if (graphPtr->leftMargin.reqSize > 0)
+ left = graphPtr->leftMargin.reqSize;
+
+ if (graphPtr->rightMargin.reqSize > 0)
+ right = graphPtr->rightMargin.reqSize;
+
+ if (graphPtr->topMargin.reqSize > 0)
+ top = graphPtr->topMargin.reqSize;
+
+ if (graphPtr->bottomMargin.reqSize > 0)
+ bottom = graphPtr->bottomMargin.reqSize;
+
+ if (graphPtr->reqPlotWidth > 0) {
+ /*
+ * Width of plotarea is constained. If there's extra space, add it to
+ * th left and/or right margins. If there's too little, grow the
+ * graph width to accomodate it.
+ */
+ int w = plotWidth + inset2 + left + right;
+ if (width > w) { /* Extra space in window. */
+ int extra = (width - w) / 2;
+ if (graphPtr->leftMargin.reqSize == 0) {
+ left += extra;
+ if (graphPtr->rightMargin.reqSize == 0) {
+ right += extra;
+ }
+ else {
+ left += extra;
+ }
+ }
+ else if (graphPtr->rightMargin.reqSize == 0) {
+ right += extra + extra;
+ }
+ }
+ else if (width < w) {
+ width = w;
+ }
+ }
+ if (graphPtr->reqPlotHeight > 0) { /* Constrain the plotarea height. */
+ /*
+ * Height of plotarea is constained. If there's extra space,
+ * add it to th top and/or bottom margins. If there's too little,
+ * grow the graph height to accomodate it.
+ */
+ int h = plotHeight + inset2 + top + bottom;
+ if (height > h) { /* Extra space in window. */
+ int extra;
+
+ extra = (height - h) / 2;
+ if (graphPtr->topMargin.reqSize == 0) {
+ top += extra;
+ if (graphPtr->bottomMargin.reqSize == 0) {
+ bottom += extra;
+ }
+ else {
+ top += extra;
+ }
+ }
+ else if (graphPtr->bottomMargin.reqSize == 0) {
+ bottom += extra + extra;
+ }
+ }
+ else if (height < h) {
+ height = h;
+ }
+ }
+ graphPtr->width = width;
+ graphPtr->height = height;
+ graphPtr->left = left + inset;
+ graphPtr->top = top + inset;
+ graphPtr->right = width - right - inset;
+ graphPtr->bottom = height - bottom - inset;
+
+ graphPtr->leftMargin.width = left + graphPtr->inset;
+ graphPtr->rightMargin.width = right + graphPtr->inset;
+ graphPtr->topMargin.height = top + graphPtr->inset;
+ graphPtr->bottomMargin.height = bottom + graphPtr->inset;
+
+ graphPtr->vOffset = graphPtr->top + graphPtr->yPad;
+ graphPtr->vRange = plotHeight - 2*graphPtr->yPad;
+ graphPtr->hOffset = graphPtr->left + graphPtr->xPad;
+ graphPtr->hRange = plotWidth - 2*graphPtr->xPad;
+
+ if (graphPtr->vRange < 1)
+ graphPtr->vRange = 1;
+
+ if (graphPtr->hRange < 1)
+ graphPtr->hRange = 1;
+
+ graphPtr->hScale = 1.0f / (float)graphPtr->hRange;
+ graphPtr->vScale = 1.0f / (float)graphPtr->vRange;
+
+ // Calculate the placement of the graph title so it is centered within the
+ // space provided for it in the top margin
+ titleY = graphPtr->titleHeight;
+ graphPtr->titleY = 3 + graphPtr->inset;
+ graphPtr->titleX = (graphPtr->right + graphPtr->left) / 2;
+}
+
+static int GetMarginGeometry(Graph* graphPtr, Margin *marginPtr)
+{
+ int isHoriz = HORIZMARGIN(marginPtr);
+
+ // Count the visible axes.
+ unsigned int nVisible = 0;
+ unsigned int l =0;
+ int w =0;
+ int h =0;
+
+ marginPtr->maxTickWidth =0;
+ marginPtr->maxTickHeight =0;
+
+ if (graphPtr->stackAxes) {
+ for (Blt_ChainLink link = Blt_Chain_FirstLink(marginPtr->axes);
+ link != NULL; link = Blt_Chain_NextLink(link)) {
+ Axis* axisPtr = (Axis*)Blt_Chain_GetValue(link);
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+ if (!ops->hide && axisPtr->use_) {
+ nVisible++;
+ if (graphPtr->flags & GET_AXIS_GEOMETRY)
+ GetAxisGeometry(graphPtr, axisPtr);
+
+ if (isHoriz) {
+ if (h < axisPtr->height_)
+ h = axisPtr->height_;
+ }
+ else {
+ if (w < axisPtr->width_)
+ w = axisPtr->width_;
+ }
+ if (axisPtr->maxTickWidth_ > marginPtr->maxTickWidth)
+ marginPtr->maxTickWidth = axisPtr->maxTickWidth_;
+
+ if (axisPtr->maxTickHeight_ > marginPtr->maxTickHeight)
+ marginPtr->maxTickHeight = axisPtr->maxTickHeight_;
+ }
+ }
+ }
+ else {
+ for (Blt_ChainLink link = Blt_Chain_FirstLink(marginPtr->axes);
+ link != NULL; link = Blt_Chain_NextLink(link)) {
+ Axis* axisPtr = (Axis*)Blt_Chain_GetValue(link);
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+ if (!ops->hide && axisPtr->use_) {
+ nVisible++;
+ if (graphPtr->flags & GET_AXIS_GEOMETRY)
+ GetAxisGeometry(graphPtr, axisPtr);
+
+ if ((ops->titleAlternate) && (l < axisPtr->titleWidth_))
+ l = axisPtr->titleWidth_;
+
+ if (isHoriz)
+ h += axisPtr->height_;
+ else
+ w += axisPtr->width_;
+
+ if (axisPtr->maxTickWidth_ > marginPtr->maxTickWidth)
+ marginPtr->maxTickWidth = axisPtr->maxTickWidth_;
+
+ if (axisPtr->maxTickHeight_ > marginPtr->maxTickHeight)
+ marginPtr->maxTickHeight = axisPtr->maxTickHeight_;
+ }
+ }
+ }
+ // Enforce a minimum size for margins.
+ if (w < 3)
+ w = 3;
+
+ if (h < 3)
+ h = 3;
+
+ marginPtr->nAxes = nVisible;
+ marginPtr->axesTitleLength = l;
+ marginPtr->width = w;
+ marginPtr->height = h;
+ marginPtr->axesOffset = (isHoriz) ? h : w;
+ return marginPtr->axesOffset;
+}
+
+static void GetAxisGeometry(Graph* graphPtr, Axis *axisPtr)
+{
+ AxisOptions* ops = (AxisOptions*)axisPtr->ops();
+ axisPtr->freeTickLabels();
+
+ // Leave room for axis baseline and padding
+ unsigned int y =0;
+ if (ops->exterior && (graphPtr->plotRelief != TK_RELIEF_SOLID))
+ y += ops->lineWidth + 2;
+
+ axisPtr->maxTickHeight_ = axisPtr->maxTickWidth_ = 0;
+
+ if (axisPtr->t1Ptr_)
+ free(axisPtr->t1Ptr_);
+ axisPtr->t1Ptr_ = axisPtr->generateTicks(&axisPtr->majorSweep_);
+ if (axisPtr->t2Ptr_)
+ free(axisPtr->t2Ptr_);
+ axisPtr->t2Ptr_ = axisPtr->generateTicks(&axisPtr->minorSweep_);
+
+ if (ops->showTicks) {
+ Ticks* t1Ptr = ops->t1UPtr ? ops->t1UPtr : axisPtr->t1Ptr_;
+
+ int nTicks =0;
+ if (t1Ptr)
+ nTicks = t1Ptr->nTicks;
+
+ unsigned int nLabels =0;
+ for (int ii=0; ii<nTicks; ii++) {
+ double x = t1Ptr->values[ii];
+ double x2 = t1Ptr->values[ii];
+ if (ops->labelOffset)
+ x2 += axisPtr->majorSweep_.step * 0.5;
+
+ if (!axisPtr->inRange(x2, &axisPtr->axisRange_))
+ continue;
+
+ TickLabel* labelPtr = axisPtr->makeLabel(x);
+ Blt_Chain_Append(axisPtr->tickLabels_, labelPtr);
+ nLabels++;
+ /*
+ * Get the dimensions of each tick label. Remember tick labels
+ * can be multi-lined and/or rotated.
+ */
+ unsigned int lw, lh; /* Label width and height. */
+ Blt_GetTextExtents(ops->tickFont, 0, labelPtr->string, -1, &lw, &lh);
+ labelPtr->width = lw;
+ labelPtr->height = lh;
+
+ if (ops->tickAngle != 0.0f) {
+ double rlw, rlh; /* Rotated label width and height. */
+ Blt_GetBoundingBox(lw, lh, ops->tickAngle, &rlw, &rlh, NULL);
+ lw = ROUND(rlw), lh = ROUND(rlh);
+ }
+ if (axisPtr->maxTickWidth_ < int(lw))
+ axisPtr->maxTickWidth_ = lw;
+
+ if (axisPtr->maxTickHeight_ < int(lh))
+ axisPtr->maxTickHeight_ = lh;
+ }
+
+ unsigned int pad =0;
+ if (ops->exterior) {
+ /* Because the axis cap style is "CapProjecting", we need to
+ * account for an extra 1.5 linewidth at the end of each line. */
+ pad = ((ops->lineWidth * 12) / 8);
+ }
+ if (axisPtr->isHorizontal())
+ y += axisPtr->maxTickHeight_ + pad;
+ else {
+ y += axisPtr->maxTickWidth_ + pad;
+ if (axisPtr->maxTickWidth_ > 0)
+ // Pad either size of label.
+ y += 5;
+ }
+ y += 2 * AXIS_PAD_TITLE;
+ if ((ops->lineWidth > 0) && ops->exterior)
+ // Distance from axis line to tick label.
+ y += ops->tickLength;
+
+ } // showTicks
+
+ if (ops->title) {
+ if (ops->titleAlternate) {
+ if (y < axisPtr->titleHeight_)
+ y = axisPtr->titleHeight_;
+ }
+ else
+ y += axisPtr->titleHeight_ + AXIS_PAD_TITLE;
+ }
+
+ // Correct for orientation of the axis
+ if (axisPtr->isHorizontal())
+ axisPtr->height_ = y;
+ else
+ axisPtr->width_ = y;
+}
+
+double AdjustViewport(double offset, double windowSize)
+{
+ // Canvas-style scrolling allows the world to be scrolled within the window.
+ if (windowSize > 1.0) {
+ if (windowSize < (1.0 - offset))
+ offset = 1.0 - windowSize;
+
+ if (offset > 0.0)
+ offset = 0.0;
+ }
+ else {
+ if ((offset + windowSize) > 1.0)
+ offset = 1.0 - windowSize;
+
+ if (offset < 0.0)
+ offset = 0.0;
+ }
+ return offset;
+}
+