/* * bltGrAxis.c -- * * This module implements coordinate axes for the BLT graph widget. * * Copyright 1993-1998 Lucent Technologies, Inc. * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby * granted, provided that the above copyright notice appear in all * copies and that both that the copyright notice and warranty * disclaimer appear in supporting documentation, and that the names * of Lucent Technologies any of their entities not be used in * advertising or publicity pertaining to distribution of the software * without specific, written prior permission. * * Lucent Technologies disclaims all warranties with regard to this * software, including all implied warranties of merchantability and * fitness. In no event shall Lucent Technologies be liable for any * special, indirect or consequential damages or any damages * whatsoever resulting from loss of use, data or profits, whether in * an action of contract, negligence or other tortuous action, arising * out of or in connection with the use or performance of this * software. */ #include "bltGraph.h" #include "bltGrElem.h" #include #define DEF_NUM_TICKS 4 /* Each minor tick is 20% */ #define STATIC_TICK_SPACE 10 #define TICK_LABEL_SIZE 200 #define MAXTICKS 10001 #define CLAMP(val,low,high) \ (((val) < (low)) ? (low) : ((val) > (high)) ? (high) : (val)) /* * Round x in terms of units */ #define UROUND(x,u) (Round((x)/(u))*(u)) #define UCEIL(x,u) (ceil((x)/(u))*(u)) #define UFLOOR(x,u) (floor((x)/(u))*(u)) #define LENGTH_MAJOR_TICK 0.030 /* Length of a major tick */ #define LENGTH_MINOR_TICK 0.015 /* Length of a minor (sub)tick */ #define LENGTH_LABEL_TICK 0.040 /* Distance from graph to start of the * label */ #define NUMDIGITS 15 /* Specifies the number of * digits of accuracy used when * outputting axis tick labels. */ #define AVG_TICK_NUM_CHARS 16 /* Assumed average tick label size */ #define TICK_RANGE_TIGHT 0 #define TICK_RANGE_LOOSE 1 #define TICK_RANGE_ALWAYS_LOOSE 2 #define AXIS_TITLE_PAD 2 /* Padding for axis title. */ #define AXIS_LINE_PAD 1 /* Padding for axis line. */ #define HORIZMARGIN(m) (!((m)->site & 0x1)) /* Even sites are horizontal */ typedef enum AxisComponents { MAJOR_TICK, MINOR_TICK, TICK_LABEL, AXIS_LINE } AxisComponent; typedef struct { int axis; /* Length of the axis. */ int t1; /* Length of a major tick (in pixels). */ int t2; /* Length of a minor tick (in pixels). */ int label; /* Distance from axis to tick label. */ } AxisInfo; extern Tk_CustomOption bltDistanceOption; extern Tk_CustomOption bltPositiveDistanceOption; extern Tk_CustomOption bltShadowOption; extern Tk_CustomOption bltListOption; static Tk_OptionParseProc StringToLimit; static Tk_OptionPrintProc LimitToString; static Tk_OptionParseProc StringToTicks; static Tk_OptionPrintProc TicksToString; static Tk_OptionParseProc StringToAxis; static Tk_OptionPrintProc AxisToString; static Tk_OptionParseProc StringToAnyAxis; static Tk_OptionParseProc StringToFormat; static Tk_OptionPrintProc FormatToString; static Tk_OptionParseProc StringToLoose; static Tk_OptionPrintProc LooseToString; static Tk_CustomOption limitOption = { StringToLimit, LimitToString, (ClientData)0 }; static Tk_CustomOption majorTicksOption = { StringToTicks, TicksToString, (ClientData)AXIS_CONFIG_MAJOR, }; static Tk_CustomOption minorTicksOption = { StringToTicks, TicksToString, (ClientData)AXIS_CONFIG_MINOR, }; Tk_CustomOption bltXAxisOption = { StringToAxis, AxisToString, (ClientData)&bltXAxisUid }; Tk_CustomOption bltYAxisOption = { StringToAxis, AxisToString, (ClientData)&bltYAxisUid }; Tk_CustomOption bltAnyXAxisOption = { StringToAnyAxis, AxisToString, (ClientData)&bltXAxisUid }; Tk_CustomOption bltAnyYAxisOption = { StringToAnyAxis, AxisToString, (ClientData)&bltYAxisUid }; static Tk_CustomOption formatOption = { StringToFormat, FormatToString, (ClientData)0, }; static Tk_CustomOption looseOption = { StringToLoose, LooseToString, (ClientData)0, }; /* Axis flags: */ #define DEF_AXIS_COMMAND (char *)NULL #define DEF_AXIS_DESCENDING "no" #define DEF_AXIS_FOREGROUND RGB_BLACK #define DEF_AXIS_FG_MONO RGB_BLACK #define DEF_AXIS_HIDE "no" #define DEF_AXIS_JUSTIFY "center" #define DEF_AXIS_LIMITS_FORMAT (char *)NULL #define DEF_AXIS_LINE_WIDTH "1" #define DEF_AXIS_LOGSCALE "no" #define DEF_AXIS_LOOSE "no" #define DEF_AXIS_RANGE "0.0" #define DEF_AXIS_ROTATE "0.0" #define DEF_AXIS_SCROLL_INCREMENT "10" #define DEF_AXIS_SHIFTBY "0.0" #define DEF_AXIS_SHOWTICKS "yes" #define DEF_AXIS_STEP "0.0" #define DEF_AXIS_STEP "0.0" #define DEF_AXIS_SUBDIVISIONS "2" #define DEF_AXIS_TAGS "all" #define DEF_AXIS_TICKS "0" #ifdef WIN32 #define DEF_AXIS_TICK_FONT "{Arial Narrow} 8" #else #define DEF_AXIS_TICK_FONT "*-Helvetica-Medium-R-Normal-*-10-*" #endif #define DEF_AXIS_TICK_LENGTH "8" #define DEF_AXIS_TITLE_ALTERNATE "0" #define DEF_AXIS_TITLE_FG RGB_BLACK #define DEF_AXIS_TITLE_FONT STD_FONT #define DEF_AXIS_X_STEP_BARCHART "1.0" #define DEF_AXIS_X_SUBDIVISIONS_BARCHART "0" #define DEF_AXIS_BACKGROUND (char *)NULL #define DEF_AXIS_BORDERWIDTH "0" #define DEF_AXIS_RELIEF "flat" static Tk_ConfigSpec configSpecs[] = { {TK_CONFIG_DOUBLE, "-autorange", "autoRange", "AutoRange", DEF_AXIS_RANGE, Tk_Offset(Axis, windowSize), ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_BORDER, "-background", "background", "Background", DEF_AXIS_BACKGROUND, Tk_Offset(Axis, border), ALL_GRAPHS | TK_CONFIG_NULL_OK}, {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0}, {TK_CONFIG_CUSTOM, "-bindtags", "bindTags", "BindTags", DEF_AXIS_TAGS, Tk_Offset(Axis, tags), ALL_GRAPHS | TK_CONFIG_NULL_OK, &bltListOption}, {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL, (char *)NULL, 0, ALL_GRAPHS}, {TK_CONFIG_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth", DEF_AXIS_BORDERWIDTH, Tk_Offset(Axis, borderWidth), ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, {TK_CONFIG_COLOR, "-color", "color", "Color", DEF_AXIS_FOREGROUND, Tk_Offset(Axis, tickTextStyle.color), TK_CONFIG_COLOR_ONLY | ALL_GRAPHS}, {TK_CONFIG_COLOR, "-color", "color", "Color", DEF_AXIS_FG_MONO, Tk_Offset(Axis, tickTextStyle.color), TK_CONFIG_MONO_ONLY | ALL_GRAPHS}, {TK_CONFIG_STRING, "-command", "command", "Command", DEF_AXIS_COMMAND, Tk_Offset(Axis, formatCmd), TK_CONFIG_NULL_OK | ALL_GRAPHS}, {TK_CONFIG_BOOLEAN, "-descending", "descending", "Descending", DEF_AXIS_DESCENDING, Tk_Offset(Axis, descending), ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_BOOLEAN, "-hide", "hide", "Hide", DEF_AXIS_HIDE, Tk_Offset(Axis, hidden), ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_JUSTIFY, "-justify", "justify", "Justify", DEF_AXIS_JUSTIFY, Tk_Offset(Axis, titleTextStyle.justify), ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_BOOLEAN, "-labeloffset", "labelOffset", "LabelOffset", (char *)NULL, Tk_Offset(Axis, labelOffset), ALL_GRAPHS}, {TK_CONFIG_COLOR, "-limitscolor", "limitsColor", "Color", DEF_AXIS_FOREGROUND, Tk_Offset(Axis, limitsTextStyle.color), TK_CONFIG_COLOR_ONLY | ALL_GRAPHS}, {TK_CONFIG_COLOR, "-limitscolor", "limitsColor", "Color", DEF_AXIS_FG_MONO, Tk_Offset(Axis, limitsTextStyle.color), TK_CONFIG_MONO_ONLY | ALL_GRAPHS}, {TK_CONFIG_FONT, "-limitsfont", "limitsFont", "Font", DEF_AXIS_TICK_FONT, Tk_Offset(Axis, limitsTextStyle.font), ALL_GRAPHS}, {TK_CONFIG_CUSTOM, "-limitsformat", "limitsFormat", "LimitsFormat", (char *)NULL, Tk_Offset(Axis, limitsFormats), TK_CONFIG_NULL_OK | ALL_GRAPHS, &formatOption}, {TK_CONFIG_CUSTOM, "-limitsshadow", "limitsShadow", "Shadow", (char *)NULL, Tk_Offset(Axis, limitsTextStyle.shadow), TK_CONFIG_COLOR_ONLY | ALL_GRAPHS, &bltShadowOption}, {TK_CONFIG_CUSTOM, "-limitsshadow", "limitsShadow", "Shadow", (char *)NULL, Tk_Offset(Axis, limitsTextStyle.shadow), TK_CONFIG_MONO_ONLY | ALL_GRAPHS, &bltShadowOption}, {TK_CONFIG_CUSTOM, "-linewidth", "lineWidth", "LineWidth", DEF_AXIS_LINE_WIDTH, Tk_Offset(Axis, lineWidth), ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption}, {TK_CONFIG_BOOLEAN, "-logscale", "logScale", "LogScale", DEF_AXIS_LOGSCALE, Tk_Offset(Axis, logScale), ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_CUSTOM, "-loose", "loose", "Loose", DEF_AXIS_LOOSE, 0, ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT, &looseOption}, {TK_CONFIG_CUSTOM, "-majorticks", "majorTicks", "MajorTicks", (char *)NULL, Tk_Offset(Axis, t1Ptr), TK_CONFIG_NULL_OK | ALL_GRAPHS, &majorTicksOption}, {TK_CONFIG_CUSTOM, "-max", "max", "Max", (char *)NULL, Tk_Offset(Axis, reqMax), TK_CONFIG_NULL_OK | ALL_GRAPHS, &limitOption}, {TK_CONFIG_CUSTOM, "-min", "min", "Min", (char *)NULL, Tk_Offset(Axis, reqMin), TK_CONFIG_NULL_OK | ALL_GRAPHS, &limitOption}, {TK_CONFIG_CUSTOM, "-minorticks", "minorTicks", "MinorTicks", (char *)NULL, Tk_Offset(Axis, t2Ptr), TK_CONFIG_NULL_OK | ALL_GRAPHS, &minorTicksOption}, {TK_CONFIG_RELIEF, "-relief", "relief", "Relief", DEF_AXIS_RELIEF, Tk_Offset(Axis, relief), ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_DOUBLE, "-rotate", "rotate", "Rotate", DEF_AXIS_ROTATE, Tk_Offset(Axis, tickTextStyle.theta), ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_STRING, "-scrollcommand", "scrollCommand", "ScrollCommand", (char *)NULL, Tk_Offset(Axis, scrollCmdPrefix), ALL_GRAPHS | TK_CONFIG_NULL_OK}, {TK_CONFIG_CUSTOM, "-scrollincrement", "scrollIncrement", "ScrollIncrement", DEF_AXIS_SCROLL_INCREMENT, Tk_Offset(Axis, scrollUnits), ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT, &bltPositiveDistanceOption}, {TK_CONFIG_CUSTOM, "-scrollmax", "scrollMax", "ScrollMax", (char *)NULL, Tk_Offset(Axis, scrollMax), TK_CONFIG_NULL_OK | ALL_GRAPHS, &limitOption}, {TK_CONFIG_CUSTOM, "-scrollmin", "scrollMin", "ScrollMin", (char *)NULL, Tk_Offset(Axis, scrollMin), TK_CONFIG_NULL_OK | ALL_GRAPHS, &limitOption}, {TK_CONFIG_DOUBLE, "-shiftby", "shiftBy", "ShiftBy", DEF_AXIS_SHIFTBY, Tk_Offset(Axis, shiftBy), ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_BOOLEAN, "-showticks", "showTicks", "ShowTicks", DEF_AXIS_SHOWTICKS, Tk_Offset(Axis, showTicks), ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_DOUBLE, "-stepsize", "stepSize", "StepSize", DEF_AXIS_STEP, Tk_Offset(Axis, reqStep), ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_DOUBLE, "-tickdivider", "tickDivider", "TickDivider", DEF_AXIS_STEP, Tk_Offset(Axis, tickZoom), ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT}, {TK_CONFIG_INT, "-subdivisions", "subdivisions", "Subdivisions", DEF_AXIS_SUBDIVISIONS, Tk_Offset(Axis, reqNumMinorTicks), ALL_GRAPHS}, {TK_CONFIG_FONT, "-tickfont", "tickFont", "Font", DEF_AXIS_TICK_FONT, Tk_Offset(Axis, tickTextStyle.font), ALL_GRAPHS}, {TK_CONFIG_PIXELS, "-ticklength", "tickLength", "TickLength", DEF_AXIS_TICK_LENGTH, Tk_Offset(Axis, tickLength), ALL_GRAPHS}, {TK_CONFIG_CUSTOM, "-tickshadow", "tickShadow", "Shadow", (char *)NULL, Tk_Offset(Axis, tickTextStyle.shadow), TK_CONFIG_COLOR_ONLY | ALL_GRAPHS, &bltShadowOption}, {TK_CONFIG_CUSTOM, "-tickshadow", "tickShadow", "Shadow", (char *)NULL, Tk_Offset(Axis, tickTextStyle.shadow), TK_CONFIG_MONO_ONLY | ALL_GRAPHS, &bltShadowOption}, {TK_CONFIG_STRING, "-title", "title", "Title", (char *)NULL, Tk_Offset(Axis, title), TK_CONFIG_DONT_SET_DEFAULT | TK_CONFIG_NULL_OK | ALL_GRAPHS}, {TK_CONFIG_BOOLEAN, "-titlealternate", "titleAlternate", "TitleAlternate", DEF_AXIS_TITLE_ALTERNATE, Tk_Offset(Axis, titleAlternate), TK_CONFIG_DONT_SET_DEFAULT | ALL_GRAPHS}, {TK_CONFIG_COLOR, "-titlecolor", "titleColor", "Color", DEF_AXIS_FOREGROUND, Tk_Offset(Axis, titleTextStyle.color), TK_CONFIG_COLOR_ONLY | ALL_GRAPHS}, {TK_CONFIG_COLOR, "-titlecolor", "titleColor", "TitleColor", DEF_AXIS_FG_MONO, Tk_Offset(Axis, titleTextStyle.color), TK_CONFIG_MONO_ONLY | ALL_GRAPHS}, {TK_CONFIG_FONT, "-titlefont", "titleFont", "Font", DEF_AXIS_TITLE_FONT, Tk_Offset(Axis, titleTextStyle.font), ALL_GRAPHS}, {TK_CONFIG_CUSTOM, "-titleshadow", "titleShadow", "Shadow", (char *)NULL, Tk_Offset(Axis, titleTextStyle.shadow), TK_CONFIG_COLOR_ONLY | ALL_GRAPHS, &bltShadowOption}, {TK_CONFIG_CUSTOM, "-titleshadow", "titleShadow", "Shadow", (char *)NULL, Tk_Offset(Axis, titleTextStyle.shadow), TK_CONFIG_MONO_ONLY | ALL_GRAPHS, &bltShadowOption}, {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0} }; /* Forward declarations */ static void DestroyAxis _ANSI_ARGS_((Graph *graphPtr, Axis *axisPtr)); static int GetAxis _ANSI_ARGS_((Graph *graphPtr, char *name, Blt_Uid classUid, Axis **axisPtrPtr)); static void FreeAxis _ANSI_ARGS_((Graph *graphPtr, Axis *axisPtr)); INLINE static int Round(register double x) { return (int) (x + ((x < 0.0) ? -0.5 : 0.5)); } static void SetAxisRange(AxisRange *rangePtr, double min, double max) { rangePtr->min = min; rangePtr->max = max; rangePtr->range = max - min; if (FABS(rangePtr->range) < DBL_EPSILON) { rangePtr->range = 1.0; } rangePtr->scale = 1.0 / rangePtr->range; } /* * ---------------------------------------------------------------------- * * InRange -- * * Determines if a value lies within a given range. * * The value is normalized and compared against the interval * [0..1], where 0.0 is the minimum and 1.0 is the maximum. * DBL_EPSILON is the smallest number that can be represented * on the host machine, such that (1.0 + epsilon) != 1.0. * * Please note, *max* can't equal *min*. * * Results: * If the value is within the interval [min..max], 1 is * returned; 0 otherwise. * * ---------------------------------------------------------------------- */ INLINE static int InRange(x, rangePtr) register double x; AxisRange *rangePtr; { if (rangePtr->range < DBL_EPSILON) { #ifdef notdef return (((rangePtr->max - x) >= (FABS(x) * DBL_EPSILON)) && ((x - rangePtr->min) >= (FABS(x) * DBL_EPSILON))); #endif return (FABS(rangePtr->max - x) >= DBL_EPSILON); } else { double norm; norm = (x - rangePtr->min) * rangePtr->scale; return ((norm >= -DBL_EPSILON) && ((norm - 1.0) < DBL_EPSILON)); } } INLINE static int AxisIsHorizontal(graphPtr, axisPtr) Graph *graphPtr; Axis *axisPtr; { return ((axisPtr->classUid == bltYAxisUid) == graphPtr->inverted); } /* ---------------------------------------------------------------------- * Custom option parse and print procedures * ---------------------------------------------------------------------- */ /* *---------------------------------------------------------------------- * * StringToAnyAxis -- * * Converts the name of an axis to a pointer to its axis structure. * * Results: * The return value is a standard Tcl result. The axis flags are * written into the widget record. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int StringToAnyAxis(clientData, interp, tkwin, string, widgRec, offset) ClientData clientData; /* Class identifier of the type of * axis we are looking for. */ Tcl_Interp *interp; /* Interpreter to send results back to. */ Tk_Window tkwin; /* Used to look up pointer to graph. */ char *string; /* String representing new value. */ char *widgRec; /* Pointer to structure record. */ int offset; /* Offset of field in structure. */ { Axis **axisPtrPtr = (Axis **)(widgRec + offset); Blt_Uid classUid = *(Blt_Uid *)clientData; Graph *graphPtr; Axis *axisPtr; graphPtr = Blt_GetGraphFromWindowData(tkwin); if (*axisPtrPtr != NULL) { FreeAxis(graphPtr, *axisPtrPtr); } if (string[0] == '\0') { axisPtr = NULL; } else if (GetAxis(graphPtr, string, classUid, &axisPtr) != TCL_OK) { return TCL_ERROR; } *axisPtrPtr = axisPtr; return TCL_OK; } /* *---------------------------------------------------------------------- * * StringToAxis -- * * Converts the name of an axis to a pointer to its axis structure. * * Results: * The return value is a standard Tcl result. The axis flags are * written into the widget record. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int StringToAxis(clientData, interp, tkwin, string, widgRec, offset) ClientData clientData; /* Class identifier of the type of * axis we are looking for. */ Tcl_Interp *interp; /* Interpreter to send results back to. */ Tk_Window tkwin; /* Used to look up pointer to graph. */ char *string; /* String representing new value. */ char *widgRec; /* Pointer to structure record. */ int offset; /* Offset of field in structure. */ { Axis **axisPtrPtr = (Axis **)(widgRec + offset); Blt_Uid classUid = *(Blt_Uid *)clientData; Graph *graphPtr; graphPtr = Blt_GetGraphFromWindowData(tkwin); if (*axisPtrPtr != NULL) { FreeAxis(graphPtr, *axisPtrPtr); } if (GetAxis(graphPtr, string, classUid, axisPtrPtr) != TCL_OK) { return TCL_ERROR; } return TCL_OK; } /* *---------------------------------------------------------------------- * * AxisToString -- * * Convert the window coordinates into a string. * * Results: * The string representing the coordinate position is returned. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static char * AxisToString(clientData, tkwin, widgRec, offset, freeProcPtr) ClientData clientData; /* Not used. */ Tk_Window tkwin; /* Not used. */ char *widgRec; /* Pointer to structure record .*/ int offset; /* Offset of field in structure. */ Tcl_FreeProc **freeProcPtr; /* Not used. */ { Axis *axisPtr = *(Axis **)(widgRec + offset); if (axisPtr == NULL) { return ""; } return axisPtr->name; } /* *---------------------------------------------------------------------- * * StringToFormat -- * * Convert the name of virtual axis to an pointer. * * Results: * The return value is a standard Tcl result. The axis flags are * written into the widget record. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int StringToFormat(clientData, interp, tkwin, string, widgRec, offset) ClientData clientData; /* Not used. */ Tcl_Interp *interp; /* Interpreter to send results back to. */ Tk_Window tkwin; /* Used to look up pointer to graph */ char *string; /* String representing new value. */ char *widgRec; /* Pointer to structure record. */ int offset; /* Offset of field in structure. */ { Axis *axisPtr = (Axis *)(widgRec); char **argv; int argc; if (axisPtr->limitsFormats != NULL) { Blt_Free(axisPtr->limitsFormats); } axisPtr->limitsFormats = NULL; axisPtr->nFormats = 0; if ((string == NULL) || (*string == '\0')) { return TCL_OK; } if (Tcl_SplitList(interp, string, &argc, &argv) != TCL_OK) { return TCL_ERROR; } if (argc > 2) { Tcl_AppendResult(interp, "too many elements in limits format list \"", string, "\"", (char *)NULL); Blt_Free(argv); return TCL_ERROR; } axisPtr->limitsFormats = argv; axisPtr->nFormats = argc; return TCL_OK; } /* *---------------------------------------------------------------------- * * FormatToString -- * * Convert the window coordinates into a string. * * Results: * The string representing the coordinate position is returned. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static char * FormatToString(clientData, tkwin, widgRec, offset, freeProcPtr) ClientData clientData; /* Not used. */ Tk_Window tkwin; /* Not used. */ char *widgRec; /* Widget record */ int offset; /* offset of limits field */ Tcl_FreeProc **freeProcPtr; /* Not used. */ { Axis *axisPtr = (Axis *)(widgRec); if (axisPtr->nFormats == 0) { return ""; } *freeProcPtr = (Tcl_FreeProc *)Blt_Free; return Tcl_Merge(axisPtr->nFormats, axisPtr->limitsFormats); } /* * ---------------------------------------------------------------------- * * StringToLimit -- * * Convert the string representation of an axis limit into its numeric * form. * * Results: * The return value is a standard Tcl result. The symbol type is * written into the widget record. * * ---------------------------------------------------------------------- */ /*ARGSUSED*/ static int StringToLimit(clientData, interp, tkwin, string, widgRec, offset) ClientData clientData; /* Either AXIS_CONFIG_MIN or AXIS_CONFIG_MAX. * Indicates which axis limit to set. */ Tcl_Interp *interp; /* Interpreter to send results back to */ Tk_Window tkwin; /* Not used. */ char *string; /* String representing new value. */ char *widgRec; /* Pointer to structure record. */ int offset; /* Offset of field in structure. */ { double *limitPtr = (double *)(widgRec + offset); if ((string == NULL) || (*string == '\0')) { *limitPtr = VALUE_UNDEFINED; } else if (Tcl_ExprDouble(interp, string, limitPtr) != TCL_OK) { return TCL_ERROR; } return TCL_OK; } /* * ---------------------------------------------------------------------- * * LimitToString -- * * Convert the floating point axis limits into a string. * * Results: * The string representation of the limits is returned. * * ---------------------------------------------------------------------- */ /*ARGSUSED*/ static char * LimitToString(clientData, tkwin, widgRec, offset, freeProcPtr) ClientData clientData; /* Either LMIN or LMAX */ Tk_Window tkwin; /* Not used. */ char *widgRec; /* */ int offset; Tcl_FreeProc **freeProcPtr; { double limit = *(double *)(widgRec + offset); char *result; result = ""; if (DEFINED(limit)) { char string[TCL_DOUBLE_SPACE + 1]; Graph *graphPtr; graphPtr = Blt_GetGraphFromWindowData(tkwin); Tcl_PrintDouble(graphPtr->interp, limit, string); result = Blt_Strdup(string); if (result == NULL) { return ""; } *freeProcPtr = (Tcl_FreeProc *)Blt_Free; } return result; } /* * ---------------------------------------------------------------------- * * StringToTicks -- * * * Results: * * ---------------------------------------------------------------------- */ /*ARGSUSED*/ static int StringToTicks(clientData, interp, tkwin, string, widgRec, offset) ClientData clientData; /* Not used. */ Tcl_Interp *interp; /* Interpreter to send results back to */ Tk_Window tkwin; /* Not used. */ char *string; /* String representing new value. */ char *widgRec; /* Pointer to structure record. */ int offset; /* Offset of field in structure. */ { unsigned int mask = (unsigned int)clientData; Axis *axisPtr = (Axis *)widgRec; Ticks **ticksPtrPtr = (Ticks **) (widgRec + offset); int nTicks; Ticks *ticksPtr; nTicks = 0; ticksPtr = NULL; if ((string != NULL) && (*string != '\0')) { int nExprs; char **exprArr; if (Tcl_SplitList(interp, string, &nExprs, &exprArr) != TCL_OK) { return TCL_ERROR; } if (nExprs > 0) { register int i; int result = TCL_ERROR; double value; ticksPtr = Blt_Malloc(sizeof(Ticks) + (nExprs * sizeof(double))); assert(ticksPtr); for (i = 0; i < nExprs; i++) { result = Tcl_ExprDouble(interp, exprArr[i], &value); if (result != TCL_OK) { break; } ticksPtr->values[i] = value; } Blt_Free(exprArr); if (result != TCL_OK) { Blt_Free(ticksPtr); return TCL_ERROR; } nTicks = nExprs; } } axisPtr->flags &= ~mask; if (ticksPtr != NULL) { axisPtr->flags |= mask; ticksPtr->nTicks = nTicks; } if (*ticksPtrPtr != NULL) { Blt_Free(*ticksPtrPtr); } *ticksPtrPtr = ticksPtr; return TCL_OK; } /* * ---------------------------------------------------------------------- * * TicksToString -- * * Convert array of tick coordinates to a list. * * Results: * * ---------------------------------------------------------------------- */ /*ARGSUSED*/ static char * TicksToString(clientData, tkwin, widgRec, offset, freeProcPtr) ClientData clientData; /* Not used. */ Tk_Window tkwin; /* Not used. */ char *widgRec; /* */ int offset; Tcl_FreeProc **freeProcPtr; { Ticks *ticksPtr = *(Ticks **) (widgRec + offset); char string[TCL_DOUBLE_SPACE + 1]; register int i; char *result; Tcl_DString dString; Graph *graphPtr; if (ticksPtr == NULL) { return ""; } Tcl_DStringInit(&dString); graphPtr = Blt_GetGraphFromWindowData(tkwin); for (i = 0; i < ticksPtr->nTicks; i++) { Tcl_PrintDouble(graphPtr->interp, ticksPtr->values[i], string); Tcl_DStringAppendElement(&dString, string); } *freeProcPtr = (Tcl_FreeProc *)Blt_Free; result = Blt_Strdup(Tcl_DStringValue(&dString)); Tcl_DStringFree(&dString); return result; } /* *---------------------------------------------------------------------- * * StringToLoose -- * * Convert a string to one of three values. * 0 - false, no, off * 1 - true, yes, on * 2 - always * Results: * If the string is successfully converted, TCL_OK is returned. * Otherwise, TCL_ERROR is returned and an error message is left in * interpreter's result field. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int StringToLoose(clientData, interp, tkwin, string, widgRec, offset) ClientData clientData; /* Not used. */ Tcl_Interp *interp; /* Interpreter to send results back to */ Tk_Window tkwin; /* Not used. */ char *string; /* String representing new value. */ char *widgRec; /* Pointer to structure record. */ int offset; /* Offset of field in structure. */ { Axis *axisPtr = (Axis *)(widgRec); register int i; int argc; char **argv; int values[2]; if (Tcl_SplitList(interp, string, &argc, &argv) != TCL_OK) { return TCL_ERROR; } if ((argc < 1) || (argc > 2)) { Tcl_AppendResult(interp, "wrong # elements in loose value \"", string, "\"", (char *)NULL); return TCL_ERROR; } for (i = 0; i < argc; i++) { if ((argv[i][0] == 'a') && (strcmp(argv[i], "always") == 0)) { values[i] = TICK_RANGE_ALWAYS_LOOSE; } else { int bool; if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) { Blt_Free(argv); return TCL_ERROR; } values[i] = bool; } } axisPtr->looseMin = axisPtr->looseMax = values[0]; if (argc > 1) { axisPtr->looseMax = values[1]; } Blt_Free(argv); return TCL_OK; } /* *---------------------------------------------------------------------- * * LooseToString -- * * Results: * The string representation of the auto boolean is returned. * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static char * LooseToString(clientData, tkwin, widgRec, offset, freeProcPtr) ClientData clientData; /* Not used. */ Tk_Window tkwin; /* Not used. */ char *widgRec; /* Widget record */ int offset; /* offset of flags field in record */ Tcl_FreeProc **freeProcPtr; /* Memory deallocation scheme to use */ { Axis *axisPtr = (Axis *)widgRec; Tcl_DString dString; char *result; Tcl_DStringInit(&dString); if (axisPtr->looseMin == TICK_RANGE_TIGHT) { Tcl_DStringAppendElement(&dString, "0"); } else if (axisPtr->looseMin == TICK_RANGE_LOOSE) { Tcl_DStringAppendElement(&dString, "1"); } else if (axisPtr->looseMin == TICK_RANGE_ALWAYS_LOOSE) { Tcl_DStringAppendElement(&dString, "always"); } if (axisPtr->looseMin != axisPtr->looseMax) { if (axisPtr->looseMax == TICK_RANGE_TIGHT) { Tcl_DStringAppendElement(&dString, "0"); } else if (axisPtr->looseMax == TICK_RANGE_LOOSE) { Tcl_DStringAppendElement(&dString, "1"); } else if (axisPtr->looseMax == TICK_RANGE_ALWAYS_LOOSE) { Tcl_DStringAppendElement(&dString, "always"); } } result = Blt_Strdup(Tcl_DStringValue(&dString)); Tcl_DStringFree(&dString); *freeProcPtr = (Tcl_FreeProc *)Blt_Free; return result; } static void FreeLabels(chainPtr) Blt_Chain *chainPtr; { Blt_ChainLink *linkPtr; TickLabel *labelPtr; for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { labelPtr = Blt_ChainGetValue(linkPtr); Blt_Free(labelPtr); } Blt_ChainReset(chainPtr); } /* * ---------------------------------------------------------------------- * * MakeLabel -- * * Converts a floating point tick value to a string to be used as its * label. * * Results: * None. * * Side Effects: * Returns a new label in the string character buffer. The formatted * tick label will be displayed on the graph. * * ---------------------------------------------------------------------- */ static TickLabel * MakeLabel(graphPtr, axisPtr, value) Graph *graphPtr; Axis *axisPtr; /* Axis structure */ double value; /* Value to be convert to a decimal string */ { char string[TICK_LABEL_SIZE + 1]; TickLabel *labelPtr; /* Generate a default tick label based upon the tick value. */ if (axisPtr->logScale) { sprintf(string, "1E%d", ROUND(value)); } else { sprintf(string, "%.*g", NUMDIGITS, value); } if (axisPtr->formatCmd != NULL) { Tcl_Interp *interp = graphPtr->interp; Tk_Window tkwin = graphPtr->tkwin; /* * A Tcl proc was designated to format tick labels. Append the path * name of the widget and the default tick label as arguments when * invoking it. Copy and save the new label from interp->result. */ Tcl_ResetResult(interp); if (Tcl_VarEval(interp, axisPtr->formatCmd, " ", Tk_PathName(tkwin), " ", string, (char *)NULL) != TCL_OK) { Tcl_BackgroundError(interp); } else { /* * The proc could return a string of any length, so arbitrarily * limit it to what will fit in the return string. */ strncpy(string, Tcl_GetStringResult(interp), TICK_LABEL_SIZE); string[TICK_LABEL_SIZE] = '\0'; Tcl_ResetResult(interp); /* Clear the interpreter's result. */ } } labelPtr = Blt_Malloc(sizeof(TickLabel) + strlen(string)); assert(labelPtr); strcpy(labelPtr->string, string); labelPtr->anchorPos.x = labelPtr->anchorPos.y = DBL_MAX; return labelPtr; } /* * ---------------------------------------------------------------------- * * Blt_InvHMap -- * * Maps the given screen coordinate back to a graph coordinate. * Called by the graph locater routine. * * Results: * Returns the graph coordinate value at the given window * y-coordinate. * * ---------------------------------------------------------------------- */ double Blt_InvHMap(graphPtr, axisPtr, x) Graph *graphPtr; Axis *axisPtr; double x; { double value; x = (double)(x - graphPtr->hOffset) * graphPtr->hScale; if (axisPtr->descending) { x = 1.0 - x; } value = (x * axisPtr->axisRange.range) + axisPtr->axisRange.min; if (axisPtr->logScale) { value = EXP10(value); } return value; } /* * ---------------------------------------------------------------------- * * Blt_InvVMap -- * * Maps the given window y-coordinate back to a graph coordinate * value. Called by the graph locater routine. * * Results: * Returns the graph coordinate value at the given window * y-coordinate. * * ---------------------------------------------------------------------- */ double Blt_InvVMap(graphPtr, axisPtr, y) Graph *graphPtr; Axis *axisPtr; double y; { double value; y = (double)(y - graphPtr->vOffset) * graphPtr->vScale; if (axisPtr->descending) { y = 1.0 - y; } value = ((1.0 - y) * axisPtr->axisRange.range) + axisPtr->axisRange.min; if (axisPtr->logScale) { value = EXP10(value); } return value; } /* * ---------------------------------------------------------------------- * * Blt_HMap -- * * Map the given graph coordinate value to its axis, returning a window * position. * * Results: * Returns a double precision number representing the window coordinate * position on the given axis. * * ---------------------------------------------------------------------- */ double Blt_HMap(graphPtr, axisPtr, x) Graph *graphPtr; Axis *axisPtr; double x; { if ((axisPtr->logScale) && (x != 0.0)) { x = log10(FABS(x)); } /* Map graph coordinate to normalized coordinates [0..1] */ x = (x - axisPtr->axisRange.min) * axisPtr->axisRange.scale; if (axisPtr->descending) { x = 1.0 - x; } return (x * graphPtr->hRange + graphPtr->hOffset); } /* * ---------------------------------------------------------------------- * * Blt_VMap -- * * Map the given graph coordinate value to its axis, returning a window * position. * * Results: * Returns a double precision number representing the window coordinate * position on the given axis. * * ---------------------------------------------------------------------- */ double Blt_VMap(graphPtr, axisPtr, y) Graph *graphPtr; Axis *axisPtr; double y; { if ((axisPtr->logScale) && (y != 0.0)) { y = log10(FABS(y)); } /* Map graph coordinate to normalized coordinates [0..1] */ y = (y - axisPtr->axisRange.min) * axisPtr->axisRange.scale; if (axisPtr->descending) { y = 1.0 - y; } return (((1.0 - y) * graphPtr->vRange) + graphPtr->vOffset); } /* * ---------------------------------------------------------------------- * * Blt_Map2D -- * * Maps the given graph x,y coordinate values to a window position. * * Results: * Returns a XPoint structure containing the window coordinates of * the given graph x,y coordinate. * * ---------------------------------------------------------------------- */ Point2D Blt_Map2D(graphPtr, x, y, axesPtr) Graph *graphPtr; double x, y; /* Graph x and y coordinates */ Axis2D *axesPtr; /* Specifies which axes to use */ { Point2D point; if (graphPtr->inverted) { point.x = Blt_HMap(graphPtr, axesPtr->y, y); point.y = Blt_VMap(graphPtr, axesPtr->x, x); } else { point.x = Blt_HMap(graphPtr, axesPtr->x, x); point.y = Blt_VMap(graphPtr, axesPtr->y, y); } return point; } /* * ---------------------------------------------------------------------- * * Blt_InvMap2D -- * * Maps the given window x,y coordinates to graph values. * * Results: * Returns a structure containing the graph coordinates of * the given window x,y coordinate. * * ---------------------------------------------------------------------- */ Point2D Blt_InvMap2D(graphPtr, x, y, axesPtr) Graph *graphPtr; double x, y; /* Window x and y coordinates */ Axis2D *axesPtr; /* Specifies which axes to use */ { Point2D point; if (graphPtr->inverted) { point.x = Blt_InvVMap(graphPtr, axesPtr->x, y); point.y = Blt_InvHMap(graphPtr, axesPtr->y, x); } else { point.x = Blt_InvHMap(graphPtr, axesPtr->x, x); point.y = Blt_InvVMap(graphPtr, axesPtr->y, y); } return point; } static void GetDataLimits(axisPtr, min, max) Axis *axisPtr; double min, max; { if (axisPtr->valueRange.min > min) { axisPtr->valueRange.min = min; } if (axisPtr->valueRange.max < max) { axisPtr->valueRange.max = max; } } static void FixAxisRange(axisPtr) Axis *axisPtr; { double min, max; /* * When auto-scaling, the axis limits are the bounds of the element * data. If no data exists, set arbitrary limits (wrt to log/linear * scale). */ min = axisPtr->valueRange.min; max = axisPtr->valueRange.max; if (min == DBL_MAX) { if (DEFINED(axisPtr->reqMin)) { min = axisPtr->reqMin; } else { min = (axisPtr->logScale) ? 0.001 : 0.0; } } if (max == -DBL_MAX) { if (DEFINED(axisPtr->reqMax)) { max = axisPtr->reqMax; } else { max = 1.0; } } if (min >= max) { double value; /* * There is no range of data (i.e. min is not less than max), * so manufacture one. */ value = min; if (value == 0.0) { min = -0.1, max = 0.1; } else { double x; x = FABS(value) * 0.1; min = value - x, max = value + x; } } SetAxisRange(&axisPtr->valueRange, min, max); /* * The axis limits are either the current data range or overridden * by the values selected by the user with the -min or -max * options. */ axisPtr->min = min; axisPtr->max = max; if (DEFINED(axisPtr->reqMin)) { axisPtr->min = axisPtr->reqMin; } if (DEFINED(axisPtr->reqMax)) { axisPtr->max = axisPtr->reqMax; } if (axisPtr->max < axisPtr->min) { /* * If the limits still don't make sense, it's because one * limit configuration option (-min or -max) was set and the * other default (based upon the data) is too small or large. * Remedy this by making up a new min or max from the * user-defined limit. */ if (!DEFINED(axisPtr->reqMin)) { axisPtr->min = axisPtr->max - (FABS(axisPtr->max) * 0.1); } if (!DEFINED(axisPtr->reqMax)) { axisPtr->max = axisPtr->min + (FABS(axisPtr->max) * 0.1); } } /* * If a window size is defined, handle auto ranging by shifting * the axis limits. */ if ((axisPtr->windowSize > 0.0) && (!DEFINED(axisPtr->reqMin)) && (!DEFINED(axisPtr->reqMax))) { if (axisPtr->shiftBy < 0.0) { axisPtr->shiftBy = 0.0; } max = axisPtr->min + axisPtr->windowSize; if (axisPtr->max >= max) { if (axisPtr->shiftBy > 0.0) { max = UCEIL(axisPtr->max, axisPtr->shiftBy); } axisPtr->min = max - axisPtr->windowSize; } axisPtr->max = max; } if ((axisPtr->max != axisPtr->prevMax) || (axisPtr->min != axisPtr->prevMin)) { /* Indicate if the axis limits have changed */ axisPtr->flags |= AXIS_DIRTY; /* and save the previous minimum and maximum values */ axisPtr->prevMin = axisPtr->min; axisPtr->prevMax = axisPtr->max; } } /* * ---------------------------------------------------------------------- * * NiceNum -- * * Reference: Paul Heckbert, "Nice Numbers for Graph Labels", * Graphics Gems, pp 61-63. * * Finds a "nice" number approximately equal to x. * * ---------------------------------------------------------------------- */ static double NiceNum(x, round) double x; int round; /* If non-zero, round. Otherwise take ceiling * of value. */ { double expt; /* Exponent of x */ double frac; /* Fractional part of x */ double nice; /* Nice, rounded fraction */ expt = floor(log10(x)); frac = x / EXP10(expt); /* between 1 and 10 */ if (round) { if (frac < 1.5) { nice = 1.0; } else if (frac < 3.0) { nice = 2.0; } else if (frac < 7.0) { nice = 5.0; } else { nice = 10.0; } } else { if (frac <= 1.0) { nice = 1.0; } else if (frac <= 2.0) { nice = 2.0; } else if (frac <= 5.0) { nice = 5.0; } else { nice = 10.0; } } return nice * EXP10(expt); } static Ticks * GenerateTicks(sweepPtr) TickSweep *sweepPtr; { Ticks *ticksPtr; register int i; ticksPtr = Blt_Malloc(sizeof(Ticks) + (sweepPtr->nSteps * sizeof(double))); assert(ticksPtr); if (sweepPtr->step == 0.0) { static double logTable[] = /* Precomputed log10 values [1..10] */ { 0.0, 0.301029995663981, 0.477121254719662, 0.602059991327962, 0.698970004336019, 0.778151250383644, 0.845098040014257, 0.903089986991944, 0.954242509439325, 1.0 }; /* Hack: A zero step indicates to use log values. */ for (i = 0; i < sweepPtr->nSteps; i++) { ticksPtr->values[i] = logTable[i]; } } else { double value; value = sweepPtr->initial; /* Start from smallest axis tick */ for (i = 0; i < sweepPtr->nSteps; i++) { value = UROUND(value, sweepPtr->step); ticksPtr->values[i] = value; value += sweepPtr->step; } } ticksPtr->nTicks = sweepPtr->nSteps; return ticksPtr; } /* * ---------------------------------------------------------------------- * * LogScaleAxis -- * * Determine the range and units of a log scaled axis. * * Unless the axis limits are specified, the axis is scaled * automatically, where the smallest and largest major ticks encompass * the range of actual data values. When an axis limit is specified, * that value represents the smallest(min)/largest(max) value in the * displayed range of values. * * Both manual and automatic scaling are affected by the step used. By * default, the step is the largest power of ten to divide the range in * more than one piece. * * Automatic scaling: * Find the smallest number of units which contain the range of values. * The minimum and maximum major tick values will be represent the * range of values for the axis. This greatest number of major ticks * possible is 10. * * Manual scaling: * Make the minimum and maximum data values the represent the range of * the values for the axis. The minimum and maximum major ticks will be * inclusive of this range. This provides the largest area for plotting * and the expected results when the axis min and max values have be set * by the user (.e.g zooming). The maximum number of major ticks is 20. * * For log scale, there's the possibility that the minimum and * maximum data values are the same magnitude. To represent the * points properly, at least one full decade should be shown. * However, if you zoom a log scale plot, the results should be * predictable. Therefore, in that case, show only minor ticks. * Lastly, there should be an appropriate way to handle numbers * <=0. * * maxY * | units = magnitude (of least significant digit) * | high = largest unit tick < max axis value * high _| low = smallest unit tick > min axis value * | * | range = high - low * | # ticks = greatest factor of range/units * _| * U | * n | * i | * t _| * | * | * | * low _| * | * |_minX________________maxX__ * | | | | | * minY low high * minY * * * numTicks = Number of ticks * min = Minimum value of axis * max = Maximum value of axis * range = Range of values (max - min) * * If the number of decades is greater than ten, it is assumed * that the full set of log-style ticks can't be drawn properly. * * Results: * None * * ---------------------------------------------------------------------- */ static void LogScaleAxis(axisPtr, min, max) Axis *axisPtr; double min, max; { double range; double tickMin, tickMax; double majorStep, minorStep; int nMajor, nMinor; nMajor = nMinor = 0; majorStep = minorStep = 0.0; if (min < max) { min = (min != 0.0) ? log10(FABS(min)) : 0.0; max = (max != 0.0) ? log10(FABS(max)) : 1.0; tickMin = floor(min); tickMax = ceil(max); range = tickMax - tickMin; if (range > 10) { /* There are too many decades to display a major tick at every * decade. Instead, treat the axis as a linear scale. */ range = NiceNum(range, 0); majorStep = NiceNum(range / DEF_NUM_TICKS, 1); tickMin = UFLOOR(tickMin, majorStep); tickMax = UCEIL(tickMax, majorStep); nMajor = (int)((tickMax - tickMin) / majorStep) + 1; minorStep = EXP10(floor(log10(majorStep))); if (minorStep == majorStep) { nMinor = 4, minorStep = 0.2; } else { nMinor = Round(majorStep / minorStep) - 1; } } else { if (tickMin == tickMax) { tickMax++; } majorStep = 1.0; nMajor = (int)(tickMax - tickMin + 1); /* FIXME: Check this. */ minorStep = 0.0; /* This is a special hack to pass * information to the GenerateTicks * routine. An interval of 0.0 tells * 1) this is a minor sweep and * 2) the axis is log scale. */ nMinor = 10; } if ((axisPtr->looseMin == TICK_RANGE_TIGHT) || ((axisPtr->looseMin == TICK_RANGE_LOOSE) && (DEFINED(axisPtr->reqMin)))) { tickMin = min; nMajor++; } if ((axisPtr->looseMax == TICK_RANGE_TIGHT) || ((axisPtr->looseMax == TICK_RANGE_LOOSE) && (DEFINED(axisPtr->reqMax)))) { tickMax = max; } } axisPtr->majorSweep.step = majorStep; axisPtr->majorSweep.initial = floor(tickMin); axisPtr->majorSweep.nSteps = nMajor; axisPtr->minorSweep.initial = axisPtr->minorSweep.step = minorStep; axisPtr->minorSweep.nSteps = nMinor; SetAxisRange(&axisPtr->axisRange, tickMin, tickMax); } /* * ---------------------------------------------------------------------- * * LinearScaleAxis -- * * Determine the units of a linear scaled axis. * * The axis limits are either the range of the data values mapped * to the axis (autoscaled), or the values specified by the -min * and -max options (manual). * * If autoscaled, the smallest and largest major ticks will * encompass the range of data values. If the -loose option is * selected, the next outer ticks are choosen. If tight, the * ticks are at or inside of the data limits are used. * * If manually set, the ticks are at or inside the data limits * are used. This makes sense for zooming. You want the * selected range to represent the next limit, not something a * bit bigger. * * Note: I added an "always" value to the -loose option to force * the manually selected axes to be loose. It's probably * not a good idea. * * maxY * | units = magnitude (of least significant digit) * | high = largest unit tick < max axis value * high _| low = smallest unit tick > min axis value * | * | range = high - low * | # ticks = greatest factor of range/units * _| * U | * n | * i | * t _| * | * | * | * low _| * | * |_minX________________maxX__ * | | | | | * minY low high * minY * * numTicks = Number of ticks * min = Minimum value of axis * max = Maximum value of axis * range = Range of values (max - min) * * Results: * None. * * Side Effects: * The axis tick information is set. The actual tick values will * be generated later. * * ---------------------------------------------------------------------- */ static void LinearScaleAxis(axisPtr, min, max) Axis *axisPtr; double min, max; { double range, step; double tickMin, tickMax; double axisMin, axisMax; int nTicks; nTicks = 0; tickMin = tickMax = 0.0; if (min < max) { range = max - min; /* Calculate the major tick stepping. */ if (axisPtr->reqStep > 0.0) { /* An interval was designated by the user. Keep scaling it * until it fits comfortably within the current range of the * axis. */ step = axisPtr->reqStep; while ((2 * step) >= range) { step *= 0.5; } } else { range = NiceNum(range, 0); step = NiceNum(range / DEF_NUM_TICKS, 1); } /* Find the outer tick values. Add 0.0 to prevent getting -0.0. */ axisMin = tickMin = floor(min / step) * step + 0.0; axisMax = tickMax = ceil(max / step) * step + 0.0; nTicks = Round((tickMax - tickMin) / step) + 1; } axisPtr->majorSweep.step = step; axisPtr->majorSweep.initial = tickMin; axisPtr->majorSweep.nSteps = nTicks; /* * The limits of the axis are either the range of the data * ("tight") or at the next outer tick interval ("loose"). The * looseness or tightness has to do with how the axis fits the * range of data values. This option is overridden when * the user sets an axis limit (by either -min or -max option). * The axis limit is always at the selected limit (otherwise we * assume that user would have picked a different number). */ if ((axisPtr->looseMin == TICK_RANGE_TIGHT) || ((axisPtr->looseMin == TICK_RANGE_LOOSE) && (DEFINED(axisPtr->reqMin)))) { axisMin = min; } if ((axisPtr->looseMax == TICK_RANGE_TIGHT) || ((axisPtr->looseMax == TICK_RANGE_LOOSE) && (DEFINED(axisPtr->reqMax)))) { axisMax = max; } SetAxisRange(&axisPtr->axisRange, axisMin, axisMax); /* Now calculate the minor tick step and number. */ if ((axisPtr->reqNumMinorTicks > 0) && ((axisPtr->flags & AXIS_CONFIG_MAJOR) == 0)) { nTicks = axisPtr->reqNumMinorTicks - 1; step = 1.0 / (nTicks + 1); } else { nTicks = 0; /* No minor ticks. */ step = 0.5; /* Don't set the minor tick interval * to 0.0. It makes the GenerateTicks * routine create minor log-scale tick * marks. */ } axisPtr->minorSweep.initial = axisPtr->minorSweep.step = step; axisPtr->minorSweep.nSteps = nTicks; } static void SweepTicks(axisPtr) Axis *axisPtr; { if ((axisPtr->flags & AXIS_CONFIG_MAJOR) == 0) { if (axisPtr->t1Ptr != NULL) { Blt_Free(axisPtr->t1Ptr); } axisPtr->t1Ptr = GenerateTicks(&axisPtr->majorSweep); } if ((axisPtr->flags & AXIS_CONFIG_MINOR) == 0) { if (axisPtr->t2Ptr != NULL) { Blt_Free(axisPtr->t2Ptr); } axisPtr->t2Ptr = GenerateTicks(&axisPtr->minorSweep); } } /* * ---------------------------------------------------------------------- * * Blt_ResetAxes -- * * Results: * None. * * ---------------------------------------------------------------------- */ void Blt_ResetAxes(graphPtr) Graph *graphPtr; { Blt_ChainLink *linkPtr; Element *elemPtr; Axis *axisPtr; Blt_HashEntry *hPtr; Blt_HashSearch cursor; Extents2D exts; double min, max; /* 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_InitFreqTable(graphPtr); if ((graphPtr->mode == MODE_STACKED) && (graphPtr->nStacks > 0)) { Blt_ComputeStacks(graphPtr); } /* * Step 1: Reset all axes. Initialize the data limits of the axis to * impossible values. */ for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { axisPtr = (Axis *)Blt_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 (linkPtr = Blt_ChainFirstLink(graphPtr->elements.displayList); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { elemPtr = Blt_ChainGetValue(linkPtr); if (!elemPtr->hidden) { (*elemPtr->procsPtr->extentsProc) (elemPtr, &exts); GetDataLimits(elemPtr->axes.x, exts.left, exts.right); GetDataLimits(elemPtr->axes.y, 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 = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { axisPtr = (Axis *)Blt_GetHashValue(hPtr); FixAxisRange(axisPtr); /* Calculate min/max tick (major/minor) layouts */ min = axisPtr->min; max = axisPtr->max; if ((DEFINED(axisPtr->scrollMin)) && (min < axisPtr->scrollMin)) { min = axisPtr->scrollMin; } if ((DEFINED(axisPtr->scrollMax)) && (max > axisPtr->scrollMax)) { max = axisPtr->scrollMax; } if (axisPtr->logScale) { LogScaleAxis(axisPtr, min, max); } else { LinearScaleAxis(axisPtr, min, max); } if ((axisPtr->flags & (AXIS_DIRTY | AXIS_ONSCREEN)) == (AXIS_DIRTY | AXIS_ONSCREEN)) { graphPtr->flags |= REDRAW_BACKING_STORE; } } 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); } /* * ---------------------------------------------------------------------- * * ResetTextStyles -- * * Configures axis attributes (font, line width, label, etc) and * allocates a new (possibly shared) graphics context. Line cap * style is projecting. This is for the problem of when a tick * sits directly at the end point of the axis. * * Results: * The return value is a standard Tcl result. * * Side Effects: * Axis resources are allocated (GC, font). Axis layout is * deferred until the height and width of the window are known. * * ---------------------------------------------------------------------- */ static void ResetTextStyles(graphPtr, axisPtr) Graph *graphPtr; Axis *axisPtr; { GC newGC; XGCValues gcValues; unsigned long gcMask; Blt_ResetTextStyle(graphPtr->tkwin, &axisPtr->titleTextStyle); Blt_ResetTextStyle(graphPtr->tkwin, &axisPtr->tickTextStyle); Blt_ResetTextStyle(graphPtr->tkwin, &axisPtr->limitsTextStyle); gcMask = (GCForeground | GCLineWidth | GCCapStyle); gcValues.foreground = axisPtr->tickTextStyle.color->pixel; gcValues.line_width = LineWidth(axisPtr->lineWidth); gcValues.cap_style = CapProjecting; newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues); if (axisPtr->tickGC != NULL) { Tk_FreeGC(graphPtr->display, axisPtr->tickGC); } axisPtr->tickGC = newGC; } /* * ---------------------------------------------------------------------- * * DestroyAxis -- * * Results: * None. * * Side effects: * Resources (font, color, gc, labels, etc.) associated with the * axis are deallocated. * * ---------------------------------------------------------------------- */ static void DestroyAxis(graphPtr, axisPtr) Graph *graphPtr; Axis *axisPtr; { int flags; flags = Blt_GraphType(graphPtr); Tk_FreeOptions(configSpecs, (char *)axisPtr, graphPtr->display, flags); if (graphPtr->bindTable != NULL) { Blt_DeleteBindings(graphPtr->bindTable, axisPtr); } if (axisPtr->linkPtr != NULL) { Blt_ChainDeleteLink(axisPtr->chainPtr, axisPtr->linkPtr); } if (axisPtr->name != NULL) { Blt_Free(axisPtr->name); } if (axisPtr->hashPtr != NULL) { Blt_DeleteHashEntry(&graphPtr->axes.table, axisPtr->hashPtr); } Blt_FreeTextStyle(graphPtr->display, &axisPtr->titleTextStyle); Blt_FreeTextStyle(graphPtr->display, &axisPtr->limitsTextStyle); Blt_FreeTextStyle(graphPtr->display, &axisPtr->tickTextStyle); if (axisPtr->tickGC != NULL) { Tk_FreeGC(graphPtr->display, axisPtr->tickGC); } if (axisPtr->t1Ptr != NULL) { Blt_Free(axisPtr->t1Ptr); } if (axisPtr->t2Ptr != NULL) { Blt_Free(axisPtr->t2Ptr); } if (axisPtr->limitsFormats != NULL) { Blt_Free(axisPtr->limitsFormats); } FreeLabels(axisPtr->tickLabels); Blt_ChainDestroy(axisPtr->tickLabels); if (axisPtr->segments != NULL) { Blt_Free(axisPtr->segments); } if (axisPtr->tags != NULL) { Blt_Free(axisPtr->tags); } Blt_Free(axisPtr); } static double titleRotate[4] = /* Rotation for each axis title */ { 0.0, 90.0, 0.0, 270.0 }; /* * ---------------------------------------------------------------------- * * AxisOffsets -- * * Determines the sites of the axis, major and minor ticks, * and title of the axis. * * Results: * None. * * ---------------------------------------------------------------------- */ static void AxisOffsets(graphPtr, axisPtr, margin, axisOffset, infoPtr) Graph *graphPtr; Axis *axisPtr; int margin; int axisOffset; AxisInfo *infoPtr; { int pad; /* Offset of axis from interior region. This * includes a possible border and the axis * line width. */ int p; int majorOffset, minorOffset, labelOffset; int offset; int x, y; axisPtr->titleTextStyle.theta = titleRotate[margin]; majorOffset = minorOffset = 0; labelOffset = AXIS_TITLE_PAD; if (axisPtr->lineWidth > 0) { majorOffset = ABS(axisPtr->tickLength); minorOffset = 10 * majorOffset / 15; labelOffset = majorOffset + AXIS_TITLE_PAD + axisPtr->lineWidth / 2; } /* Adjust offset for the interior border width and the line width */ pad = axisPtr->lineWidth + 1; if (graphPtr->plotBorderWidth > 0) { pad += graphPtr->plotBorderWidth + 1; } offset = axisOffset + 1 + pad; if ((margin == MARGIN_LEFT) || (margin == MARGIN_TOP)) { majorOffset = -majorOffset; minorOffset = -minorOffset; labelOffset = -labelOffset; } /* * Pre-calculate the x-coordinate positions of the axis, tick labels, and * the individual major and minor ticks. */ p = 0; /* Suppress compiler warning */ switch (margin) { case MARGIN_TOP: p = graphPtr->top - axisOffset - pad; if (axisPtr->titleAlternate) { x = graphPtr->right + AXIS_TITLE_PAD; y = graphPtr->top - axisOffset - (axisPtr->height / 2); axisPtr->titleTextStyle.anchor = TK_ANCHOR_W; } else { x = (graphPtr->right + graphPtr->left) / 2; y = graphPtr->top - axisOffset - axisPtr->height - AXIS_TITLE_PAD; axisPtr->titleTextStyle.anchor = TK_ANCHOR_N; } axisPtr->tickTextStyle.anchor = TK_ANCHOR_S; offset = axisPtr->borderWidth + axisPtr->lineWidth / 2; axisPtr->region.left = graphPtr->hOffset - offset - 2; axisPtr->region.right = graphPtr->hOffset + graphPtr->hRange + offset - 1; axisPtr->region.top = p + labelOffset - 1; axisPtr->region.bottom = p; axisPtr->titlePos.x = x; axisPtr->titlePos.y = y; break; case MARGIN_BOTTOM: p = graphPtr->bottom + axisOffset + pad; if (axisPtr->titleAlternate) { x = graphPtr->right + AXIS_TITLE_PAD; y = graphPtr->bottom + axisOffset + (axisPtr->height / 2); axisPtr->titleTextStyle.anchor = TK_ANCHOR_W; } else { x = (graphPtr->right + graphPtr->left) / 2; y = graphPtr->bottom + axisOffset + axisPtr->height + AXIS_TITLE_PAD; axisPtr->titleTextStyle.anchor = TK_ANCHOR_S; } axisPtr->tickTextStyle.anchor = TK_ANCHOR_N; offset = axisPtr->borderWidth + axisPtr->lineWidth / 2; axisPtr->region.left = graphPtr->hOffset - offset - 2; axisPtr->region.right = graphPtr->hOffset + graphPtr->hRange + offset - 1; axisPtr->region.top = graphPtr->bottom + axisOffset + axisPtr->lineWidth - axisPtr->lineWidth / 2; axisPtr->region.bottom = graphPtr->bottom + axisOffset + axisPtr->lineWidth + labelOffset + 1; axisPtr->titlePos.x = x; axisPtr->titlePos.y = y; break; case MARGIN_LEFT: p = graphPtr->left - axisOffset - pad; if (axisPtr->titleAlternate) { x = graphPtr->left - axisOffset - (axisPtr->width / 2); y = graphPtr->top - AXIS_TITLE_PAD; axisPtr->titleTextStyle.anchor = TK_ANCHOR_SW; } else { x = graphPtr->left - axisOffset - axisPtr->width - graphPtr->plotBorderWidth; y = (graphPtr->bottom + graphPtr->top) / 2; axisPtr->titleTextStyle.anchor = TK_ANCHOR_W; } axisPtr->tickTextStyle.anchor = TK_ANCHOR_E; axisPtr->region.left = graphPtr->left - offset + labelOffset - 1; axisPtr->region.right = graphPtr->left - offset + 2; offset = axisPtr->borderWidth + axisPtr->lineWidth / 2; axisPtr->region.top = graphPtr->vOffset - offset - 2; axisPtr->region.bottom = graphPtr->vOffset + graphPtr->vRange + offset - 1; axisPtr->titlePos.x = x; axisPtr->titlePos.y = y; break; case MARGIN_RIGHT: p = graphPtr->right + axisOffset + pad; if (axisPtr->titleAlternate) { x = graphPtr->right + axisOffset + (axisPtr->width / 2); y = graphPtr->top - AXIS_TITLE_PAD; axisPtr->titleTextStyle.anchor = TK_ANCHOR_SE; } else { x = graphPtr->right + axisOffset + axisPtr->width + AXIS_TITLE_PAD; y = (graphPtr->bottom + graphPtr->top) / 2; axisPtr->titleTextStyle.anchor = TK_ANCHOR_E; } axisPtr->tickTextStyle.anchor = TK_ANCHOR_W; axisPtr->region.left = graphPtr->right + axisOffset + axisPtr->lineWidth - axisPtr->lineWidth / 2; axisPtr->region.right = graphPtr->right + axisOffset + labelOffset + axisPtr->lineWidth + 1; offset = axisPtr->borderWidth + axisPtr->lineWidth / 2; axisPtr->region.top = graphPtr->vOffset - offset - 2; axisPtr->region.bottom = graphPtr->vOffset + graphPtr->vRange + offset - 1; axisPtr->titlePos.x = x; axisPtr->titlePos.y = y; break; case MARGIN_NONE: break; } infoPtr->axis = p - (axisPtr->lineWidth / 2); infoPtr->t1 = p + majorOffset; infoPtr->t2 = p + minorOffset; infoPtr->label = p + labelOffset; if (axisPtr->tickLength < 0) { int hold; hold = infoPtr->t1; infoPtr->t1 = infoPtr->axis; infoPtr->axis = hold; } } static void MakeAxisLine(graphPtr, axisPtr, line, segPtr) Graph *graphPtr; Axis *axisPtr; /* Axis information */ int line; Segment2D *segPtr; { double min, max; min = axisPtr->axisRange.min; max = axisPtr->axisRange.max; if (axisPtr->logScale) { min = EXP10(min); max = EXP10(max); } if (AxisIsHorizontal(graphPtr, axisPtr)) { segPtr->p.x = Blt_HMap(graphPtr, axisPtr, min); segPtr->q.x = Blt_HMap(graphPtr, axisPtr, max); segPtr->p.y = segPtr->q.y = line; } else { segPtr->q.x = segPtr->p.x = line; segPtr->p.y = Blt_VMap(graphPtr, axisPtr, min); segPtr->q.y = Blt_VMap(graphPtr, axisPtr, max); } } static void MakeTick(graphPtr, axisPtr, value, tick, line, segPtr) Graph *graphPtr; Axis *axisPtr; double value; int tick, line; /* Lengths of tick and axis line. */ Segment2D *segPtr; { if (axisPtr->logScale) { value = EXP10(value); } if (AxisIsHorizontal(graphPtr, axisPtr)) { segPtr->p.x = segPtr->q.x = Blt_HMap(graphPtr, axisPtr, value); segPtr->p.y = line; segPtr->q.y = tick; } else { segPtr->p.x = line; segPtr->p.y = segPtr->q.y = Blt_VMap(graphPtr, axisPtr, value); segPtr->q.x = tick; } } /* * ----------------------------------------------------------------- * * MapAxis -- * * Pre-calculates positions of the axis, ticks, and labels (to be * used later when displaying the axis). Calculates the values * for each major and minor tick and checks to see if they are in * range (the outer ticks may be outside of the range of plotted * values). * * Line segments for the minor and major ticks are saved into one * XSegment array so that they can be drawn by a single * XDrawSegments call. The positions of the tick labels are also * computed and saved. * * Results: * None. * * Side Effects: * Line segments and tick labels are saved and used later to draw * the axis. * * ----------------------------------------------------------------- */ static void MapAxis(graphPtr, axisPtr, offset, margin) Graph *graphPtr; Axis *axisPtr; int offset; int margin; { int arraySize; int nMajorTicks, nMinorTicks; AxisInfo info; Segment2D *segments; Segment2D *segPtr; AxisOffsets(graphPtr, axisPtr, margin, offset, &info); /* Save all line coordinates in an array of line segments. */ if (axisPtr->segments != NULL) { Blt_Free(axisPtr->segments); } nMajorTicks = nMinorTicks = 0; if (axisPtr->t1Ptr != NULL) { nMajorTicks = axisPtr->t1Ptr->nTicks; } if (axisPtr->t2Ptr != NULL) { nMinorTicks = axisPtr->t2Ptr->nTicks; } arraySize = 1 + (nMajorTicks * (nMinorTicks + 1)); segments = Blt_Malloc(arraySize * sizeof(Segment2D)); assert(segments); segPtr = segments; if (axisPtr->lineWidth > 0) { /* Axis baseline */ MakeAxisLine(graphPtr, axisPtr, info.axis, segPtr); segPtr++; } if (axisPtr->showTicks) { double t1, t2; double labelPos; register int i, j; int isHoriz; TickLabel *labelPtr; Blt_ChainLink *linkPtr; Segment2D seg; isHoriz = AxisIsHorizontal(graphPtr, axisPtr); for (i = 0; i < axisPtr->t1Ptr->nTicks; i++) { t1 = axisPtr->t1Ptr->values[i]; /* Minor ticks */ for (j = 0; j < axisPtr->t2Ptr->nTicks; j++) { t2 = t1 + (axisPtr->majorSweep.step * axisPtr->t2Ptr->values[j]); if (InRange(t2, &axisPtr->axisRange)) { MakeTick(graphPtr, axisPtr, t2, info.t2, info.axis, segPtr); segPtr++; } } if (!InRange(t1, &axisPtr->axisRange)) { continue; } /* Major tick */ MakeTick(graphPtr, axisPtr, t1, info.t1, info.axis, segPtr); segPtr++; } linkPtr = Blt_ChainFirstLink(axisPtr->tickLabels); labelPos = (double)info.label; for (i = 0; i < axisPtr->t1Ptr->nTicks; i++) { t1 = axisPtr->t1Ptr->values[i]; if (axisPtr->labelOffset) { t1 += axisPtr->majorSweep.step * 0.5; } if (!InRange(t1, &axisPtr->axisRange)) { continue; } labelPtr = Blt_ChainGetValue(linkPtr); linkPtr = Blt_ChainNextLink(linkPtr); MakeTick(graphPtr, axisPtr, t1, info.t1, info.axis, &seg); /* Save tick label X-Y position. */ if (isHoriz) { labelPtr->anchorPos.x = seg.p.x; labelPtr->anchorPos.y = labelPos; } else { labelPtr->anchorPos.x = labelPos; labelPtr->anchorPos.y = seg.p.y; } } } if (AxisIsHorizontal(graphPtr, axisPtr)) { axisPtr->width = graphPtr->right - graphPtr->left; } else { axisPtr->height = graphPtr->bottom - graphPtr->top; } axisPtr->segments = segments; axisPtr->nSegments = segPtr - segments; assert(axisPtr->nSegments <= arraySize); } /* *---------------------------------------------------------------------- * * AdjustViewport -- * * Adjusts the offsets of the viewport according to the scroll mode. * This is to accommodate both "listbox" and "canvas" style scrolling. * * "canvas" The viewport scrolls within the range of world * coordinates. This way the viewport always displays * a full page of the world. If the world is smaller * than the viewport, then (bizarrely) the world and * viewport are inverted so that the world moves up * and down within the viewport. * * "listbox" The viewport can scroll beyond the range of world * coordinates. Every entry can be displayed at the * top of the viewport. This also means that the * scrollbar thumb weirdly shrinks as the last entry * is scrolled upward. * * Results: * The corrected offset is returned. * *---------------------------------------------------------------------- */ static double AdjustViewport(offset, windowSize) double offset, 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; } static int GetAxisScrollInfo(interp, argc, argv, offsetPtr, windowSize, scrollUnits) Tcl_Interp *interp; int argc; char **argv; double *offsetPtr; double windowSize; double scrollUnits; { char c; unsigned int length; double offset; int count; double fract; offset = *offsetPtr; c = argv[0][0]; length = strlen(argv[0]); if ((c == 's') && (strncmp(argv[0], "scroll", length) == 0)) { assert(argc == 3); /* scroll number unit/page */ if (Tcl_GetInt(interp, argv[1], &count) != TCL_OK) { return TCL_ERROR; } c = argv[2][0]; length = strlen(argv[2]); if ((c == 'u') && (strncmp(argv[2], "units", length) == 0)) { fract = (double)count * scrollUnits; } else if ((c == 'p') && (strncmp(argv[2], "pages", length) == 0)) { /* A page is 90% of the view-able window. */ fract = (double)count * windowSize * 0.9; } else { Tcl_AppendResult(interp, "unknown \"scroll\" units \"", argv[2], "\"", (char *)NULL); return TCL_ERROR; } offset += fract; } else if ((c == 'm') && (strncmp(argv[0], "moveto", length) == 0)) { assert(argc == 2); /* moveto fraction */ if (Tcl_GetDouble(interp, argv[1], &fract) != TCL_OK) { return TCL_ERROR; } offset = fract; } else { /* Treat like "scroll units" */ if (Tcl_GetInt(interp, argv[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; } /* * ----------------------------------------------------------------- * * DrawAxis -- * * Draws the axis, ticks, and labels onto the canvas. * * Initializes and passes text attribute information through * TextStyle structure. * * Results: * None. * * Side Effects: * Axis gets drawn on window. * * ----------------------------------------------------------------- */ static void DrawAxis(graphPtr, drawable, axisPtr) Graph *graphPtr; Drawable drawable; Axis *axisPtr; { if (axisPtr->border != NULL) { Blt_Fill3DRectangle(graphPtr->tkwin, drawable, axisPtr->border, axisPtr->region.left + graphPtr->plotBorderWidth, axisPtr->region.top + graphPtr->plotBorderWidth, axisPtr->region.right - axisPtr->region.left, axisPtr->region.bottom - axisPtr->region.top, axisPtr->borderWidth, axisPtr->relief); } if (axisPtr->title != NULL) { Blt_DrawText(graphPtr->tkwin, drawable, axisPtr->title, &axisPtr->titleTextStyle, (int)axisPtr->titlePos.x, (int)axisPtr->titlePos.y); } if (axisPtr->scrollCmdPrefix != NULL) { double viewWidth, viewMin, viewMax; double worldWidth, worldMin, worldMax; double fract; int isHoriz; worldMin = axisPtr->valueRange.min; worldMax = axisPtr->valueRange.max; if (DEFINED(axisPtr->scrollMin)) { worldMin = axisPtr->scrollMin; } if (DEFINED(axisPtr->scrollMax)) { worldMax = axisPtr->scrollMax; } viewMin = axisPtr->min; viewMax = axisPtr->max; if (viewMin < worldMin) { viewMin = worldMin; } if (viewMax > worldMax) { viewMax = worldMax; } if (axisPtr->logScale) { worldMin = log10(worldMin); worldMax = log10(worldMax); viewMin = log10(viewMin); viewMax = log10(viewMax); } worldWidth = worldMax - worldMin; viewWidth = viewMax - viewMin; isHoriz = AxisIsHorizontal(graphPtr, axisPtr); if (isHoriz != axisPtr->descending) { fract = (viewMin - worldMin) / worldWidth; } else { fract = (worldMax - viewMax) / worldWidth; } fract = AdjustViewport(fract, viewWidth / worldWidth); if (isHoriz != axisPtr->descending) { viewMin = (fract * worldWidth); axisPtr->min = viewMin + worldMin; axisPtr->max = axisPtr->min + viewWidth; viewMax = viewMin + viewWidth; if (axisPtr->logScale) { axisPtr->min = EXP10(axisPtr->min); axisPtr->max = EXP10(axisPtr->max); } Blt_UpdateScrollbar(graphPtr->interp, axisPtr->scrollCmdPrefix, (viewMin / worldWidth), (viewMax / worldWidth)); } else { viewMax = (fract * worldWidth); axisPtr->max = worldMax - viewMax; axisPtr->min = axisPtr->max - viewWidth; viewMin = viewMax + viewWidth; if (axisPtr->logScale) { axisPtr->min = EXP10(axisPtr->min); axisPtr->max = EXP10(axisPtr->max); } Blt_UpdateScrollbar(graphPtr->interp, axisPtr->scrollCmdPrefix, (viewMax / worldWidth), (viewMin / worldWidth)); } } if (axisPtr->showTicks) { register Blt_ChainLink *linkPtr; TickLabel *labelPtr; for (linkPtr = Blt_ChainFirstLink(axisPtr->tickLabels); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { /* Draw major tick labels */ labelPtr = Blt_ChainGetValue(linkPtr); Blt_DrawText(graphPtr->tkwin, drawable, labelPtr->string, &axisPtr->tickTextStyle, (int)labelPtr->anchorPos.x, (int)labelPtr->anchorPos.y); } } if ((axisPtr->nSegments > 0) && (axisPtr->lineWidth > 0)) { /* Draw the tick marks and axis line. */ Blt_Draw2DSegments(graphPtr->display, drawable, axisPtr->tickGC, axisPtr->segments, axisPtr->nSegments); } } /* * ----------------------------------------------------------------- * * AxisToPostScript -- * * Generates PostScript output to draw the axis, ticks, and * labels. * * Initializes and passes text attribute information through * TextStyle structure. * * Results: * None. * * Side Effects: * PostScript output is left in graphPtr->interp->result; * * ----------------------------------------------------------------- */ /* ARGSUSED */ static void AxisToPostScript(psToken, axisPtr) PsToken psToken; Axis *axisPtr; { if (axisPtr->title != NULL) { Blt_TextToPostScript(psToken, axisPtr->title, &axisPtr->titleTextStyle, axisPtr->titlePos.x, axisPtr->titlePos.y); } if (axisPtr->showTicks) { register Blt_ChainLink *linkPtr; TickLabel *labelPtr; for (linkPtr = Blt_ChainFirstLink(axisPtr->tickLabels); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { labelPtr = Blt_ChainGetValue(linkPtr); Blt_TextToPostScript(psToken, labelPtr->string, &axisPtr->tickTextStyle, labelPtr->anchorPos.x, labelPtr->anchorPos.y); } } if ((axisPtr->nSegments > 0) && (axisPtr->lineWidth > 0)) { Blt_LineAttributesToPostScript(psToken, axisPtr->tickTextStyle.color, axisPtr->lineWidth, (Blt_Dashes *)NULL, CapButt, JoinMiter); Blt_2DSegmentsToPostScript(psToken, axisPtr->segments, axisPtr->nSegments); } } static void MakeGridLine(graphPtr, axisPtr, value, segPtr) Graph *graphPtr; Axis *axisPtr; double value; Segment2D *segPtr; { if (axisPtr->logScale) { value = EXP10(value); } /* Grid lines run orthogonally to the axis */ if (AxisIsHorizontal(graphPtr, axisPtr)) { segPtr->p.y = graphPtr->top; segPtr->q.y = graphPtr->bottom; segPtr->p.x = segPtr->q.x = Blt_HMap(graphPtr, axisPtr, value); } else { segPtr->p.x = graphPtr->left; segPtr->q.x = graphPtr->right; segPtr->p.y = segPtr->q.y = Blt_VMap(graphPtr, axisPtr, value); } } /* *---------------------------------------------------------------------- * * Blt_GetAxisSegments -- * * Assembles the grid lines associated with an axis. Generates * tick positions if necessary (this happens when the axis is * not a logical axis too). * * Results: * None. * *---------------------------------------------------------------------- */ void Blt_GetAxisSegments(graphPtr, axisPtr, segPtrPtr, nSegmentsPtr) Graph *graphPtr; Axis *axisPtr; Segment2D **segPtrPtr; int *nSegmentsPtr; { int needed; Ticks *t1Ptr, *t2Ptr; register int i; double value; Segment2D *segments, *segPtr; *nSegmentsPtr = 0; *segPtrPtr = NULL; if (axisPtr == NULL) { return; } t1Ptr = axisPtr->t1Ptr; if (t1Ptr == NULL) { t1Ptr = GenerateTicks(&axisPtr->majorSweep); } t2Ptr = axisPtr->t2Ptr; if (t2Ptr == NULL) { t2Ptr = GenerateTicks(&axisPtr->minorSweep); } needed = t1Ptr->nTicks; if (graphPtr->gridPtr->minorGrid) { needed += (t1Ptr->nTicks * t2Ptr->nTicks); } if (needed == 0) { return; } segments = Blt_Malloc(sizeof(Segment2D) * needed); if (segments == NULL) { return; /* Can't allocate memory for grid. */ } segPtr = segments; for (i = 0; i < t1Ptr->nTicks; i++) { value = t1Ptr->values[i]; if (graphPtr->gridPtr->minorGrid) { register int j; double subValue; for (j = 0; j < t2Ptr->nTicks; j++) { subValue = value + (axisPtr->majorSweep.step * t2Ptr->values[j]); if (InRange(subValue, &axisPtr->axisRange)) { MakeGridLine(graphPtr, axisPtr, subValue, segPtr); segPtr++; } } } if (InRange(value, &axisPtr->axisRange)) { MakeGridLine(graphPtr, axisPtr, value, segPtr); segPtr++; } } if (t1Ptr != axisPtr->t1Ptr) { Blt_Free(t1Ptr); /* Free generated ticks. */ } if (t2Ptr != axisPtr->t2Ptr) { Blt_Free(t2Ptr); /* Free generated ticks. */ } *nSegmentsPtr = segPtr - segments; assert(*nSegmentsPtr <= needed); *segPtrPtr = segments; } /* *---------------------------------------------------------------------- * * GetAxisGeometry -- * * Results: * None. * *---------------------------------------------------------------------- */ static void GetAxisGeometry(graphPtr, axisPtr) Graph *graphPtr; Axis *axisPtr; { int height; FreeLabels(axisPtr->tickLabels); height = 0; if (axisPtr->lineWidth > 0) { /* Leave room for axis baseline (and pad) */ height += axisPtr->lineWidth + 2; } if (axisPtr->showTicks) { int pad; register int i, nLabels; int lw, lh; double x, x2; int maxWidth, maxHeight; TickLabel *labelPtr; SweepTicks(axisPtr); if (axisPtr->t1Ptr->nTicks < 0) { fprintf(stderr, "%s major ticks can't be %d\n", axisPtr->name, axisPtr->t1Ptr->nTicks); abort(); } if (axisPtr->t1Ptr->nTicks > MAXTICKS) { fprintf(stderr, "too big, %s major ticks can't be %d\n", axisPtr->name, axisPtr->t1Ptr->nTicks); abort(); } maxHeight = maxWidth = 0; nLabels = 0; for (i = 0; i < axisPtr->t1Ptr->nTicks; i++) { x2 = x = axisPtr->t1Ptr->values[i]; if (axisPtr->labelOffset) { x2 += axisPtr->majorSweep.step * 0.5; } if (!InRange(x2, &axisPtr->axisRange)) { continue; } labelPtr = MakeLabel(graphPtr, axisPtr, x); Blt_ChainAppend(axisPtr->tickLabels, labelPtr); nLabels++; /* * Get the dimensions of each tick label. * Remember tick labels can be multi-lined and/or rotated. */ Blt_GetTextExtents(&axisPtr->tickTextStyle, labelPtr->string, &lw, &lh); labelPtr->width = lw; labelPtr->height = lh; if (axisPtr->tickTextStyle.theta > 0.0) { double rotWidth, rotHeight; Blt_GetBoundingBox(lw, lh, axisPtr->tickTextStyle.theta, &rotWidth, &rotHeight, (Point2D *)NULL); lw = ROUND(rotWidth); lh = ROUND(rotHeight); } if (maxWidth < lw) { maxWidth = lw; } if (maxHeight < lh) { maxHeight = lh; } } assert(nLabels <= axisPtr->t1Ptr->nTicks); /* Because the axis cap style is "CapProjecting", we need to * account for an extra 1.5 linewidth at the end of each * line. */ pad = ((axisPtr->lineWidth * 15) / 10); if (AxisIsHorizontal(graphPtr, axisPtr)) { height += maxHeight + pad; } else { height += maxWidth + pad; } if (axisPtr->lineWidth > 0) { /* Distance from axis line to tick label. */ height += AXIS_TITLE_PAD; height += ABS(axisPtr->tickLength); } } if (axisPtr->title != NULL) { if (axisPtr->titleAlternate) { if (height < axisPtr->titleHeight) { height = axisPtr->titleHeight; } } else { height += axisPtr->titleHeight + AXIS_TITLE_PAD; } } /* Correct for orientation of the axis. */ if (AxisIsHorizontal(graphPtr, axisPtr)) { axisPtr->height = height; } else { axisPtr->width = height; } } /* *---------------------------------------------------------------------- * * GetMarginGeometry -- * * Examines all the axes in the given margin and determines the * area required to display them. * * Note: For multiple axes, the titles are displayed in another * margin. So we must keep track of the widest title. * * Results: * Returns the width or height of the margin, depending if it * runs horizontally along the graph or vertically. * * Side Effects: * The area width and height set in the margin. Note again that * this may be corrected later (mulitple axes) to adjust for * the longest title in another margin. * *---------------------------------------------------------------------- */ static int GetMarginGeometry(graphPtr, marginPtr) Graph *graphPtr; Margin *marginPtr; { Blt_ChainLink *linkPtr; Axis *axisPtr; int width, height; int isHoriz; int length, count; isHoriz = HORIZMARGIN(marginPtr); /* Count the number of visible axes. */ count = 0; length = width = height = 0; for (linkPtr = Blt_ChainFirstLink(marginPtr->axes); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { axisPtr = Blt_ChainGetValue(linkPtr); if ((!axisPtr->hidden) && (axisPtr->flags & AXIS_ONSCREEN)) { count++; if (graphPtr->flags & GET_AXIS_GEOMETRY) { GetAxisGeometry(graphPtr, axisPtr); } if ((axisPtr->titleAlternate) && (length < axisPtr->titleWidth)) { length = axisPtr->titleWidth; } if (isHoriz) { height += axisPtr->height; } else { width += axisPtr->width; } } } /* Enforce a minimum size for margins. */ if (width < 3) { width = 3; } if (height < 3) { height = 3; } marginPtr->nAxes = count; marginPtr->axesTitleLength = length; marginPtr->width = width; marginPtr->height = height; marginPtr->axesOffset = (HORIZMARGIN(marginPtr)) ? height : width; return marginPtr->axesOffset; } /* *---------------------------------------------------------------------- * * ComputeMargins -- * * Computes the size of the margins and the plotting area. We * first compute the space needed for the axes in each margin. * Then how much space the legend will occupy. Finally, if the * user has requested a margin size, we override the computed * value. * * Results: * *---------------------------------------------------------------------- */ static void ComputeMargins(graphPtr) Graph *graphPtr; { int left, right, top, bottom; int width, height; int insets; /* * Step 1: Compute the amount of space needed to display the * axes (there many be 0 or more) associated with the * margin. */ top = GetMarginGeometry(graphPtr, &graphPtr->topMargin); bottom = GetMarginGeometry(graphPtr, &graphPtr->bottomMargin); left = GetMarginGeometry(graphPtr, &graphPtr->leftMargin); right = GetMarginGeometry(graphPtr, &graphPtr->rightMargin); /* * Step 2: Add the graph title height to the top margin. */ if (graphPtr->title != NULL) { top += graphPtr->titleTextStyle.height; } insets = 2 * (graphPtr->inset + graphPtr->plotBorderWidth); /* * Step 3: Use the current estimate of the plot area to compute * the legend size. Add it to the proper margin. */ width = graphPtr->width - (insets + left + right); height = graphPtr->height - (insets + top + bottom); Blt_MapLegend(graphPtr->legend, width, height); if (!Blt_LegendIsHidden(graphPtr->legend)) { switch (Blt_LegendSite(graphPtr->legend)) { case LEGEND_RIGHT: right += Blt_LegendWidth(graphPtr->legend) + 2; break; case LEGEND_LEFT: left += Blt_LegendWidth(graphPtr->legend) + 2; break; case LEGEND_TOP: top += Blt_LegendHeight(graphPtr->legend) + 2; break; case LEGEND_BOTTOM: bottom += Blt_LegendHeight(graphPtr->legend) + 2; break; case LEGEND_XY: case LEGEND_PLOT: case LEGEND_WINDOW: /* Do nothing. */ break; } } /* * Recompute the plotarea, now accounting for the legend. */ width = graphPtr->width - (insets + left + right); height = graphPtr->height - (insets + top + bottom); /* * Step 5: If necessary, correct for the requested plot area * aspect ratio. */ if (graphPtr->aspect > 0.0) { double ratio; /* * Shrink one dimension of the plotarea to fit the requested * width/height aspect ratio. */ ratio = (double)width / (double)height; if (ratio > graphPtr->aspect) { int scaledWidth; /* Shrink the width. */ scaledWidth = (int)(height * graphPtr->aspect); if (scaledWidth < 1) { scaledWidth = 1; } right += (width - scaledWidth); /* Add the difference to * the right margin. */ /* CHECK THIS: width = scaledWidth; */ } else { int scaledHeight; /* Shrink the height. */ scaledHeight = (int)(width / graphPtr->aspect); if (scaledHeight < 1) { scaledHeight = 1; } top += (height - scaledHeight); /* Add the difference to * the top margin. */ /* CHECK THIS: height = scaledHeight; */ } } /* * Step 6: If there's multiple axes in a margin, the axis * titles will be displayed in the adjoining marging. * 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. */ graphPtr->leftMargin.width = left; graphPtr->rightMargin.width = right; graphPtr->topMargin.height = top; graphPtr->bottomMargin.height = bottom; if (graphPtr->leftMargin.reqSize > 0) { graphPtr->leftMargin.width = graphPtr->leftMargin.reqSize; } if (graphPtr->rightMargin.reqSize > 0) { graphPtr->rightMargin.width = graphPtr->rightMargin.reqSize; } if (graphPtr->topMargin.reqSize > 0) { graphPtr->topMargin.height = graphPtr->topMargin.reqSize; } if (graphPtr->bottomMargin.reqSize > 0) { graphPtr->bottomMargin.height = graphPtr->bottomMargin.reqSize; } } /* * ----------------------------------------------------------------- * * Blt_LayoutMargins -- * * 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. * * * ----------------------------------------------------------------- */ void Blt_LayoutMargins(graphPtr) Graph *graphPtr; { int width, height; int titleY; int left, right, top, bottom; ComputeMargins(graphPtr); left = graphPtr->leftMargin.width + graphPtr->inset + graphPtr->plotBorderWidth; right = graphPtr->rightMargin.width + graphPtr->inset + graphPtr->plotBorderWidth; top = graphPtr->topMargin.height + graphPtr->inset + graphPtr->plotBorderWidth; bottom = graphPtr->bottomMargin.height + graphPtr->inset + graphPtr->plotBorderWidth; /* Based upon the margins, calculate the space left for the graph. */ width = graphPtr->width - (left + right); height = graphPtr->height - (top + bottom); if (width < 1) { width = 1; } if (height < 1) { height = 1; } graphPtr->left = left; graphPtr->right = left + width; graphPtr->bottom = top + height; graphPtr->top = top; graphPtr->vOffset = top + graphPtr->padTop; graphPtr->vRange = height - PADDING(graphPtr->padY); graphPtr->hOffset = left + graphPtr->padLeft; graphPtr->hRange = width - PADDING(graphPtr->padX); if (graphPtr->vRange < 1) { graphPtr->vRange = 1; } if (graphPtr->hRange < 1) { graphPtr->hRange = 1; } graphPtr->hScale = 1.0 / (double)graphPtr->hRange; graphPtr->vScale = 1.0 / (double)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->titleTextStyle.height; graphPtr->titleY = (titleY / 2) + graphPtr->inset; graphPtr->titleX = (graphPtr->right + graphPtr->left) / 2; } /* * ---------------------------------------------------------------------- * * ConfigureAxis -- * * Configures axis attributes (font, line width, label, etc). * * Results: * The return value is a standard Tcl result. * * Side Effects: * Axis layout is deferred until the height and width of the * window are known. * * ---------------------------------------------------------------------- */ static int ConfigureAxis(graphPtr, axisPtr) Graph *graphPtr; Axis *axisPtr; { char errMsg[200]; /* Check the requested axis limits. Can't allow -min to be greater * than -max, or have undefined log scale limits. */ if (((DEFINED(axisPtr->reqMin)) && (DEFINED(axisPtr->reqMax))) && (axisPtr->reqMin >= axisPtr->reqMax)) { sprintf(errMsg, "impossible limits (min %g >= max %g) for axis \"%s\"", axisPtr->reqMin, axisPtr->reqMax, axisPtr->name); Tcl_AppendResult(graphPtr->interp, errMsg, (char *)NULL); /* Bad values, turn on axis auto-scaling */ axisPtr->reqMin = axisPtr->reqMax = VALUE_UNDEFINED; return TCL_ERROR; } if ((axisPtr->logScale) && (DEFINED(axisPtr->reqMin)) && (axisPtr->reqMin <= 0.0)) { sprintf(errMsg, "bad logscale limits (min=%g,max=%g) for axis \"%s\"", axisPtr->reqMin, axisPtr->reqMax, axisPtr->name); Tcl_AppendResult(graphPtr->interp, errMsg, (char *)NULL); /* Bad minimum value, turn on auto-scaling */ axisPtr->reqMin = VALUE_UNDEFINED; return TCL_ERROR; } axisPtr->tickTextStyle.theta = FMOD(axisPtr->tickTextStyle.theta, 360.0); if (axisPtr->tickTextStyle.theta < 0.0) { axisPtr->tickTextStyle.theta += 360.0; } ResetTextStyles(graphPtr, axisPtr); axisPtr->titleWidth = axisPtr->titleHeight = 0; if (axisPtr->title != NULL) { int w, h; Blt_GetTextExtents(&axisPtr->titleTextStyle, axisPtr->title, &w, &h); axisPtr->titleWidth = (short int)w; axisPtr->titleHeight = (short int)h; } /* * Don't bother to check what configuration options have changed. * Almost every option changes the size of the plotting area * (except for -color and -titlecolor), requiring the graph and * its contents to be completely redrawn. * * Recompute the scale and offset of the axis in case -min, -max * options have changed. */ graphPtr->flags |= REDRAW_WORLD; if (!Blt_ConfigModified(configSpecs, "-*color", "-background", "-bg", (char *)NULL)) { graphPtr->flags |= (MAP_WORLD | RESET_AXES); axisPtr->flags |= AXIS_DIRTY; } Blt_EventuallyRedrawGraph(graphPtr); return TCL_OK; } /* * ---------------------------------------------------------------------- * * CreateAxis -- * * Create and initialize a structure containing information to * display a graph axis. * * Results: * The return value is a standard Tcl result. * * ---------------------------------------------------------------------- */ static Axis * CreateAxis(graphPtr, name, margin) Graph *graphPtr; char *name; /* Identifier for axis. */ int margin; { Axis *axisPtr; Blt_HashEntry *hPtr; int isNew; if (name[0] == '-') { Tcl_AppendResult(graphPtr->interp, "name of axis \"", name, "\" can't start with a '-'", (char *)NULL); return NULL; } hPtr = Blt_CreateHashEntry(&graphPtr->axes.table, name, &isNew); if (!isNew) { axisPtr = (Axis *)Blt_GetHashValue(hPtr); if (!axisPtr->deletePending) { Tcl_AppendResult(graphPtr->interp, "axis \"", name, "\" already exists in \"", Tk_PathName(graphPtr->tkwin), "\"", (char *)NULL); return NULL; } axisPtr->deletePending = FALSE; } else { axisPtr = Blt_Calloc(1, sizeof(Axis)); assert(axisPtr); axisPtr->name = Blt_Strdup(name); axisPtr->hashPtr = hPtr; axisPtr->classUid = NULL; axisPtr->looseMin = axisPtr->looseMax = TICK_RANGE_TIGHT; axisPtr->reqNumMinorTicks = 2; axisPtr->scrollUnits = 10; axisPtr->showTicks = TRUE; axisPtr->reqMin = axisPtr->reqMax = VALUE_UNDEFINED; axisPtr->scrollMin = axisPtr->scrollMax = VALUE_UNDEFINED; if ((graphPtr->classUid == bltBarElementUid) && ((margin == MARGIN_TOP) || (margin == MARGIN_BOTTOM))) { axisPtr->reqStep = 1.0; axisPtr->reqNumMinorTicks = 0; } if ((margin == MARGIN_RIGHT) || (margin == MARGIN_TOP)) { axisPtr->hidden = TRUE; } Blt_InitTextStyle(&axisPtr->titleTextStyle); Blt_InitTextStyle(&axisPtr->limitsTextStyle); Blt_InitTextStyle(&axisPtr->tickTextStyle); axisPtr->tickLabels = Blt_ChainCreate(); axisPtr->lineWidth = 1; axisPtr->tickTextStyle.padX.side1 = 2; axisPtr->tickTextStyle.padX.side2 = 2; Blt_SetHashValue(hPtr, axisPtr); } return axisPtr; } static int NameToAxis(graphPtr, name, axisPtrPtr) Graph *graphPtr; /* Graph widget record. */ char *name; /* Name of the axis to be searched for. */ Axis **axisPtrPtr; /* (out) Pointer to found axis structure. */ { Blt_HashEntry *hPtr; hPtr = Blt_FindHashEntry(&graphPtr->axes.table, name); if (hPtr != NULL) { Axis *axisPtr; axisPtr = (Axis *)Blt_GetHashValue(hPtr); if (!axisPtr->deletePending) { *axisPtrPtr = axisPtr; return TCL_OK; } } Tcl_AppendResult(graphPtr->interp, "can't find axis \"", name, "\" in \"", Tk_PathName(graphPtr->tkwin), "\"", (char *)NULL); *axisPtrPtr = NULL; return TCL_ERROR; } static int GetAxis(graphPtr, axisName, classUid, axisPtrPtr) Graph *graphPtr; char *axisName; Blt_Uid classUid; Axis **axisPtrPtr; { Axis *axisPtr; if (NameToAxis(graphPtr, axisName, &axisPtr) != TCL_OK) { return TCL_ERROR; } if (classUid != NULL) { if ((axisPtr->refCount == 0) || (axisPtr->classUid == NULL)) { /* Set the axis type on the first use of it. */ axisPtr->classUid = classUid; } else if (axisPtr->classUid != classUid) { Tcl_AppendResult(graphPtr->interp, "axis \"", axisName, "\" is already in use on an opposite ", axisPtr->classUid, "-axis", (char *)NULL); return TCL_ERROR; } axisPtr->refCount++; } *axisPtrPtr = axisPtr; return TCL_OK; } static void FreeAxis(graphPtr, axisPtr) Graph *graphPtr; Axis *axisPtr; { axisPtr->refCount--; if ((axisPtr->deletePending) && (axisPtr->refCount == 0)) { DestroyAxis(graphPtr, axisPtr); } } void Blt_DestroyAxes(graphPtr) Graph *graphPtr; { Blt_HashEntry *hPtr; Blt_HashSearch cursor; Axis *axisPtr; int i; for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { axisPtr = (Axis *)Blt_GetHashValue(hPtr); axisPtr->hashPtr = NULL; DestroyAxis(graphPtr, axisPtr); } Blt_DeleteHashTable(&graphPtr->axes.table); for (i = 0; i < 4; i++) { Blt_ChainDestroy(graphPtr->axisChain[i]); } Blt_DeleteHashTable(&graphPtr->axes.tagTable); Blt_ChainDestroy(graphPtr->axes.displayList); } int Blt_DefaultAxes(graphPtr) Graph *graphPtr; { register int i; Axis *axisPtr; Blt_Chain *chainPtr; static char *axisNames[4] = { "x", "y", "x2", "y2" } ; int flags; flags = Blt_GraphType(graphPtr); for (i = 0; i < 4; i++) { chainPtr = Blt_ChainCreate(); graphPtr->axisChain[i] = chainPtr; /* Create a default axis for each chain. */ axisPtr = CreateAxis(graphPtr, axisNames[i], i); if (axisPtr == NULL) { return TCL_ERROR; } axisPtr->refCount = 1; /* Default axes are assumed in use. */ axisPtr->classUid = (i & 1) ? bltYAxisUid : bltXAxisUid; axisPtr->flags |= AXIS_ONSCREEN; /* * Blt_ConfigureWidgetComponent creates a temporary child window * by the name of the axis. It's used so that the Tk routines * that access the X resource database can describe a single * component and not the entire graph. */ if (Blt_ConfigureWidgetComponent(graphPtr->interp, graphPtr->tkwin, axisPtr->name, "Axis", configSpecs, 0, (char **)NULL, (char *)axisPtr, flags) != TCL_OK) { return TCL_ERROR; } if (ConfigureAxis(graphPtr, axisPtr) != TCL_OK) { return TCL_ERROR; } axisPtr->linkPtr = Blt_ChainAppend(chainPtr, axisPtr); axisPtr->chainPtr = chainPtr; } return TCL_OK; } /*---------------------------------------------------------------------- * * BindOp -- * * .g axis bind axisName sequence command * *---------------------------------------------------------------------- */ static int BindOp(graphPtr, axisPtr, argc, argv) Graph *graphPtr; Axis *axisPtr; int argc; char **argv; { Tcl_Interp *interp = graphPtr->interp; return Blt_ConfigureBindings(interp, graphPtr->bindTable, Blt_MakeAxisTag(graphPtr, axisPtr->name), argc, argv); } /* * ---------------------------------------------------------------------- * * CgetOp -- * * Queries axis attributes (font, line width, label, etc). * * Results: * Return value is a standard Tcl result. If querying configuration * values, interp->result will contain the results. * * ---------------------------------------------------------------------- */ /* ARGSUSED */ static int CgetOp(graphPtr, axisPtr, argc, argv) Graph *graphPtr; Axis *axisPtr; int argc; /* Not used. */ char *argv[]; { return Tk_ConfigureValue(graphPtr->interp, graphPtr->tkwin, configSpecs, (char *)axisPtr, argv[0], Blt_GraphType(graphPtr)); } /* * ---------------------------------------------------------------------- * * ConfigureOp -- * * Queries or resets axis attributes (font, line width, label, etc). * * Results: * Return value is a standard Tcl result. If querying configuration * values, interp->result will contain the results. * * Side Effects: * Axis resources are possibly allocated (GC, font). Axis layout is * deferred until the height and width of the window are known. * * ---------------------------------------------------------------------- */ static int ConfigureOp(graphPtr, axisPtr, argc, argv) Graph *graphPtr; Axis *axisPtr; int argc; char *argv[]; { int flags; flags = TK_CONFIG_ARGV_ONLY | Blt_GraphType(graphPtr); if (argc == 0) { return Tk_ConfigureInfo(graphPtr->interp, graphPtr->tkwin, configSpecs, (char *)axisPtr, (char *)NULL, flags); } else if (argc == 1) { return Tk_ConfigureInfo(graphPtr->interp, graphPtr->tkwin, configSpecs, (char *)axisPtr, argv[0], flags); } if (Tk_ConfigureWidget(graphPtr->interp, graphPtr->tkwin, configSpecs, argc, argv, (char *)axisPtr, flags) != TCL_OK) { return TCL_ERROR; } if (ConfigureAxis(graphPtr, axisPtr) != TCL_OK) { return TCL_ERROR; } if (axisPtr->flags & AXIS_ONSCREEN) { if (!Blt_ConfigModified(configSpecs, "-*color", "-background", "-bg", (char *)NULL)) { graphPtr->flags |= REDRAW_BACKING_STORE; } graphPtr->flags |= DRAW_MARGINS; Blt_EventuallyRedrawGraph(graphPtr); } return TCL_OK; } /* * ---------------------------------------------------------------------- * * GetOp -- * * Returns the name of the picked axis (using the axis * bind operation). Right now, the only name accepted is * "current". * * Results: * A standard Tcl result. The interpreter result will contain * the name of the axis. * * ---------------------------------------------------------------------- */ /*ARGSUSED*/ static int GetOp(graphPtr, argc, argv) Graph *graphPtr; int argc; /* Not used. */ char *argv[]; { Tcl_Interp *interp = graphPtr->interp; register Axis *axisPtr; axisPtr = (Axis *)Blt_GetCurrentItem(graphPtr->bindTable); /* Report only on axes. */ if ((axisPtr != NULL) && ((axisPtr->classUid == bltXAxisUid) || (axisPtr->classUid == bltYAxisUid) || (axisPtr->classUid == NULL))) { char c; c = argv[3][0]; if ((c == 'c') && (strcmp(argv[3], "current") == 0)) { Tcl_SetResult(interp, axisPtr->name, TCL_VOLATILE); } else if ((c == 'd') && (strcmp(argv[3], "detail") == 0)) { Tcl_SetResult(interp, axisPtr->detail, TCL_VOLATILE); } } return TCL_OK; } /* *-------------------------------------------------------------- * * LimitsOp -- * * This procedure returns a string representing the axis limits * of the graph. The format of the string is { left top right bottom}. * * Results: * Always returns TCL_OK. The interp->result field is * a list of the graph axis limits. * *-------------------------------------------------------------- */ /*ARGSUSED*/ static int LimitsOp(graphPtr, axisPtr, argc, argv) Graph *graphPtr; Axis *axisPtr; int argc; /* Not used. */ char **argv; /* Not used. */ { Tcl_Interp *interp = graphPtr->interp; double min, max; if (graphPtr->flags & RESET_AXES) { Blt_ResetAxes(graphPtr); } if (axisPtr->logScale) { min = EXP10(axisPtr->axisRange.min); max = EXP10(axisPtr->axisRange.max); } else { min = axisPtr->axisRange.min; max = axisPtr->axisRange.max; } Tcl_AppendElement(interp, Blt_Dtoa(interp, min)); Tcl_AppendElement(interp, Blt_Dtoa(interp, max)); return TCL_OK; } /* * ---------------------------------------------------------------------- * * InvTransformOp -- * * Maps the given window coordinate into an axis-value. * * Results: * Returns a standard Tcl result. interp->result contains * the axis value. If an error occurred, TCL_ERROR is returned * and interp->result will contain an error message. * * ---------------------------------------------------------------------- */ /*ARGSUSED*/ static int InvTransformOp(graphPtr, axisPtr, argc, argv) Graph *graphPtr; Axis *axisPtr; int argc; /* Not used. */ char **argv; { int x; /* Integer window coordinate*/ double y; /* Real graph coordinate */ if (graphPtr->flags & RESET_AXES) { Blt_ResetAxes(graphPtr); } if (Tcl_GetInt(graphPtr->interp, argv[0], &x) != 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). */ if (AxisIsHorizontal(graphPtr, axisPtr)) { y = Blt_InvHMap(graphPtr, axisPtr, (double)x); } else { y = Blt_InvVMap(graphPtr, axisPtr, (double)x); } Tcl_AppendElement(graphPtr->interp, Blt_Dtoa(graphPtr->interp, y)); return TCL_OK; } /* * ---------------------------------------------------------------------- * * TransformOp -- * * Maps the given axis-value to a window coordinate. * * Results: * Returns a standard Tcl result. interp->result contains * the window coordinate. If an error occurred, TCL_ERROR * is returned and interp->result will contain an error * message. * * ---------------------------------------------------------------------- */ /*ARGSUSED*/ static int TransformOp(graphPtr, axisPtr, argc, argv) Graph *graphPtr; Axis *axisPtr; /* Axis */ int argc; /* Not used. */ char **argv; { double x; if (graphPtr->flags & RESET_AXES) { Blt_ResetAxes(graphPtr); } if (Tcl_ExprDouble(graphPtr->interp, argv[0], &x) != TCL_OK) { return TCL_ERROR; } if (AxisIsHorizontal(graphPtr, axisPtr)) { x = Blt_HMap(graphPtr, axisPtr, x); } else { x = Blt_VMap(graphPtr, axisPtr, x); } Tcl_SetResult(graphPtr->interp, Blt_Itoa((int)x), TCL_VOLATILE); return TCL_OK; } /* *-------------------------------------------------------------- * * UseOp -- * * Changes the virtual axis used by the logical axis. * * Results: * A standard Tcl result. If the named axis doesn't exist * an error message is put in interp->result. * * .g xaxis use "abc def gah" * .g xaxis use [lappend abc [.g axis use]] * *-------------------------------------------------------------- */ /*ARGSUSED*/ static int UseOp(graphPtr, axisPtr, argc, argv) Graph *graphPtr; Axis *axisPtr; /* Not used. */ int argc; char **argv; { Blt_Chain *chainPtr; int nNames; char **names; Blt_ChainLink *linkPtr; int i; Blt_Uid classUid; int margin; margin = (int)argv[-1]; chainPtr = graphPtr->margins[margin].axes; if (argc == 0) { for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr!= NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { axisPtr = Blt_ChainGetValue(linkPtr); Tcl_AppendElement(graphPtr->interp, axisPtr->name); } return TCL_OK; } if ((margin == MARGIN_BOTTOM) || (margin == MARGIN_TOP)) { classUid = (graphPtr->inverted) ? bltYAxisUid : bltXAxisUid; } else { classUid = (graphPtr->inverted) ? bltXAxisUid : bltYAxisUid; } if (Tcl_SplitList(graphPtr->interp, argv[0], &nNames, &names) != TCL_OK) { return TCL_ERROR; } for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr!= NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { axisPtr = Blt_ChainGetValue(linkPtr); axisPtr->linkPtr = NULL; axisPtr->flags &= ~AXIS_ONSCREEN; /* Clear the axis type if it's not currently used.*/ if (axisPtr->refCount == 0) { axisPtr->classUid = NULL; } } Blt_ChainReset(chainPtr); for (i = 0; i < nNames; i++) { if (NameToAxis(graphPtr, names[i], &axisPtr) != TCL_OK) { Blt_Free(names); return TCL_ERROR; } if (axisPtr->classUid == NULL) { axisPtr->classUid = classUid; } else if (axisPtr->classUid != classUid) { Tcl_AppendResult(graphPtr->interp, "wrong type axis \"", axisPtr->name, "\": can't use ", classUid, " type axis.", (char *)NULL); Blt_Free(names); return TCL_ERROR; } if (axisPtr->linkPtr != NULL) { /* Move the axis from the old margin's "use" list to the new. */ Blt_ChainUnlinkLink(axisPtr->chainPtr, axisPtr->linkPtr); Blt_ChainAppendLink(chainPtr, axisPtr->linkPtr); } else { axisPtr->linkPtr = Blt_ChainAppend(chainPtr, axisPtr); } axisPtr->chainPtr = chainPtr; axisPtr->flags |= AXIS_ONSCREEN; } 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); Blt_Free(names); return TCL_OK; } /* * ---------------------------------------------------------------------- * * CreateVirtualOp -- * * Creates a new axis. * * Results: * Returns a standard Tcl result. * * ---------------------------------------------------------------------- */ /*ARGSUSED*/ static int CreateVirtualOp(graphPtr, argc, argv) Graph *graphPtr; int argc; char **argv; { Axis *axisPtr; int flags; axisPtr = CreateAxis(graphPtr, argv[3], MARGIN_NONE); if (axisPtr == NULL) { return TCL_ERROR; } flags = Blt_GraphType(graphPtr); if (Blt_ConfigureWidgetComponent(graphPtr->interp, graphPtr->tkwin, axisPtr->name, "Axis", configSpecs, argc - 4, argv + 4, (char *)axisPtr, flags) != TCL_OK) { goto error; } if (ConfigureAxis(graphPtr, axisPtr) != TCL_OK) { goto error; } Tcl_SetResult(graphPtr->interp, axisPtr->name, TCL_VOLATILE); return TCL_OK; error: DestroyAxis(graphPtr, axisPtr); return TCL_ERROR; } /*---------------------------------------------------------------------- * * BindVirtualOp -- * * .g axis bind axisName sequence command * *---------------------------------------------------------------------- */ /*ARGSUSED*/ static int BindVirtualOp(graphPtr, argc, argv) Graph *graphPtr; int argc; char **argv; { Tcl_Interp *interp = graphPtr->interp; if (argc == 3) { Blt_HashEntry *hPtr; Blt_HashSearch cursor; char *tagName; for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.tagTable, &cursor); hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { tagName = Blt_GetHashKey(&graphPtr->axes.tagTable, hPtr); Tcl_AppendElement(interp, tagName); } return TCL_OK; } return Blt_ConfigureBindings(interp, graphPtr->bindTable, Blt_MakeAxisTag(graphPtr, argv[3]), argc - 4, argv + 4); } /* * ---------------------------------------------------------------------- * * CgetVirtualOp -- * * Queries axis attributes (font, line width, label, etc). * * Results: * Return value is a standard Tcl result. If querying configuration * values, interp->result will contain the results. * * ---------------------------------------------------------------------- */ /* ARGSUSED */ static int CgetVirtualOp(graphPtr, argc, argv) Graph *graphPtr; int argc; char *argv[]; { Axis *axisPtr; if (NameToAxis(graphPtr, argv[3], &axisPtr) != TCL_OK) { return TCL_ERROR; } return CgetOp(graphPtr, axisPtr, argc - 4, argv + 4); } /* * ---------------------------------------------------------------------- * * ConfigureVirtualOp -- * * Queries or resets axis attributes (font, line width, label, etc). * * Results: * Return value is a standard Tcl result. If querying configuration * values, interp->result will contain the results. * * Side Effects: * Axis resources are possibly allocated (GC, font). Axis layout is * deferred until the height and width of the window are known. * * ---------------------------------------------------------------------- */ static int ConfigureVirtualOp(graphPtr, argc, argv) Graph *graphPtr; int argc; char *argv[]; { Axis *axisPtr; int nNames, nOpts; char **options; register int i; /* Figure out where the option value pairs begin */ argc -= 3; argv += 3; for (i = 0; i < argc; i++) { if (argv[i][0] == '-') { break; } if (NameToAxis(graphPtr, argv[i], &axisPtr) != TCL_OK) { return TCL_ERROR; } } nNames = i; /* Number of pen names specified */ nOpts = argc - i; /* Number of options specified */ options = argv + i; /* Start of options in argv */ for (i = 0; i < nNames; i++) { if (NameToAxis(graphPtr, argv[i], &axisPtr) != TCL_OK) { return TCL_ERROR; } if (ConfigureOp(graphPtr, axisPtr, nOpts, options) != TCL_OK) { break; } } if (i < nNames) { return TCL_ERROR; } return TCL_OK; } /* * ---------------------------------------------------------------------- * * DeleteVirtualOp -- * * Deletes one or more axes. The actual removal may be deferred * until the axis is no longer used by any element. The axis * can't be referenced by its name any longer and it may be * recreated. * * Results: * Returns a standard Tcl result. * * ---------------------------------------------------------------------- */ /*ARGSUSED*/ static int DeleteVirtualOp(graphPtr, argc, argv) Graph *graphPtr; int argc; char **argv; { register int i; Axis *axisPtr; for (i = 3; i < argc; i++) { if (NameToAxis(graphPtr, argv[i], &axisPtr) != TCL_OK) { return TCL_ERROR; } axisPtr->deletePending = TRUE; if (axisPtr->refCount == 0) { DestroyAxis(graphPtr, axisPtr); } } return TCL_OK; } /* * ---------------------------------------------------------------------- * * InvTransformVirtualOp -- * * Maps the given window coordinate into an axis-value. * * Results: * Returns a standard Tcl result. interp->result contains * the axis value. If an error occurred, TCL_ERROR is returned * and interp->result will contain an error message. * * ---------------------------------------------------------------------- */ /*ARGSUSED*/ static int InvTransformVirtualOp(graphPtr, argc, argv) Graph *graphPtr; int argc; /* Not used. */ char **argv; { Axis *axisPtr; if (NameToAxis(graphPtr, argv[3], &axisPtr) != TCL_OK) { return TCL_ERROR; } return InvTransformOp(graphPtr, axisPtr, argc - 4, argv + 4); } /* *-------------------------------------------------------------- * * LimitsVirtualOp -- * * This procedure returns a string representing the axis limits * of the graph. The format of the string is { left top right bottom}. * * Results: * Always returns TCL_OK. The interp->result field is * a list of the graph axis limits. * *-------------------------------------------------------------- */ /*ARGSUSED*/ static int LimitsVirtualOp(graphPtr, argc, argv) Graph *graphPtr; int argc; /* Not used. */ char **argv; /* Not used. */ { Axis *axisPtr; if (NameToAxis(graphPtr, argv[3], &axisPtr) != TCL_OK) { return TCL_ERROR; } return LimitsOp(graphPtr, axisPtr, argc - 4, argv + 4); } /* * ---------------------------------------------------------------------- * * NamesVirtualOp -- * * Return a list of the names of all the axes. * * Results: * Returns a standard Tcl result. * * ---------------------------------------------------------------------- */ /*ARGSUSED*/ static int NamesVirtualOp(graphPtr, argc, argv) Graph *graphPtr; int argc; /* Not used. */ char **argv; /* Not used. */ { Blt_HashEntry *hPtr; Blt_HashSearch cursor; Axis *axisPtr; register int i; for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { axisPtr = (Axis *)Blt_GetHashValue(hPtr); if (axisPtr->deletePending) { continue; } if (argc == 3) { Tcl_AppendElement(graphPtr->interp, axisPtr->name); continue; } for (i = 3; i < argc; i++) { if (Tcl_StringMatch(axisPtr->name, argv[i])) { Tcl_AppendElement(graphPtr->interp, axisPtr->name); break; } } } return TCL_OK; } /* * ---------------------------------------------------------------------- * * TransformVirtualOp -- * * Maps the given axis-value to a window coordinate. * * Results: * Returns a standard Tcl result. interp->result contains * the window coordinate. If an error occurred, TCL_ERROR * is returned and interp->result will contain an error * message. * * ---------------------------------------------------------------------- */ /*ARGSUSED*/ static int TransformVirtualOp(graphPtr, argc, argv) Graph *graphPtr; int argc; /* Not used. */ char **argv; { Axis *axisPtr; if (NameToAxis(graphPtr, argv[3], &axisPtr) != TCL_OK) { return TCL_ERROR; } return TransformOp(graphPtr, axisPtr, argc - 4, argv + 4); } static int ViewOp(graphPtr, argc, argv) Graph *graphPtr; int argc; char **argv; { Axis *axisPtr; Tcl_Interp *interp = graphPtr->interp; double axisOffset, scrollUnits; double fract; double viewMin, viewMax, worldMin, worldMax; double viewWidth, worldWidth; if (NameToAxis(graphPtr, argv[3], &axisPtr) != TCL_OK) { return TCL_ERROR; } worldMin = axisPtr->valueRange.min; worldMax = axisPtr->valueRange.max; /* Override data dimensions with user-selected limits. */ if (DEFINED(axisPtr->scrollMin)) { worldMin = axisPtr->scrollMin; } if (DEFINED(axisPtr->scrollMax)) { worldMax = axisPtr->scrollMax; } viewMin = axisPtr->min; viewMax = axisPtr->max; /* Bound the view within scroll region. */ if (viewMin < worldMin) { viewMin = worldMin; } if (viewMax > worldMax) { viewMax = worldMax; } if (axisPtr->logScale) { worldMin = log10(worldMin); worldMax = log10(worldMax); viewMin = log10(viewMin); viewMax = log10(viewMax); } worldWidth = worldMax - worldMin; 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. */ if (AxisIsHorizontal(graphPtr, axisPtr) != axisPtr->descending) { axisOffset = viewMin - worldMin; scrollUnits = (double)axisPtr->scrollUnits * graphPtr->hScale; } else { axisOffset = worldMax - viewMax; scrollUnits = (double)axisPtr->scrollUnits * graphPtr->vScale; } if (argc == 4) { /* Note: Bound the fractions between 0.0 and 1.0 to support * "canvas"-style scrolling. */ fract = axisOffset / worldWidth; Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0))); fract = (axisOffset + viewWidth) / worldWidth; Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0))); return TCL_OK; } fract = axisOffset / worldWidth; if (GetAxisScrollInfo(interp, argc - 4, argv + 4, &fract, viewWidth / worldWidth, scrollUnits) != TCL_OK) { return TCL_ERROR; } if (AxisIsHorizontal(graphPtr, axisPtr) != axisPtr->descending) { axisPtr->reqMin = (fract * worldWidth) + worldMin; axisPtr->reqMax = axisPtr->reqMin + viewWidth; } else { axisPtr->reqMax = worldMax - (fract * worldWidth); axisPtr->reqMin = axisPtr->reqMax - viewWidth; } if (axisPtr->logScale) { axisPtr->reqMin = EXP10(axisPtr->reqMin); axisPtr->reqMax = EXP10(axisPtr->reqMax); } graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED | RESET_AXES); Blt_EventuallyRedrawGraph(graphPtr); return TCL_OK; } int Blt_VirtualAxisOp(graphPtr, interp, argc, argv) Graph *graphPtr; Tcl_Interp *interp; int argc; char **argv; { Blt_Op proc; int result; static Blt_OpSpec axisOps[] = { {"bind", 1, (Blt_Op)BindVirtualOp, 3, 6, "axisName sequence command",}, {"cget", 2, (Blt_Op)CgetVirtualOp, 5, 5, "axisName option",}, {"configure", 2, (Blt_Op)ConfigureVirtualOp, 4, 0, "axisName ?axisName?... ?option value?...",}, {"create", 2, (Blt_Op)CreateVirtualOp, 4, 0, "axisName ?option value?...",}, {"delete", 1, (Blt_Op)DeleteVirtualOp, 3, 0, "?axisName?...",}, {"get", 1, (Blt_Op)GetOp, 4, 4, "name",}, {"invtransform", 1, (Blt_Op)InvTransformVirtualOp, 5, 5, "axisName value",}, {"limits", 1, (Blt_Op)LimitsVirtualOp, 4, 4, "axisName",}, {"names", 1, (Blt_Op)NamesVirtualOp, 3, 0, "?pattern?...",}, {"transform", 1, (Blt_Op)TransformVirtualOp, 5, 5, "axisName value",}, {"view", 1, (Blt_Op)ViewOp, 4, 7, "axisName ?moveto fract? ?scroll number what?",}, }; static int nAxisOps = sizeof(axisOps) / sizeof(Blt_OpSpec); proc = Blt_GetOp(interp, nAxisOps, axisOps, BLT_OP_ARG2, argc, argv, 0); if (proc == NULL) { return TCL_ERROR; } result = (*proc) (graphPtr, argc, argv); return result; } int Blt_AxisOp(graphPtr, margin, argc, argv) Graph *graphPtr; int margin; int argc; char **argv; { int result; Blt_Op proc; Axis *axisPtr; static Blt_OpSpec axisOps[] = { {"bind", 1, (Blt_Op)BindOp, 2, 5, "sequence command",}, {"cget", 2, (Blt_Op)CgetOp, 4, 4, "option",}, {"configure", 2, (Blt_Op)ConfigureOp, 3, 0, "?option value?...",}, {"invtransform", 1, (Blt_Op)InvTransformOp, 4, 4, "value",}, {"limits", 1, (Blt_Op)LimitsOp, 3, 3, "",}, {"transform", 1, (Blt_Op)TransformOp, 4, 4, "value",}, {"use", 1, (Blt_Op)UseOp, 3, 4, "?axisName?",}, }; static int nAxisOps = sizeof(axisOps) / sizeof(Blt_OpSpec); proc = Blt_GetOp(graphPtr->interp, nAxisOps, axisOps, BLT_OP_ARG2, argc, argv, 0); if (proc == NULL) { return TCL_ERROR; } argv[2] = (char *)margin; /* Hack. Slide a reference to the margin in * the argument list. Needed only for UseOp. */ axisPtr = Blt_GetFirstAxis(graphPtr->margins[margin].axes); result = (*proc)(graphPtr, axisPtr, argc - 3, argv + 3); return result; } void Blt_MapAxes(graphPtr) Graph *graphPtr; { Axis *axisPtr; Blt_Chain *chainPtr; Blt_ChainLink *linkPtr; register int margin; int offset; for (margin = 0; margin < 4; margin++) { chainPtr = graphPtr->margins[margin].axes; offset = 0; for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { axisPtr = Blt_ChainGetValue(linkPtr); if ((!axisPtr->hidden) && (axisPtr->flags & AXIS_ONSCREEN)) { MapAxis(graphPtr, axisPtr, offset, margin); if (AxisIsHorizontal(graphPtr, axisPtr)) { offset += axisPtr->height; } else { offset += axisPtr->width; } } } } } void Blt_DrawAxes(graphPtr, drawable) Graph *graphPtr; Drawable drawable; { Axis *axisPtr; Blt_ChainLink *linkPtr; register int i; for (i = 0; i < 4; i++) { for (linkPtr = Blt_ChainFirstLink(graphPtr->margins[i].axes); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { axisPtr = Blt_ChainGetValue(linkPtr); if ((!axisPtr->hidden) && (axisPtr->flags & AXIS_ONSCREEN)) { DrawAxis(graphPtr, drawable, axisPtr); } } } } void Blt_AxesToPostScript(graphPtr, psToken) Graph *graphPtr; PsToken psToken; { Axis *axisPtr; Blt_ChainLink *linkPtr; register int i; for (i = 0; i < 4; i++) { for (linkPtr = Blt_ChainFirstLink(graphPtr->margins[i].axes); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { axisPtr = Blt_ChainGetValue(linkPtr); if ((!axisPtr->hidden) && (axisPtr->flags & AXIS_ONSCREEN)) { AxisToPostScript(psToken, axisPtr); } } } } /* * ---------------------------------------------------------------------- * * Blt_DrawAxisLimits -- * * Draws the min/max values of the axis in the plotting area. * The text strings are formatted according to the "sprintf" * format descriptors in the limitsFormats array. * * Results: * None. * * Side Effects: * Draws the numeric values of the axis limits into the outer * regions of the plotting area. * * ---------------------------------------------------------------------- */ void Blt_DrawAxisLimits(graphPtr, drawable) Graph *graphPtr; Drawable drawable; { Axis *axisPtr; Blt_HashEntry *hPtr; Blt_HashSearch cursor; Dim2D textDim; int isHoriz; char *minPtr, *maxPtr; char *minFormat, *maxFormat; char minString[200], maxString[200]; int vMin, hMin, vMax, hMax; #define SPACING 8 vMin = vMax = graphPtr->left + graphPtr->padLeft + 2; hMin = hMax = graphPtr->bottom - graphPtr->padBottom - 2; /* Offsets */ for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { axisPtr = (Axis *)Blt_GetHashValue(hPtr); if (axisPtr->nFormats == 0) { continue; } isHoriz = AxisIsHorizontal(graphPtr, axisPtr); minPtr = maxPtr = NULL; minFormat = maxFormat = axisPtr->limitsFormats[0]; if (axisPtr->nFormats > 1) { maxFormat = axisPtr->limitsFormats[1]; } if (minFormat[0] != '\0') { minPtr = minString; sprintf(minString, minFormat, axisPtr->axisRange.min); } if (maxFormat[0] != '\0') { maxPtr = maxString; sprintf(maxString, maxFormat, axisPtr->axisRange.max); } if (axisPtr->descending) { char *tmp; tmp = minPtr, minPtr = maxPtr, maxPtr = tmp; } if (maxPtr != NULL) { if (isHoriz) { axisPtr->limitsTextStyle.theta = 90.0; axisPtr->limitsTextStyle.anchor = TK_ANCHOR_SE; Blt_DrawText2(graphPtr->tkwin, drawable, maxPtr, &axisPtr->limitsTextStyle, graphPtr->right, hMax, &textDim); hMax -= (textDim.height + SPACING); } else { axisPtr->limitsTextStyle.theta = 0.0; axisPtr->limitsTextStyle.anchor = TK_ANCHOR_NW; Blt_DrawText2(graphPtr->tkwin, drawable, maxPtr, &axisPtr->limitsTextStyle, vMax, graphPtr->top, &textDim); vMax += (textDim.width + SPACING); } } if (minPtr != NULL) { axisPtr->limitsTextStyle.anchor = TK_ANCHOR_SW; if (isHoriz) { axisPtr->limitsTextStyle.theta = 90.0; Blt_DrawText2(graphPtr->tkwin, drawable, minPtr, &axisPtr->limitsTextStyle, graphPtr->left, hMin, &textDim); hMin -= (textDim.height + SPACING); } else { axisPtr->limitsTextStyle.theta = 0.0; Blt_DrawText2(graphPtr->tkwin, drawable, minPtr, &axisPtr->limitsTextStyle, vMin, graphPtr->bottom, &textDim); vMin += (textDim.width + SPACING); } } } /* Loop on axes */ } void Blt_AxisLimitsToPostScript(graphPtr, psToken) Graph *graphPtr; PsToken psToken; { Axis *axisPtr; Blt_HashEntry *hPtr; Blt_HashSearch cursor; double vMin, hMin, vMax, hMax; char string[200]; int textWidth, textHeight; char *minFmt, *maxFmt; #define SPACING 8 vMin = vMax = graphPtr->left + graphPtr->padLeft + 2; hMin = hMax = graphPtr->bottom - graphPtr->padBottom - 2; /* Offsets */ for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { axisPtr = (Axis *)Blt_GetHashValue(hPtr); if (axisPtr->nFormats == 0) { continue; } minFmt = maxFmt = axisPtr->limitsFormats[0]; if (axisPtr->nFormats > 1) { maxFmt = axisPtr->limitsFormats[1]; } if (*maxFmt != '\0') { sprintf(string, maxFmt, axisPtr->axisRange.max); Blt_GetTextExtents(&axisPtr->tickTextStyle, string, &textWidth, &textHeight); if ((textWidth > 0) && (textHeight > 0)) { if (axisPtr->classUid == bltXAxisUid) { axisPtr->limitsTextStyle.theta = 90.0; axisPtr->limitsTextStyle.anchor = TK_ANCHOR_SE; Blt_TextToPostScript(psToken, string, &axisPtr->limitsTextStyle, (double)graphPtr->right, hMax); hMax -= (textWidth + SPACING); } else { axisPtr->limitsTextStyle.theta = 0.0; axisPtr->limitsTextStyle.anchor = TK_ANCHOR_NW; Blt_TextToPostScript(psToken, string, &axisPtr->limitsTextStyle, vMax, (double)graphPtr->top); vMax += (textWidth + SPACING); } } } if (*minFmt != '\0') { sprintf(string, minFmt, axisPtr->axisRange.min); Blt_GetTextExtents(&axisPtr->tickTextStyle, string, &textWidth, &textHeight); if ((textWidth > 0) && (textHeight > 0)) { axisPtr->limitsTextStyle.anchor = TK_ANCHOR_SW; if (axisPtr->classUid == bltXAxisUid) { axisPtr->limitsTextStyle.theta = 90.0; Blt_TextToPostScript(psToken, string, &axisPtr->limitsTextStyle, (double)graphPtr->left, hMin); hMin -= (textWidth + SPACING); } else { axisPtr->limitsTextStyle.theta = 0.0; Blt_TextToPostScript(psToken, string, &axisPtr->limitsTextStyle, vMin, (double)graphPtr->bottom); vMin += (textWidth + SPACING); } } } } } Axis * Blt_GetFirstAxis(chainPtr) Blt_Chain *chainPtr; { Blt_ChainLink *linkPtr; linkPtr = Blt_ChainFirstLink(chainPtr); if (linkPtr == NULL) { return NULL; } return Blt_ChainGetValue(linkPtr); } Axis * Blt_NearestAxis(graphPtr, x, y) Graph *graphPtr; int x, y; /* Point to be tested */ { register Blt_HashEntry *hPtr; Blt_HashSearch cursor; Axis *axisPtr; int width, height; double rotWidth, rotHeight; Point2D bbox[5]; for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor); hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) { axisPtr = (Axis *)Blt_GetHashValue(hPtr); if ((axisPtr->hidden) || (!(axisPtr->flags & AXIS_ONSCREEN))) { continue; /* Don't check hidden axes or axes * that are virtual. */ } if (axisPtr->showTicks) { register Blt_ChainLink *linkPtr; TickLabel *labelPtr; Point2D t; for (linkPtr = Blt_ChainFirstLink(axisPtr->tickLabels); linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) { labelPtr = Blt_ChainGetValue(linkPtr); Blt_GetBoundingBox(labelPtr->width, labelPtr->height, axisPtr->tickTextStyle.theta, &rotWidth, &rotHeight, bbox); width = ROUND(rotWidth); height = ROUND(rotHeight); t = Blt_TranslatePoint(&labelPtr->anchorPos, width, height, axisPtr->tickTextStyle.anchor); t.x = x - t.x - (width * 0.5); t.y = y - t.y - (height * 0.5); bbox[4] = bbox[0]; if (Blt_PointInPolygon(&t, bbox, 5)) { axisPtr->detail = "label"; return axisPtr; } } } if (axisPtr->title != NULL) { /* and then the title string. */ Point2D t; Blt_GetTextExtents(&axisPtr->titleTextStyle, axisPtr->title,&width, &height); Blt_GetBoundingBox(width, height, axisPtr->titleTextStyle.theta, &rotWidth, &rotHeight, bbox); width = ROUND(rotWidth); height = ROUND(rotHeight); t = Blt_TranslatePoint(&axisPtr->titlePos, width, height, axisPtr->titleTextStyle.anchor); /* Translate the point so that the 0,0 is the upper left * corner of the bounding box. */ t.x = x - t.x - (width / 2); t.y = y - t.y - (height / 2); bbox[4] = bbox[0]; if (Blt_PointInPolygon(&t, bbox, 5)) { axisPtr->detail = "title"; return axisPtr; } } if (axisPtr->lineWidth > 0) { /* Check for the axis region */ if (PointInRegion(&axisPtr->region, x, y)) { axisPtr->detail = "line"; return axisPtr; } } } return NULL; } ClientData Blt_MakeAxisTag(graphPtr, tagName) Graph *graphPtr; char *tagName; { Blt_HashEntry *hPtr; int isNew; hPtr = Blt_CreateHashEntry(&graphPtr->axes.tagTable, tagName, &isNew); assert(hPtr); return Blt_GetHashKey(&graphPtr->axes.tagTable, hPtr); }