1
2 /*
3 * bltGrAxis.c --
4 *
5 * This module implements coordinate axes for the BLT graph widget.
6 *
7 * Copyright 1993-1998 Lucent Technologies, Inc.
8 *
9 * Permission to use, copy, modify, and distribute this software and
10 * its documentation for any purpose and without fee is hereby
11 * granted, provided that the above copyright notice appear in all
12 * copies and that both that the copyright notice and warranty
13 * disclaimer appear in supporting documentation, and that the names
14 * of Lucent Technologies any of their entities not be used in
15 * advertising or publicity pertaining to distribution of the software
16 * without specific, written prior permission.
17 *
18 * Lucent Technologies disclaims all warranties with regard to this
19 * software, including all implied warranties of merchantability and
20 * fitness. In no event shall Lucent Technologies be liable for any
21 * special, indirect or consequential damages or any damages
22 * whatsoever resulting from loss of use, data or profits, whether in
23 * an action of contract, negligence or other tortuous action, arising
24 * out of or in connection with the use or performance of this
25 * software.
26 */
27
28 #include "bltGraph.h"
29 #include "bltGrElem.h"
30 #include <X11/Xutil.h>
31
32 #define DEF_NUM_TICKS 4 /* Each minor tick is 20% */
33 #define STATIC_TICK_SPACE 10
34
35 #define TICK_LABEL_SIZE 200
36 #define MAXTICKS 10001
37
38 #define CLAMP(val,low,high) \
39 (((val) < (low)) ? (low) : ((val) > (high)) ? (high) : (val))
40
41 /*
42 * Round x in terms of units
43 */
44 #define UROUND(x,u) (Round((x)/(u))*(u))
45 #define UCEIL(x,u) (ceil((x)/(u))*(u))
46 #define UFLOOR(x,u) (floor((x)/(u))*(u))
47
48 #define LENGTH_MAJOR_TICK 0.030 /* Length of a major tick */
49 #define LENGTH_MINOR_TICK 0.015 /* Length of a minor (sub)tick */
50 #define LENGTH_LABEL_TICK 0.040 /* Distance from graph to start of the
51 * label */
52 #define NUMDIGITS 15 /* Specifies the number of
53 * digits of accuracy used when
54 * outputting axis tick labels. */
55 #define AVG_TICK_NUM_CHARS 16 /* Assumed average tick label size */
56
57 #define TICK_RANGE_TIGHT 0
58 #define TICK_RANGE_LOOSE 1
59 #define TICK_RANGE_ALWAYS_LOOSE 2
60
61 #define AXIS_TITLE_PAD 2 /* Padding for axis title. */
62 #define AXIS_LINE_PAD 1 /* Padding for axis line. */
63
64 #define HORIZMARGIN(m) (!((m)->site & 0x1)) /* Even sites are horizontal */
65
66 typedef enum AxisComponents {
67 MAJOR_TICK, MINOR_TICK, TICK_LABEL, AXIS_LINE
68 } AxisComponent;
69
70
71 typedef struct {
72 int axis; /* Length of the axis. */
73 int t1; /* Length of a major tick (in pixels). */
74 int t2; /* Length of a minor tick (in pixels). */
75 int label; /* Distance from axis to tick label. */
76 } AxisInfo;
77
78 extern Tk_CustomOption bltDistanceOption;
79 extern Tk_CustomOption bltPositiveDistanceOption;
80 extern Tk_CustomOption bltShadowOption;
81 extern Tk_CustomOption bltListOption;
82
83 static Tk_OptionParseProc StringToLimit;
84 static Tk_OptionPrintProc LimitToString;
85 static Tk_OptionParseProc StringToTicks;
86 static Tk_OptionPrintProc TicksToString;
87 static Tk_OptionParseProc StringToAxis;
88 static Tk_OptionPrintProc AxisToString;
89 static Tk_OptionParseProc StringToAnyAxis;
90 static Tk_OptionParseProc StringToFormat;
91 static Tk_OptionPrintProc FormatToString;
92 static Tk_OptionParseProc StringToLoose;
93 static Tk_OptionPrintProc LooseToString;
94
95 static Tk_CustomOption limitOption =
96 {
97 StringToLimit, LimitToString, (ClientData)0
98 };
99
100 static Tk_CustomOption majorTicksOption =
101 {
102 StringToTicks, TicksToString, (ClientData)AXIS_CONFIG_MAJOR,
103 };
104 static Tk_CustomOption minorTicksOption =
105 {
106 StringToTicks, TicksToString, (ClientData)AXIS_CONFIG_MINOR,
107 };
108 Tk_CustomOption bltXAxisOption =
109 {
110 StringToAxis, AxisToString, (ClientData)&bltXAxisUid
111 };
112 Tk_CustomOption bltYAxisOption =
113 {
114 StringToAxis, AxisToString, (ClientData)&bltYAxisUid
115 };
116 Tk_CustomOption bltAnyXAxisOption =
117 {
118 StringToAnyAxis, AxisToString, (ClientData)&bltXAxisUid
119 };
120 Tk_CustomOption bltAnyYAxisOption =
121 {
122 StringToAnyAxis, AxisToString, (ClientData)&bltYAxisUid
123 };
124 static Tk_CustomOption formatOption =
125 {
126 StringToFormat, FormatToString, (ClientData)0,
127 };
128 static Tk_CustomOption looseOption =
129 {
130 StringToLoose, LooseToString, (ClientData)0,
131 };
132
133 /* Axis flags: */
134
135 #define DEF_AXIS_COMMAND (char *)NULL
136 #define DEF_AXIS_DESCENDING "no"
137 #define DEF_AXIS_FOREGROUND RGB_BLACK
138 #define DEF_AXIS_FG_MONO RGB_BLACK
139 #define DEF_AXIS_HIDE "no"
140 #define DEF_AXIS_JUSTIFY "center"
141 #define DEF_AXIS_LIMITS_FORMAT (char *)NULL
142 #define DEF_AXIS_LINE_WIDTH "1"
143 #define DEF_AXIS_LOGSCALE "no"
144 #define DEF_AXIS_LOOSE "no"
145 #define DEF_AXIS_RANGE "0.0"
146 #define DEF_AXIS_ROTATE "0.0"
147 #define DEF_AXIS_SCROLL_INCREMENT "10"
148 #define DEF_AXIS_SHIFTBY "0.0"
149 #define DEF_AXIS_SHOWTICKS "yes"
150 #define DEF_AXIS_STEP "0.0"
151 #define DEF_AXIS_STEP "0.0"
152 #define DEF_AXIS_SUBDIVISIONS "2"
153 #define DEF_AXIS_TAGS "all"
154 #define DEF_AXIS_TICKS "0"
155 #ifdef WIN32
156 #define DEF_AXIS_TICK_FONT "{Arial Narrow} 8"
157 #else
158 #define DEF_AXIS_TICK_FONT "*-Helvetica-Medium-R-Normal-*-10-*"
159 #endif
160 #define DEF_AXIS_TICK_LENGTH "8"
161 #define DEF_AXIS_TITLE_ALTERNATE "0"
162 #define DEF_AXIS_TITLE_FG RGB_BLACK
163 #define DEF_AXIS_TITLE_FONT STD_FONT
164 #define DEF_AXIS_X_STEP_BARCHART "1.0"
165 #define DEF_AXIS_X_SUBDIVISIONS_BARCHART "0"
166 #define DEF_AXIS_BACKGROUND (char *)NULL
167 #define DEF_AXIS_BORDERWIDTH "0"
168 #define DEF_AXIS_RELIEF "flat"
169
170 static Tk_ConfigSpec configSpecs[] =
171 {
172 {TK_CONFIG_DOUBLE, "-autorange", "autoRange", "AutoRange",
173 DEF_AXIS_RANGE, Tk_Offset(Axis, windowSize),
174 ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
175 {TK_CONFIG_BORDER, "-background", "background", "Background",
176 DEF_AXIS_BACKGROUND, Tk_Offset(Axis, border),
177 ALL_GRAPHS | TK_CONFIG_NULL_OK},
178 {TK_CONFIG_SYNONYM, "-bg", "background", (char *)NULL, (char *)NULL, 0, 0},
179 {TK_CONFIG_CUSTOM, "-bindtags", "bindTags", "BindTags",
180 DEF_AXIS_TAGS, Tk_Offset(Axis, tags),
181 ALL_GRAPHS | TK_CONFIG_NULL_OK, &bltListOption},
182 {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *)NULL,
183 (char *)NULL, 0, ALL_GRAPHS},
184 {TK_CONFIG_CUSTOM, "-borderwidth", "borderWidth", "BorderWidth",
185 DEF_AXIS_BORDERWIDTH, Tk_Offset(Axis, borderWidth),
186 ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
187 {TK_CONFIG_COLOR, "-color", "color", "Color",
188 DEF_AXIS_FOREGROUND, Tk_Offset(Axis, tickTextStyle.color),
189 TK_CONFIG_COLOR_ONLY | ALL_GRAPHS},
190 {TK_CONFIG_COLOR, "-color", "color", "Color",
191 DEF_AXIS_FG_MONO, Tk_Offset(Axis, tickTextStyle.color),
192 TK_CONFIG_MONO_ONLY | ALL_GRAPHS},
193 {TK_CONFIG_STRING, "-command", "command", "Command",
194 DEF_AXIS_COMMAND, Tk_Offset(Axis, formatCmd),
195 TK_CONFIG_NULL_OK | ALL_GRAPHS},
196 {TK_CONFIG_BOOLEAN, "-descending", "descending", "Descending",
197 DEF_AXIS_DESCENDING, Tk_Offset(Axis, descending),
198 ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
199 {TK_CONFIG_BOOLEAN, "-hide", "hide", "Hide",
200 DEF_AXIS_HIDE, Tk_Offset(Axis, hidden),
201 ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
202 {TK_CONFIG_JUSTIFY, "-justify", "justify", "Justify",
203 DEF_AXIS_JUSTIFY, Tk_Offset(Axis, titleTextStyle.justify),
204 ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
205 {TK_CONFIG_BOOLEAN, "-labeloffset", "labelOffset", "LabelOffset",
206 (char *)NULL, Tk_Offset(Axis, labelOffset), ALL_GRAPHS},
207 {TK_CONFIG_COLOR, "-limitscolor", "limitsColor", "Color",
208 DEF_AXIS_FOREGROUND, Tk_Offset(Axis, limitsTextStyle.color),
209 TK_CONFIG_COLOR_ONLY | ALL_GRAPHS},
210 {TK_CONFIG_COLOR, "-limitscolor", "limitsColor", "Color",
211 DEF_AXIS_FG_MONO, Tk_Offset(Axis, limitsTextStyle.color),
212 TK_CONFIG_MONO_ONLY | ALL_GRAPHS},
213 {TK_CONFIG_FONT, "-limitsfont", "limitsFont", "Font",
214 DEF_AXIS_TICK_FONT, Tk_Offset(Axis, limitsTextStyle.font), ALL_GRAPHS},
215 {TK_CONFIG_CUSTOM, "-limitsformat", "limitsFormat", "LimitsFormat",
216 (char *)NULL, Tk_Offset(Axis, limitsFormats),
217 TK_CONFIG_NULL_OK | ALL_GRAPHS, &formatOption},
218 {TK_CONFIG_CUSTOM, "-limitsshadow", "limitsShadow", "Shadow",
219 (char *)NULL, Tk_Offset(Axis, limitsTextStyle.shadow),
220 TK_CONFIG_COLOR_ONLY | ALL_GRAPHS, &bltShadowOption},
221 {TK_CONFIG_CUSTOM, "-limitsshadow", "limitsShadow", "Shadow",
222 (char *)NULL, Tk_Offset(Axis, limitsTextStyle.shadow),
223 TK_CONFIG_MONO_ONLY | ALL_GRAPHS, &bltShadowOption},
224 {TK_CONFIG_CUSTOM, "-linewidth", "lineWidth", "LineWidth",
225 DEF_AXIS_LINE_WIDTH, Tk_Offset(Axis, lineWidth),
226 ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT, &bltDistanceOption},
227 {TK_CONFIG_BOOLEAN, "-logscale", "logScale", "LogScale",
228 DEF_AXIS_LOGSCALE, Tk_Offset(Axis, logScale),
229 ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
230 {TK_CONFIG_CUSTOM, "-loose", "loose", "Loose",
231 DEF_AXIS_LOOSE, 0, ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT,
232 &looseOption},
233 {TK_CONFIG_CUSTOM, "-majorticks", "majorTicks", "MajorTicks",
234 (char *)NULL, Tk_Offset(Axis, t1Ptr),
235 TK_CONFIG_NULL_OK | ALL_GRAPHS, &majorTicksOption},
236 {TK_CONFIG_CUSTOM, "-max", "max", "Max",
237 (char *)NULL, Tk_Offset(Axis, reqMax),
238 TK_CONFIG_NULL_OK | ALL_GRAPHS, &limitOption},
239 {TK_CONFIG_CUSTOM, "-min", "min", "Min",
240 (char *)NULL, Tk_Offset(Axis, reqMin),
241 TK_CONFIG_NULL_OK | ALL_GRAPHS, &limitOption},
242 {TK_CONFIG_CUSTOM, "-minorticks", "minorTicks", "MinorTicks",
243 (char *)NULL, Tk_Offset(Axis, t2Ptr),
244 TK_CONFIG_NULL_OK | ALL_GRAPHS, &minorTicksOption},
245 {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
246 DEF_AXIS_RELIEF, Tk_Offset(Axis, relief),
247 ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
248 {TK_CONFIG_DOUBLE, "-rotate", "rotate", "Rotate",
249 DEF_AXIS_ROTATE, Tk_Offset(Axis, tickTextStyle.theta),
250 ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
251 {TK_CONFIG_STRING, "-scrollcommand", "scrollCommand", "ScrollCommand",
252 (char *)NULL, Tk_Offset(Axis, scrollCmdPrefix),
253 ALL_GRAPHS | TK_CONFIG_NULL_OK},
254 {TK_CONFIG_CUSTOM, "-scrollincrement", "scrollIncrement", "ScrollIncrement",
255 DEF_AXIS_SCROLL_INCREMENT, Tk_Offset(Axis, scrollUnits),
256 ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT, &bltPositiveDistanceOption},
257 {TK_CONFIG_CUSTOM, "-scrollmax", "scrollMax", "ScrollMax",
258 (char *)NULL, Tk_Offset(Axis, scrollMax),
259 TK_CONFIG_NULL_OK | ALL_GRAPHS, &limitOption},
260 {TK_CONFIG_CUSTOM, "-scrollmin", "scrollMin", "ScrollMin",
261 (char *)NULL, Tk_Offset(Axis, scrollMin),
262 TK_CONFIG_NULL_OK | ALL_GRAPHS, &limitOption},
263 {TK_CONFIG_DOUBLE, "-shiftby", "shiftBy", "ShiftBy",
264 DEF_AXIS_SHIFTBY, Tk_Offset(Axis, shiftBy),
265 ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
266 {TK_CONFIG_BOOLEAN, "-showticks", "showTicks", "ShowTicks",
267 DEF_AXIS_SHOWTICKS, Tk_Offset(Axis, showTicks),
268 ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
269 {TK_CONFIG_DOUBLE, "-stepsize", "stepSize", "StepSize",
270 DEF_AXIS_STEP, Tk_Offset(Axis, reqStep),
271 ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
272 {TK_CONFIG_DOUBLE, "-tickdivider", "tickDivider", "TickDivider",
273 DEF_AXIS_STEP, Tk_Offset(Axis, tickZoom),
274 ALL_GRAPHS | TK_CONFIG_DONT_SET_DEFAULT},
275 {TK_CONFIG_INT, "-subdivisions", "subdivisions", "Subdivisions",
276 DEF_AXIS_SUBDIVISIONS, Tk_Offset(Axis, reqNumMinorTicks),
277 ALL_GRAPHS},
278 {TK_CONFIG_FONT, "-tickfont", "tickFont", "Font",
279 DEF_AXIS_TICK_FONT, Tk_Offset(Axis, tickTextStyle.font), ALL_GRAPHS},
280 {TK_CONFIG_PIXELS, "-ticklength", "tickLength", "TickLength",
281 DEF_AXIS_TICK_LENGTH, Tk_Offset(Axis, tickLength), ALL_GRAPHS},
282 {TK_CONFIG_CUSTOM, "-tickshadow", "tickShadow", "Shadow",
283 (char *)NULL, Tk_Offset(Axis, tickTextStyle.shadow),
284 TK_CONFIG_COLOR_ONLY | ALL_GRAPHS, &bltShadowOption},
285 {TK_CONFIG_CUSTOM, "-tickshadow", "tickShadow", "Shadow",
286 (char *)NULL, Tk_Offset(Axis, tickTextStyle.shadow),
287 TK_CONFIG_MONO_ONLY | ALL_GRAPHS, &bltShadowOption},
288 {TK_CONFIG_STRING, "-title", "title", "Title",
289 (char *)NULL, Tk_Offset(Axis, title),
290 TK_CONFIG_DONT_SET_DEFAULT | TK_CONFIG_NULL_OK | ALL_GRAPHS},
291 {TK_CONFIG_BOOLEAN, "-titlealternate", "titleAlternate", "TitleAlternate",
292 DEF_AXIS_TITLE_ALTERNATE, Tk_Offset(Axis, titleAlternate),
293 TK_CONFIG_DONT_SET_DEFAULT | ALL_GRAPHS},
294 {TK_CONFIG_COLOR, "-titlecolor", "titleColor", "Color",
295 DEF_AXIS_FOREGROUND, Tk_Offset(Axis, titleTextStyle.color),
296 TK_CONFIG_COLOR_ONLY | ALL_GRAPHS},
297 {TK_CONFIG_COLOR, "-titlecolor", "titleColor", "TitleColor",
298 DEF_AXIS_FG_MONO, Tk_Offset(Axis, titleTextStyle.color),
299 TK_CONFIG_MONO_ONLY | ALL_GRAPHS},
300 {TK_CONFIG_FONT, "-titlefont", "titleFont", "Font",
301 DEF_AXIS_TITLE_FONT, Tk_Offset(Axis, titleTextStyle.font), ALL_GRAPHS},
302 {TK_CONFIG_CUSTOM, "-titleshadow", "titleShadow", "Shadow",
303 (char *)NULL, Tk_Offset(Axis, titleTextStyle.shadow),
304 TK_CONFIG_COLOR_ONLY | ALL_GRAPHS, &bltShadowOption},
305 {TK_CONFIG_CUSTOM, "-titleshadow", "titleShadow", "Shadow",
306 (char *)NULL, Tk_Offset(Axis, titleTextStyle.shadow),
307 TK_CONFIG_MONO_ONLY | ALL_GRAPHS, &bltShadowOption},
308 {TK_CONFIG_END, NULL, NULL, NULL, NULL, 0, 0}
309 };
310
311 /* Forward declarations */
312 static void DestroyAxis _ANSI_ARGS_((Graph *graphPtr, Axis *axisPtr));
313 static int GetAxis _ANSI_ARGS_((Graph *graphPtr, char *name, Blt_Uid classUid,
314 Axis **axisPtrPtr));
315 static void FreeAxis _ANSI_ARGS_((Graph *graphPtr, Axis *axisPtr));
316
317 INLINE static int
Round(register double x)318 Round(register double x)
319 {
320 return (int) (x + ((x < 0.0) ? -0.5 : 0.5));
321 }
322
323 static void
SetAxisRange(AxisRange * rangePtr,double min,double max)324 SetAxisRange(AxisRange *rangePtr, double min, double max)
325 {
326 rangePtr->min = min;
327 rangePtr->max = max;
328 rangePtr->range = max - min;
329 if (FABS(rangePtr->range) < DBL_EPSILON) {
330 rangePtr->range = 1.0;
331 }
332 rangePtr->scale = 1.0 / rangePtr->range;
333 }
334
335 /*
336 * ----------------------------------------------------------------------
337 *
338 * InRange --
339 *
340 * Determines if a value lies within a given range.
341 *
342 * The value is normalized and compared against the interval
343 * [0..1], where 0.0 is the minimum and 1.0 is the maximum.
344 * DBL_EPSILON is the smallest number that can be represented
345 * on the host machine, such that (1.0 + epsilon) != 1.0.
346 *
347 * Please note, *max* can't equal *min*.
348 *
349 * Results:
350 * If the value is within the interval [min..max], 1 is
351 * returned; 0 otherwise.
352 *
353 * ----------------------------------------------------------------------
354 */
355 INLINE static int
InRange(x,rangePtr)356 InRange(x, rangePtr)
357 register double x;
358 AxisRange *rangePtr;
359 {
360 if (rangePtr->range < DBL_EPSILON) {
361 #ifdef notdef
362 return (((rangePtr->max - x) >= (FABS(x) * DBL_EPSILON)) &&
363 ((x - rangePtr->min) >= (FABS(x) * DBL_EPSILON)));
364 #endif
365 return (FABS(rangePtr->max - x) >= DBL_EPSILON);
366 } else {
367 double norm;
368
369 norm = (x - rangePtr->min) * rangePtr->scale;
370 return ((norm >= -DBL_EPSILON) && ((norm - 1.0) < DBL_EPSILON));
371 }
372 }
373
374 INLINE static int
AxisIsHorizontal(graphPtr,axisPtr)375 AxisIsHorizontal(graphPtr, axisPtr)
376 Graph *graphPtr;
377 Axis *axisPtr;
378 {
379 return ((axisPtr->classUid == bltYAxisUid) == graphPtr->inverted);
380 }
381
382
383 /* ----------------------------------------------------------------------
384 * Custom option parse and print procedures
385 * ----------------------------------------------------------------------
386 */
387
388 /*
389 *----------------------------------------------------------------------
390 *
391 * StringToAnyAxis --
392 *
393 * Converts the name of an axis to a pointer to its axis structure.
394 *
395 * Results:
396 * The return value is a standard Tcl result. The axis flags are
397 * written into the widget record.
398 *
399 *----------------------------------------------------------------------
400 */
401 /*ARGSUSED*/
402 static int
StringToAnyAxis(clientData,interp,tkwin,string,widgRec,offset)403 StringToAnyAxis(clientData, interp, tkwin, string, widgRec, offset)
404 ClientData clientData; /* Class identifier of the type of
405 * axis we are looking for. */
406 Tcl_Interp *interp; /* Interpreter to send results back to. */
407 Tk_Window tkwin; /* Used to look up pointer to graph. */
408 char *string; /* String representing new value. */
409 char *widgRec; /* Pointer to structure record. */
410 int offset; /* Offset of field in structure. */
411 {
412 Axis **axisPtrPtr = (Axis **)(widgRec + offset);
413 Blt_Uid classUid = *(Blt_Uid *)clientData;
414 Graph *graphPtr;
415 Axis *axisPtr;
416
417 graphPtr = Blt_GetGraphFromWindowData(tkwin);
418 if (*axisPtrPtr != NULL) {
419 FreeAxis(graphPtr, *axisPtrPtr);
420 }
421 if (string[0] == '\0') {
422 axisPtr = NULL;
423 } else if (GetAxis(graphPtr, string, classUid, &axisPtr) != TCL_OK) {
424 return TCL_ERROR;
425 }
426 *axisPtrPtr = axisPtr;
427 return TCL_OK;
428 }
429
430 /*
431 *----------------------------------------------------------------------
432 *
433 * StringToAxis --
434 *
435 * Converts the name of an axis to a pointer to its axis structure.
436 *
437 * Results:
438 * The return value is a standard Tcl result. The axis flags are
439 * written into the widget record.
440 *
441 *----------------------------------------------------------------------
442 */
443 /*ARGSUSED*/
444 static int
StringToAxis(clientData,interp,tkwin,string,widgRec,offset)445 StringToAxis(clientData, interp, tkwin, string, widgRec, offset)
446 ClientData clientData; /* Class identifier of the type of
447 * axis we are looking for. */
448 Tcl_Interp *interp; /* Interpreter to send results back to. */
449 Tk_Window tkwin; /* Used to look up pointer to graph. */
450 char *string; /* String representing new value. */
451 char *widgRec; /* Pointer to structure record. */
452 int offset; /* Offset of field in structure. */
453 {
454 Axis **axisPtrPtr = (Axis **)(widgRec + offset);
455 Blt_Uid classUid = *(Blt_Uid *)clientData;
456 Graph *graphPtr;
457
458 graphPtr = Blt_GetGraphFromWindowData(tkwin);
459 if (*axisPtrPtr != NULL) {
460 FreeAxis(graphPtr, *axisPtrPtr);
461 }
462 if (GetAxis(graphPtr, string, classUid, axisPtrPtr) != TCL_OK) {
463 return TCL_ERROR;
464 }
465 return TCL_OK;
466 }
467
468 /*
469 *----------------------------------------------------------------------
470 *
471 * AxisToString --
472 *
473 * Convert the window coordinates into a string.
474 *
475 * Results:
476 * The string representing the coordinate position is returned.
477 *
478 *----------------------------------------------------------------------
479 */
480 /*ARGSUSED*/
481 static char *
AxisToString(clientData,tkwin,widgRec,offset,freeProcPtr)482 AxisToString(clientData, tkwin, widgRec, offset, freeProcPtr)
483 ClientData clientData; /* Not used. */
484 Tk_Window tkwin; /* Not used. */
485 char *widgRec; /* Pointer to structure record .*/
486 int offset; /* Offset of field in structure. */
487 Tcl_FreeProc **freeProcPtr; /* Not used. */
488 {
489 Axis *axisPtr = *(Axis **)(widgRec + offset);
490
491 if (axisPtr == NULL) {
492 return "";
493 }
494 return axisPtr->name;
495 }
496
497 /*
498 *----------------------------------------------------------------------
499 *
500 * StringToFormat --
501 *
502 * Convert the name of virtual axis to an pointer.
503 *
504 * Results:
505 * The return value is a standard Tcl result. The axis flags are
506 * written into the widget record.
507 *
508 *----------------------------------------------------------------------
509 */
510 /*ARGSUSED*/
511 static int
StringToFormat(clientData,interp,tkwin,string,widgRec,offset)512 StringToFormat(clientData, interp, tkwin, string, widgRec, offset)
513 ClientData clientData; /* Not used. */
514 Tcl_Interp *interp; /* Interpreter to send results back to. */
515 Tk_Window tkwin; /* Used to look up pointer to graph */
516 char *string; /* String representing new value. */
517 char *widgRec; /* Pointer to structure record. */
518 int offset; /* Offset of field in structure. */
519 {
520 Axis *axisPtr = (Axis *)(widgRec);
521 char **argv;
522 int argc;
523
524 if (axisPtr->limitsFormats != NULL) {
525 Blt_Free(axisPtr->limitsFormats);
526 }
527 axisPtr->limitsFormats = NULL;
528 axisPtr->nFormats = 0;
529
530 if ((string == NULL) || (*string == '\0')) {
531 return TCL_OK;
532 }
533 if (Tcl_SplitList(interp, string, &argc, &argv) != TCL_OK) {
534 return TCL_ERROR;
535 }
536 if (argc > 2) {
537 Tcl_AppendResult(interp, "too many elements in limits format list \"",
538 string, "\"", (char *)NULL);
539 Blt_Free(argv);
540 return TCL_ERROR;
541 }
542 axisPtr->limitsFormats = argv;
543 axisPtr->nFormats = argc;
544 return TCL_OK;
545 }
546
547 /*
548 *----------------------------------------------------------------------
549 *
550 * FormatToString --
551 *
552 * Convert the window coordinates into a string.
553 *
554 * Results:
555 * The string representing the coordinate position is returned.
556 *
557 *----------------------------------------------------------------------
558 */
559 /*ARGSUSED*/
560 static char *
FormatToString(clientData,tkwin,widgRec,offset,freeProcPtr)561 FormatToString(clientData, tkwin, widgRec, offset, freeProcPtr)
562 ClientData clientData; /* Not used. */
563 Tk_Window tkwin; /* Not used. */
564 char *widgRec; /* Widget record */
565 int offset; /* offset of limits field */
566 Tcl_FreeProc **freeProcPtr; /* Not used. */
567 {
568 Axis *axisPtr = (Axis *)(widgRec);
569
570 if (axisPtr->nFormats == 0) {
571 return "";
572 }
573 *freeProcPtr = (Tcl_FreeProc *)Blt_Free;
574 return Tcl_Merge(axisPtr->nFormats, axisPtr->limitsFormats);
575 }
576
577 /*
578 * ----------------------------------------------------------------------
579 *
580 * StringToLimit --
581 *
582 * Convert the string representation of an axis limit into its numeric
583 * form.
584 *
585 * Results:
586 * The return value is a standard Tcl result. The symbol type is
587 * written into the widget record.
588 *
589 * ----------------------------------------------------------------------
590 */
591 /*ARGSUSED*/
592 static int
StringToLimit(clientData,interp,tkwin,string,widgRec,offset)593 StringToLimit(clientData, interp, tkwin, string, widgRec, offset)
594 ClientData clientData; /* Either AXIS_CONFIG_MIN or AXIS_CONFIG_MAX.
595 * Indicates which axis limit to set. */
596 Tcl_Interp *interp; /* Interpreter to send results back to */
597 Tk_Window tkwin; /* Not used. */
598 char *string; /* String representing new value. */
599 char *widgRec; /* Pointer to structure record. */
600 int offset; /* Offset of field in structure. */
601 {
602 double *limitPtr = (double *)(widgRec + offset);
603
604 if ((string == NULL) || (*string == '\0')) {
605 *limitPtr = VALUE_UNDEFINED;
606 } else if (Tcl_ExprDouble(interp, string, limitPtr) != TCL_OK) {
607 return TCL_ERROR;
608 }
609 return TCL_OK;
610 }
611
612 /*
613 * ----------------------------------------------------------------------
614 *
615 * LimitToString --
616 *
617 * Convert the floating point axis limits into a string.
618 *
619 * Results:
620 * The string representation of the limits is returned.
621 *
622 * ----------------------------------------------------------------------
623 */
624 /*ARGSUSED*/
625 static char *
LimitToString(clientData,tkwin,widgRec,offset,freeProcPtr)626 LimitToString(clientData, tkwin, widgRec, offset, freeProcPtr)
627 ClientData clientData; /* Either LMIN or LMAX */
628 Tk_Window tkwin; /* Not used. */
629 char *widgRec; /* */
630 int offset;
631 Tcl_FreeProc **freeProcPtr;
632 {
633 double limit = *(double *)(widgRec + offset);
634 char *result;
635
636 result = "";
637 if (DEFINED(limit)) {
638 char string[TCL_DOUBLE_SPACE + 1];
639 Graph *graphPtr;
640
641 graphPtr = Blt_GetGraphFromWindowData(tkwin);
642 Tcl_PrintDouble(graphPtr->interp, limit, string);
643 result = Blt_Strdup(string);
644 if (result == NULL) {
645 return "";
646 }
647 *freeProcPtr = (Tcl_FreeProc *)Blt_Free;
648 }
649 return result;
650 }
651
652 /*
653 * ----------------------------------------------------------------------
654 *
655 * StringToTicks --
656 *
657 *
658 * Results:
659 *
660 * ----------------------------------------------------------------------
661 */
662 /*ARGSUSED*/
663 static int
StringToTicks(clientData,interp,tkwin,string,widgRec,offset)664 StringToTicks(clientData, interp, tkwin, string, widgRec, offset)
665 ClientData clientData; /* Not used. */
666 Tcl_Interp *interp; /* Interpreter to send results back to */
667 Tk_Window tkwin; /* Not used. */
668 char *string; /* String representing new value. */
669 char *widgRec; /* Pointer to structure record. */
670 int offset; /* Offset of field in structure. */
671 {
672 unsigned int mask = (unsigned int)clientData;
673 Axis *axisPtr = (Axis *)widgRec;
674 Ticks **ticksPtrPtr = (Ticks **) (widgRec + offset);
675 int nTicks;
676 Ticks *ticksPtr;
677
678 nTicks = 0;
679 ticksPtr = NULL;
680 if ((string != NULL) && (*string != '\0')) {
681 int nExprs;
682 char **exprArr;
683
684 if (Tcl_SplitList(interp, string, &nExprs, &exprArr) != TCL_OK) {
685 return TCL_ERROR;
686 }
687 if (nExprs > 0) {
688 register int i;
689 int result = TCL_ERROR;
690 double value;
691
692 ticksPtr = Blt_Malloc(sizeof(Ticks) + (nExprs * sizeof(double)));
693 assert(ticksPtr);
694 for (i = 0; i < nExprs; i++) {
695 result = Tcl_ExprDouble(interp, exprArr[i], &value);
696 if (result != TCL_OK) {
697 break;
698 }
699 ticksPtr->values[i] = value;
700 }
701 Blt_Free(exprArr);
702 if (result != TCL_OK) {
703 Blt_Free(ticksPtr);
704 return TCL_ERROR;
705 }
706 nTicks = nExprs;
707 }
708 }
709 axisPtr->flags &= ~mask;
710 if (ticksPtr != NULL) {
711 axisPtr->flags |= mask;
712 ticksPtr->nTicks = nTicks;
713 }
714 if (*ticksPtrPtr != NULL) {
715 Blt_Free(*ticksPtrPtr);
716 }
717 *ticksPtrPtr = ticksPtr;
718 return TCL_OK;
719 }
720
721 /*
722 * ----------------------------------------------------------------------
723 *
724 * TicksToString --
725 *
726 * Convert array of tick coordinates to a list.
727 *
728 * Results:
729 *
730 * ----------------------------------------------------------------------
731 */
732 /*ARGSUSED*/
733 static char *
TicksToString(clientData,tkwin,widgRec,offset,freeProcPtr)734 TicksToString(clientData, tkwin, widgRec, offset, freeProcPtr)
735 ClientData clientData; /* Not used. */
736 Tk_Window tkwin; /* Not used. */
737 char *widgRec; /* */
738 int offset;
739 Tcl_FreeProc **freeProcPtr;
740 {
741 Ticks *ticksPtr = *(Ticks **) (widgRec + offset);
742 char string[TCL_DOUBLE_SPACE + 1];
743 register int i;
744 char *result;
745 Tcl_DString dString;
746 Graph *graphPtr;
747
748 if (ticksPtr == NULL) {
749 return "";
750 }
751 Tcl_DStringInit(&dString);
752 graphPtr = Blt_GetGraphFromWindowData(tkwin);
753 for (i = 0; i < ticksPtr->nTicks; i++) {
754 Tcl_PrintDouble(graphPtr->interp, ticksPtr->values[i], string);
755 Tcl_DStringAppendElement(&dString, string);
756 }
757 *freeProcPtr = (Tcl_FreeProc *)Blt_Free;
758 result = Blt_Strdup(Tcl_DStringValue(&dString));
759 Tcl_DStringFree(&dString);
760 return result;
761 }
762
763 /*
764 *----------------------------------------------------------------------
765 *
766 * StringToLoose --
767 *
768 * Convert a string to one of three values.
769 * 0 - false, no, off
770 * 1 - true, yes, on
771 * 2 - always
772 * Results:
773 * If the string is successfully converted, TCL_OK is returned.
774 * Otherwise, TCL_ERROR is returned and an error message is left in
775 * interpreter's result field.
776 *
777 *----------------------------------------------------------------------
778 */
779 /*ARGSUSED*/
780 static int
StringToLoose(clientData,interp,tkwin,string,widgRec,offset)781 StringToLoose(clientData, interp, tkwin, string, widgRec, offset)
782 ClientData clientData; /* Not used. */
783 Tcl_Interp *interp; /* Interpreter to send results back to */
784 Tk_Window tkwin; /* Not used. */
785 char *string; /* String representing new value. */
786 char *widgRec; /* Pointer to structure record. */
787 int offset; /* Offset of field in structure. */
788 {
789 Axis *axisPtr = (Axis *)(widgRec);
790 register int i;
791 int argc;
792 char **argv;
793 int values[2];
794
795 if (Tcl_SplitList(interp, string, &argc, &argv) != TCL_OK) {
796 return TCL_ERROR;
797 }
798 if ((argc < 1) || (argc > 2)) {
799 Tcl_AppendResult(interp, "wrong # elements in loose value \"",
800 string, "\"", (char *)NULL);
801 return TCL_ERROR;
802 }
803 for (i = 0; i < argc; i++) {
804 if ((argv[i][0] == 'a') && (strcmp(argv[i], "always") == 0)) {
805 values[i] = TICK_RANGE_ALWAYS_LOOSE;
806 } else {
807 int bool;
808
809 if (Tcl_GetBoolean(interp, argv[i], &bool) != TCL_OK) {
810 Blt_Free(argv);
811 return TCL_ERROR;
812 }
813 values[i] = bool;
814 }
815 }
816 axisPtr->looseMin = axisPtr->looseMax = values[0];
817 if (argc > 1) {
818 axisPtr->looseMax = values[1];
819 }
820 Blt_Free(argv);
821 return TCL_OK;
822 }
823
824 /*
825 *----------------------------------------------------------------------
826 *
827 * LooseToString --
828 *
829 * Results:
830 * The string representation of the auto boolean is returned.
831 *
832 *----------------------------------------------------------------------
833 */
834 /*ARGSUSED*/
835 static char *
LooseToString(clientData,tkwin,widgRec,offset,freeProcPtr)836 LooseToString(clientData, tkwin, widgRec, offset, freeProcPtr)
837 ClientData clientData; /* Not used. */
838 Tk_Window tkwin; /* Not used. */
839 char *widgRec; /* Widget record */
840 int offset; /* offset of flags field in record */
841 Tcl_FreeProc **freeProcPtr; /* Memory deallocation scheme to use */
842 {
843 Axis *axisPtr = (Axis *)widgRec;
844 Tcl_DString dString;
845 char *result;
846
847 Tcl_DStringInit(&dString);
848 if (axisPtr->looseMin == TICK_RANGE_TIGHT) {
849 Tcl_DStringAppendElement(&dString, "0");
850 } else if (axisPtr->looseMin == TICK_RANGE_LOOSE) {
851 Tcl_DStringAppendElement(&dString, "1");
852 } else if (axisPtr->looseMin == TICK_RANGE_ALWAYS_LOOSE) {
853 Tcl_DStringAppendElement(&dString, "always");
854 }
855 if (axisPtr->looseMin != axisPtr->looseMax) {
856 if (axisPtr->looseMax == TICK_RANGE_TIGHT) {
857 Tcl_DStringAppendElement(&dString, "0");
858 } else if (axisPtr->looseMax == TICK_RANGE_LOOSE) {
859 Tcl_DStringAppendElement(&dString, "1");
860 } else if (axisPtr->looseMax == TICK_RANGE_ALWAYS_LOOSE) {
861 Tcl_DStringAppendElement(&dString, "always");
862 }
863 }
864 result = Blt_Strdup(Tcl_DStringValue(&dString));
865 Tcl_DStringFree(&dString);
866 *freeProcPtr = (Tcl_FreeProc *)Blt_Free;
867 return result;
868 }
869
870 static void
FreeLabels(chainPtr)871 FreeLabels(chainPtr)
872 Blt_Chain *chainPtr;
873 {
874 Blt_ChainLink *linkPtr;
875 TickLabel *labelPtr;
876
877 for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr != NULL;
878 linkPtr = Blt_ChainNextLink(linkPtr)) {
879 labelPtr = Blt_ChainGetValue(linkPtr);
880 Blt_Free(labelPtr);
881 }
882 Blt_ChainReset(chainPtr);
883 }
884
885 /*
886 * ----------------------------------------------------------------------
887 *
888 * MakeLabel --
889 *
890 * Converts a floating point tick value to a string to be used as its
891 * label.
892 *
893 * Results:
894 * None.
895 *
896 * Side Effects:
897 * Returns a new label in the string character buffer. The formatted
898 * tick label will be displayed on the graph.
899 *
900 * ----------------------------------------------------------------------
901 */
902 static TickLabel *
MakeLabel(graphPtr,axisPtr,value)903 MakeLabel(graphPtr, axisPtr, value)
904 Graph *graphPtr;
905 Axis *axisPtr; /* Axis structure */
906 double value; /* Value to be convert to a decimal string */
907 {
908 char string[TICK_LABEL_SIZE + 1];
909 TickLabel *labelPtr;
910
911 /* Generate a default tick label based upon the tick value. */
912 if (axisPtr->logScale) {
913 sprintf(string, "1E%d", ROUND(value));
914 } else {
915 sprintf(string, "%.*g", NUMDIGITS, value);
916 }
917
918 if (axisPtr->formatCmd != NULL) {
919 Tcl_Interp *interp = graphPtr->interp;
920 Tk_Window tkwin = graphPtr->tkwin;
921
922 /*
923 * A Tcl proc was designated to format tick labels. Append the path
924 * name of the widget and the default tick label as arguments when
925 * invoking it. Copy and save the new label from interp->result.
926 */
927 Tcl_ResetResult(interp);
928 if (Tcl_VarEval(interp, axisPtr->formatCmd, " ", Tk_PathName(tkwin),
929 " ", string, (char *)NULL) != TCL_OK) {
930 Tcl_BackgroundError(interp);
931 } else {
932 /*
933 * The proc could return a string of any length, so arbitrarily
934 * limit it to what will fit in the return string.
935 */
936 strncpy(string, Tcl_GetStringResult(interp), TICK_LABEL_SIZE);
937 string[TICK_LABEL_SIZE] = '\0';
938
939 Tcl_ResetResult(interp); /* Clear the interpreter's result. */
940 }
941 }
942 labelPtr = Blt_Malloc(sizeof(TickLabel) + strlen(string));
943 assert(labelPtr);
944 strcpy(labelPtr->string, string);
945 labelPtr->anchorPos.x = labelPtr->anchorPos.y = DBL_MAX;
946 return labelPtr;
947 }
948
949 /*
950 * ----------------------------------------------------------------------
951 *
952 * Blt_InvHMap --
953 *
954 * Maps the given screen coordinate back to a graph coordinate.
955 * Called by the graph locater routine.
956 *
957 * Results:
958 * Returns the graph coordinate value at the given window
959 * y-coordinate.
960 *
961 * ----------------------------------------------------------------------
962 */
963 double
Blt_InvHMap(graphPtr,axisPtr,x)964 Blt_InvHMap(graphPtr, axisPtr, x)
965 Graph *graphPtr;
966 Axis *axisPtr;
967 double x;
968 {
969 double value;
970
971 x = (double)(x - graphPtr->hOffset) * graphPtr->hScale;
972 if (axisPtr->descending) {
973 x = 1.0 - x;
974 }
975 value = (x * axisPtr->axisRange.range) + axisPtr->axisRange.min;
976 if (axisPtr->logScale) {
977 value = EXP10(value);
978 }
979 return value;
980 }
981
982 /*
983 * ----------------------------------------------------------------------
984 *
985 * Blt_InvVMap --
986 *
987 * Maps the given window y-coordinate back to a graph coordinate
988 * value. Called by the graph locater routine.
989 *
990 * Results:
991 * Returns the graph coordinate value at the given window
992 * y-coordinate.
993 *
994 * ----------------------------------------------------------------------
995 */
996 double
Blt_InvVMap(graphPtr,axisPtr,y)997 Blt_InvVMap(graphPtr, axisPtr, y)
998 Graph *graphPtr;
999 Axis *axisPtr;
1000 double y;
1001 {
1002 double value;
1003
1004 y = (double)(y - graphPtr->vOffset) * graphPtr->vScale;
1005 if (axisPtr->descending) {
1006 y = 1.0 - y;
1007 }
1008 value = ((1.0 - y) * axisPtr->axisRange.range) + axisPtr->axisRange.min;
1009 if (axisPtr->logScale) {
1010 value = EXP10(value);
1011 }
1012 return value;
1013 }
1014
1015 /*
1016 * ----------------------------------------------------------------------
1017 *
1018 * Blt_HMap --
1019 *
1020 * Map the given graph coordinate value to its axis, returning a window
1021 * position.
1022 *
1023 * Results:
1024 * Returns a double precision number representing the window coordinate
1025 * position on the given axis.
1026 *
1027 * ----------------------------------------------------------------------
1028 */
1029 double
Blt_HMap(graphPtr,axisPtr,x)1030 Blt_HMap(graphPtr, axisPtr, x)
1031 Graph *graphPtr;
1032 Axis *axisPtr;
1033 double x;
1034 {
1035 if ((axisPtr->logScale) && (x != 0.0)) {
1036 x = log10(FABS(x));
1037 }
1038 /* Map graph coordinate to normalized coordinates [0..1] */
1039 x = (x - axisPtr->axisRange.min) * axisPtr->axisRange.scale;
1040 if (axisPtr->descending) {
1041 x = 1.0 - x;
1042 }
1043 return (x * graphPtr->hRange + graphPtr->hOffset);
1044 }
1045
1046 /*
1047 * ----------------------------------------------------------------------
1048 *
1049 * Blt_VMap --
1050 *
1051 * Map the given graph coordinate value to its axis, returning a window
1052 * position.
1053 *
1054 * Results:
1055 * Returns a double precision number representing the window coordinate
1056 * position on the given axis.
1057 *
1058 * ----------------------------------------------------------------------
1059 */
1060 double
Blt_VMap(graphPtr,axisPtr,y)1061 Blt_VMap(graphPtr, axisPtr, y)
1062 Graph *graphPtr;
1063 Axis *axisPtr;
1064 double y;
1065 {
1066 if ((axisPtr->logScale) && (y != 0.0)) {
1067 y = log10(FABS(y));
1068 }
1069 /* Map graph coordinate to normalized coordinates [0..1] */
1070 y = (y - axisPtr->axisRange.min) * axisPtr->axisRange.scale;
1071 if (axisPtr->descending) {
1072 y = 1.0 - y;
1073 }
1074 return (((1.0 - y) * graphPtr->vRange) + graphPtr->vOffset);
1075 }
1076
1077 /*
1078 * ----------------------------------------------------------------------
1079 *
1080 * Blt_Map2D --
1081 *
1082 * Maps the given graph x,y coordinate values to a window position.
1083 *
1084 * Results:
1085 * Returns a XPoint structure containing the window coordinates of
1086 * the given graph x,y coordinate.
1087 *
1088 * ----------------------------------------------------------------------
1089 */
1090 Point2D
Blt_Map2D(graphPtr,x,y,axesPtr)1091 Blt_Map2D(graphPtr, x, y, axesPtr)
1092 Graph *graphPtr;
1093 double x, y; /* Graph x and y coordinates */
1094 Axis2D *axesPtr; /* Specifies which axes to use */
1095 {
1096 Point2D point;
1097
1098 if (graphPtr->inverted) {
1099 point.x = Blt_HMap(graphPtr, axesPtr->y, y);
1100 point.y = Blt_VMap(graphPtr, axesPtr->x, x);
1101 } else {
1102 point.x = Blt_HMap(graphPtr, axesPtr->x, x);
1103 point.y = Blt_VMap(graphPtr, axesPtr->y, y);
1104 }
1105 return point;
1106 }
1107
1108 /*
1109 * ----------------------------------------------------------------------
1110 *
1111 * Blt_InvMap2D --
1112 *
1113 * Maps the given window x,y coordinates to graph values.
1114 *
1115 * Results:
1116 * Returns a structure containing the graph coordinates of
1117 * the given window x,y coordinate.
1118 *
1119 * ----------------------------------------------------------------------
1120 */
1121 Point2D
Blt_InvMap2D(graphPtr,x,y,axesPtr)1122 Blt_InvMap2D(graphPtr, x, y, axesPtr)
1123 Graph *graphPtr;
1124 double x, y; /* Window x and y coordinates */
1125 Axis2D *axesPtr; /* Specifies which axes to use */
1126 {
1127 Point2D point;
1128
1129 if (graphPtr->inverted) {
1130 point.x = Blt_InvVMap(graphPtr, axesPtr->x, y);
1131 point.y = Blt_InvHMap(graphPtr, axesPtr->y, x);
1132 } else {
1133 point.x = Blt_InvHMap(graphPtr, axesPtr->x, x);
1134 point.y = Blt_InvVMap(graphPtr, axesPtr->y, y);
1135 }
1136 return point;
1137 }
1138
1139
1140 static void
GetDataLimits(axisPtr,min,max)1141 GetDataLimits(axisPtr, min, max)
1142 Axis *axisPtr;
1143 double min, max;
1144 {
1145 if (axisPtr->valueRange.min > min) {
1146 axisPtr->valueRange.min = min;
1147 }
1148 if (axisPtr->valueRange.max < max) {
1149 axisPtr->valueRange.max = max;
1150 }
1151 }
1152
1153 static void
FixAxisRange(axisPtr)1154 FixAxisRange(axisPtr)
1155 Axis *axisPtr;
1156 {
1157 double min, max;
1158 /*
1159 * When auto-scaling, the axis limits are the bounds of the element
1160 * data. If no data exists, set arbitrary limits (wrt to log/linear
1161 * scale).
1162 */
1163 min = axisPtr->valueRange.min;
1164 max = axisPtr->valueRange.max;
1165
1166 if (min == DBL_MAX) {
1167 if (DEFINED(axisPtr->reqMin)) {
1168 min = axisPtr->reqMin;
1169 } else {
1170 min = (axisPtr->logScale) ? 0.001 : 0.0;
1171 }
1172 }
1173 if (max == -DBL_MAX) {
1174 if (DEFINED(axisPtr->reqMax)) {
1175 max = axisPtr->reqMax;
1176 } else {
1177 max = 1.0;
1178 }
1179 }
1180 if (min >= max) {
1181 double value;
1182
1183 /*
1184 * There is no range of data (i.e. min is not less than max),
1185 * so manufacture one.
1186 */
1187 value = min;
1188 if (value == 0.0) {
1189 min = -0.1, max = 0.1;
1190 } else {
1191 double x;
1192
1193 x = FABS(value) * 0.1;
1194 min = value - x, max = value + x;
1195 }
1196 }
1197 SetAxisRange(&axisPtr->valueRange, min, max);
1198
1199 /*
1200 * The axis limits are either the current data range or overridden
1201 * by the values selected by the user with the -min or -max
1202 * options.
1203 */
1204 axisPtr->min = min;
1205 axisPtr->max = max;
1206 if (DEFINED(axisPtr->reqMin)) {
1207 axisPtr->min = axisPtr->reqMin;
1208 }
1209 if (DEFINED(axisPtr->reqMax)) {
1210 axisPtr->max = axisPtr->reqMax;
1211 }
1212
1213 if (axisPtr->max < axisPtr->min) {
1214
1215 /*
1216 * If the limits still don't make sense, it's because one
1217 * limit configuration option (-min or -max) was set and the
1218 * other default (based upon the data) is too small or large.
1219 * Remedy this by making up a new min or max from the
1220 * user-defined limit.
1221 */
1222
1223 if (!DEFINED(axisPtr->reqMin)) {
1224 axisPtr->min = axisPtr->max - (FABS(axisPtr->max) * 0.1);
1225 }
1226 if (!DEFINED(axisPtr->reqMax)) {
1227 axisPtr->max = axisPtr->min + (FABS(axisPtr->max) * 0.1);
1228 }
1229 }
1230 /*
1231 * If a window size is defined, handle auto ranging by shifting
1232 * the axis limits.
1233 */
1234 if ((axisPtr->windowSize > 0.0) &&
1235 (!DEFINED(axisPtr->reqMin)) && (!DEFINED(axisPtr->reqMax))) {
1236 if (axisPtr->shiftBy < 0.0) {
1237 axisPtr->shiftBy = 0.0;
1238 }
1239 max = axisPtr->min + axisPtr->windowSize;
1240 if (axisPtr->max >= max) {
1241 if (axisPtr->shiftBy > 0.0) {
1242 max = UCEIL(axisPtr->max, axisPtr->shiftBy);
1243 }
1244 axisPtr->min = max - axisPtr->windowSize;
1245 }
1246 axisPtr->max = max;
1247 }
1248 if ((axisPtr->max != axisPtr->prevMax) ||
1249 (axisPtr->min != axisPtr->prevMin)) {
1250 /* Indicate if the axis limits have changed */
1251 axisPtr->flags |= AXIS_DIRTY;
1252 /* and save the previous minimum and maximum values */
1253 axisPtr->prevMin = axisPtr->min;
1254 axisPtr->prevMax = axisPtr->max;
1255 }
1256 }
1257
1258 /*
1259 * ----------------------------------------------------------------------
1260 *
1261 * NiceNum --
1262 *
1263 * Reference: Paul Heckbert, "Nice Numbers for Graph Labels",
1264 * Graphics Gems, pp 61-63.
1265 *
1266 * Finds a "nice" number approximately equal to x.
1267 *
1268 * ----------------------------------------------------------------------
1269 */
1270 static double
NiceNum(x,doround)1271 NiceNum(x, doround)
1272 double x;
1273 int doround; /* If non-zero, round. Otherwise take ceiling
1274 * of value. */
1275 {
1276 double expt; /* Exponent of x */
1277 double frac; /* Fractional part of x */
1278 double fnice; /* Nice, rounded fraction */
1279
1280 expt = floor(log10(x));
1281 frac = x / EXP10(expt); /* between 1 and 10 */
1282 if (doround) {
1283 if (frac < 1.5) {
1284 fnice = 1.0;
1285 } else if (frac < 3.0) {
1286 fnice = 2.0;
1287 } else if (frac < 7.0) {
1288 fnice = 5.0;
1289 } else {
1290 fnice = 10.0;
1291 }
1292 } else {
1293 if (frac <= 1.0) {
1294 fnice = 1.0;
1295 } else if (frac <= 2.0) {
1296 fnice = 2.0;
1297 } else if (frac <= 5.0) {
1298 fnice = 5.0;
1299 } else {
1300 fnice = 10.0;
1301 }
1302 }
1303 return fnice * EXP10(expt);
1304 }
1305
1306 static Ticks *
GenerateTicks(sweepPtr)1307 GenerateTicks(sweepPtr)
1308 TickSweep *sweepPtr;
1309 {
1310 Ticks *ticksPtr;
1311 register int i;
1312
1313 ticksPtr = Blt_Malloc(sizeof(Ticks) + (sweepPtr->nSteps * sizeof(double)));
1314 assert(ticksPtr);
1315
1316 if (sweepPtr->step == 0.0) {
1317 static double logTable[] = /* Precomputed log10 values [1..10] */
1318 {
1319 0.0,
1320 0.301029995663981, 0.477121254719662,
1321 0.602059991327962, 0.698970004336019,
1322 0.778151250383644, 0.845098040014257,
1323 0.903089986991944, 0.954242509439325,
1324 1.0
1325 };
1326 /* Hack: A zero step indicates to use log values. */
1327 for (i = 0; i < sweepPtr->nSteps; i++) {
1328 ticksPtr->values[i] = logTable[i];
1329 }
1330 } else {
1331 double value;
1332
1333 value = sweepPtr->initial; /* Start from smallest axis tick */
1334 for (i = 0; i < sweepPtr->nSteps; i++) {
1335 value = UROUND(value, sweepPtr->step);
1336 ticksPtr->values[i] = value;
1337 value += sweepPtr->step;
1338 }
1339 }
1340 ticksPtr->nTicks = sweepPtr->nSteps;
1341 return ticksPtr;
1342 }
1343
1344 /*
1345 * ----------------------------------------------------------------------
1346 *
1347 * LogScaleAxis --
1348 *
1349 * Determine the range and units of a log scaled axis.
1350 *
1351 * Unless the axis limits are specified, the axis is scaled
1352 * automatically, where the smallest and largest major ticks encompass
1353 * the range of actual data values. When an axis limit is specified,
1354 * that value represents the smallest(min)/largest(max) value in the
1355 * displayed range of values.
1356 *
1357 * Both manual and automatic scaling are affected by the step used. By
1358 * default, the step is the largest power of ten to divide the range in
1359 * more than one piece.
1360 *
1361 * Automatic scaling:
1362 * Find the smallest number of units which contain the range of values.
1363 * The minimum and maximum major tick values will be represent the
1364 * range of values for the axis. This greatest number of major ticks
1365 * possible is 10.
1366 *
1367 * Manual scaling:
1368 * Make the minimum and maximum data values the represent the range of
1369 * the values for the axis. The minimum and maximum major ticks will be
1370 * inclusive of this range. This provides the largest area for plotting
1371 * and the expected results when the axis min and max values have be set
1372 * by the user (.e.g zooming). The maximum number of major ticks is 20.
1373 *
1374 * For log scale, there's the possibility that the minimum and
1375 * maximum data values are the same magnitude. To represent the
1376 * points properly, at least one full decade should be shown.
1377 * However, if you zoom a log scale plot, the results should be
1378 * predictable. Therefore, in that case, show only minor ticks.
1379 * Lastly, there should be an appropriate way to handle numbers
1380 * <=0.
1381 *
1382 * maxY
1383 * | units = magnitude (of least significant digit)
1384 * | high = largest unit tick < max axis value
1385 * high _| low = smallest unit tick > min axis value
1386 * |
1387 * | range = high - low
1388 * | # ticks = greatest factor of range/units
1389 * _|
1390 * U |
1391 * n |
1392 * i |
1393 * t _|
1394 * |
1395 * |
1396 * |
1397 * low _|
1398 * |
1399 * |_minX________________maxX__
1400 * | | | | |
1401 * minY low high
1402 * minY
1403 *
1404 *
1405 * numTicks = Number of ticks
1406 * min = Minimum value of axis
1407 * max = Maximum value of axis
1408 * range = Range of values (max - min)
1409 *
1410 * If the number of decades is greater than ten, it is assumed
1411 * that the full set of log-style ticks can't be drawn properly.
1412 *
1413 * Results:
1414 * None
1415 *
1416 * ---------------------------------------------------------------------- */
1417 static void
LogScaleAxis(axisPtr,min,max)1418 LogScaleAxis(axisPtr, min, max)
1419 Axis *axisPtr;
1420 double min, max;
1421 {
1422 double range;
1423 double tickMin, tickMax;
1424 double majorStep, minorStep;
1425 int nMajor, nMinor;
1426
1427 nMajor = nMinor = 0;
1428 majorStep = minorStep = 0.0;
1429 if (min < max) {
1430 min = (min != 0.0) ? log10(FABS(min)) : 0.0;
1431 max = (max != 0.0) ? log10(FABS(max)) : 1.0;
1432
1433 tickMin = floor(min);
1434 tickMax = ceil(max);
1435 range = tickMax - tickMin;
1436
1437 if (range > 10) {
1438 /* There are too many decades to display a major tick at every
1439 * decade. Instead, treat the axis as a linear scale. */
1440 range = NiceNum(range, 0);
1441 majorStep = NiceNum(range / DEF_NUM_TICKS, 1);
1442 tickMin = UFLOOR(tickMin, majorStep);
1443 tickMax = UCEIL(tickMax, majorStep);
1444 nMajor = (int)((tickMax - tickMin) / majorStep) + 1;
1445 minorStep = EXP10(floor(log10(majorStep)));
1446 if (minorStep == majorStep) {
1447 nMinor = 4, minorStep = 0.2;
1448 } else {
1449 nMinor = Round(majorStep / minorStep) - 1;
1450 }
1451 } else {
1452 if (tickMin == tickMax) {
1453 tickMax++;
1454 }
1455 majorStep = 1.0;
1456 nMajor = (int)(tickMax - tickMin + 1); /* FIXME: Check this. */
1457
1458 minorStep = 0.0; /* This is a special hack to pass
1459 * information to the GenerateTicks
1460 * routine. An interval of 0.0 tells
1461 * 1) this is a minor sweep and
1462 * 2) the axis is log scale.
1463 */
1464 nMinor = 10;
1465 }
1466 if ((axisPtr->looseMin == TICK_RANGE_TIGHT) ||
1467 ((axisPtr->looseMin == TICK_RANGE_LOOSE) &&
1468 (DEFINED(axisPtr->reqMin)))) {
1469 tickMin = min;
1470 nMajor++;
1471 }
1472 if ((axisPtr->looseMax == TICK_RANGE_TIGHT) ||
1473 ((axisPtr->looseMax == TICK_RANGE_LOOSE) &&
1474 (DEFINED(axisPtr->reqMax)))) {
1475 tickMax = max;
1476 }
1477 }
1478 axisPtr->majorSweep.step = majorStep;
1479 axisPtr->majorSweep.initial = floor(tickMin);
1480 axisPtr->majorSweep.nSteps = nMajor;
1481 axisPtr->minorSweep.initial = axisPtr->minorSweep.step = minorStep;
1482 axisPtr->minorSweep.nSteps = nMinor;
1483 SetAxisRange(&axisPtr->axisRange, tickMin, tickMax);
1484 }
1485
1486 /*
1487 * ----------------------------------------------------------------------
1488 *
1489 * LinearScaleAxis --
1490 *
1491 * Determine the units of a linear scaled axis.
1492 *
1493 * The axis limits are either the range of the data values mapped
1494 * to the axis (autoscaled), or the values specified by the -min
1495 * and -max options (manual).
1496 *
1497 * If autoscaled, the smallest and largest major ticks will
1498 * encompass the range of data values. If the -loose option is
1499 * selected, the next outer ticks are choosen. If tight, the
1500 * ticks are at or inside of the data limits are used.
1501 *
1502 * If manually set, the ticks are at or inside the data limits
1503 * are used. This makes sense for zooming. You want the
1504 * selected range to represent the next limit, not something a
1505 * bit bigger.
1506 *
1507 * Note: I added an "always" value to the -loose option to force
1508 * the manually selected axes to be loose. It's probably
1509 * not a good idea.
1510 *
1511 * maxY
1512 * | units = magnitude (of least significant digit)
1513 * | high = largest unit tick < max axis value
1514 * high _| low = smallest unit tick > min axis value
1515 * |
1516 * | range = high - low
1517 * | # ticks = greatest factor of range/units
1518 * _|
1519 * U |
1520 * n |
1521 * i |
1522 * t _|
1523 * |
1524 * |
1525 * |
1526 * low _|
1527 * |
1528 * |_minX________________maxX__
1529 * | | | | |
1530 * minY low high
1531 * minY
1532 *
1533 * numTicks = Number of ticks
1534 * min = Minimum value of axis
1535 * max = Maximum value of axis
1536 * range = Range of values (max - min)
1537 *
1538 * Results:
1539 * None.
1540 *
1541 * Side Effects:
1542 * The axis tick information is set. The actual tick values will
1543 * be generated later.
1544 *
1545 * ----------------------------------------------------------------------
1546 */
1547 static void
LinearScaleAxis(axisPtr,min,max)1548 LinearScaleAxis(axisPtr, min, max)
1549 Axis *axisPtr;
1550 double min, max;
1551 {
1552 double range, step;
1553 double tickMin, tickMax;
1554 double axisMin, axisMax;
1555 int nTicks;
1556
1557 nTicks = 0;
1558 tickMin = tickMax = 0.0;
1559 if (min < max) {
1560 range = max - min;
1561
1562 /* Calculate the major tick stepping. */
1563 if (axisPtr->reqStep > 0.0) {
1564 /* An interval was designated by the user. Keep scaling it
1565 * until it fits comfortably within the current range of the
1566 * axis. */
1567 step = axisPtr->reqStep;
1568 while ((2 * step) >= range) {
1569 step *= 0.5;
1570 }
1571 } else {
1572 range = NiceNum(range, 0);
1573 step = NiceNum(range / DEF_NUM_TICKS, 1);
1574 }
1575
1576 /* Find the outer tick values. Add 0.0 to prevent getting -0.0. */
1577 axisMin = tickMin = floor(min / step) * step + 0.0;
1578 axisMax = tickMax = ceil(max / step) * step + 0.0;
1579
1580 nTicks = Round((tickMax - tickMin) / step) + 1;
1581 }
1582 axisPtr->majorSweep.step = step;
1583 axisPtr->majorSweep.initial = tickMin;
1584 axisPtr->majorSweep.nSteps = nTicks;
1585
1586 /*
1587 * The limits of the axis are either the range of the data
1588 * ("tight") or at the next outer tick interval ("loose"). The
1589 * looseness or tightness has to do with how the axis fits the
1590 * range of data values. This option is overridden when
1591 * the user sets an axis limit (by either -min or -max option).
1592 * The axis limit is always at the selected limit (otherwise we
1593 * assume that user would have picked a different number).
1594 */
1595 if ((axisPtr->looseMin == TICK_RANGE_TIGHT) ||
1596 ((axisPtr->looseMin == TICK_RANGE_LOOSE) &&
1597 (DEFINED(axisPtr->reqMin)))) {
1598 axisMin = min;
1599 }
1600 if ((axisPtr->looseMax == TICK_RANGE_TIGHT) ||
1601 ((axisPtr->looseMax == TICK_RANGE_LOOSE) &&
1602 (DEFINED(axisPtr->reqMax)))) {
1603 axisMax = max;
1604 }
1605 SetAxisRange(&axisPtr->axisRange, axisMin, axisMax);
1606
1607 /* Now calculate the minor tick step and number. */
1608
1609 if ((axisPtr->reqNumMinorTicks > 0) &&
1610 ((axisPtr->flags & AXIS_CONFIG_MAJOR) == 0)) {
1611 nTicks = axisPtr->reqNumMinorTicks - 1;
1612 step = 1.0 / (nTicks + 1);
1613 } else {
1614 nTicks = 0; /* No minor ticks. */
1615 step = 0.5; /* Don't set the minor tick interval
1616 * to 0.0. It makes the GenerateTicks
1617 * routine create minor log-scale tick
1618 * marks. */
1619 }
1620 axisPtr->minorSweep.initial = axisPtr->minorSweep.step = step;
1621 axisPtr->minorSweep.nSteps = nTicks;
1622 }
1623
1624 static void
SweepTicks(axisPtr)1625 SweepTicks(axisPtr)
1626 Axis *axisPtr;
1627 {
1628 if ((axisPtr->flags & AXIS_CONFIG_MAJOR) == 0) {
1629 if (axisPtr->t1Ptr != NULL) {
1630 Blt_Free(axisPtr->t1Ptr);
1631 }
1632 axisPtr->t1Ptr = GenerateTicks(&axisPtr->majorSweep);
1633 }
1634 if ((axisPtr->flags & AXIS_CONFIG_MINOR) == 0) {
1635 if (axisPtr->t2Ptr != NULL) {
1636 Blt_Free(axisPtr->t2Ptr);
1637 }
1638 axisPtr->t2Ptr = GenerateTicks(&axisPtr->minorSweep);
1639 }
1640 }
1641
1642 /*
1643 * ----------------------------------------------------------------------
1644 *
1645 * Blt_ResetAxes --
1646 *
1647 * Results:
1648 * None.
1649 *
1650 * ----------------------------------------------------------------------
1651 */
1652 void
Blt_ResetAxes(graphPtr)1653 Blt_ResetAxes(graphPtr)
1654 Graph *graphPtr;
1655 {
1656 Blt_ChainLink *linkPtr;
1657 Element *elemPtr;
1658 Axis *axisPtr;
1659 Blt_HashEntry *hPtr;
1660 Blt_HashSearch cursor;
1661 Extents2D exts;
1662 double min, max;
1663
1664 /* FIXME: This should be called whenever the display list of
1665 * elements change. Maybe yet another flag INIT_STACKS to
1666 * indicate that the element display list has changed.
1667 * Needs to be done before the axis limits are set.
1668 */
1669 Blt_InitFreqTable(graphPtr);
1670 if ((graphPtr->mode == MODE_STACKED) && (graphPtr->nStacks > 0)) {
1671 Blt_ComputeStacks(graphPtr);
1672 }
1673 /*
1674 * Step 1: Reset all axes. Initialize the data limits of the axis to
1675 * impossible values.
1676 */
1677 for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor);
1678 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
1679 axisPtr = (Axis *)Blt_GetHashValue(hPtr);
1680 axisPtr->min = axisPtr->valueRange.min = DBL_MAX;
1681 axisPtr->max = axisPtr->valueRange.max = -DBL_MAX;
1682 }
1683
1684 /*
1685 * Step 2: For each element that's to be displayed, get the smallest
1686 * and largest data values mapped to each X and Y-axis. This
1687 * will be the axis limits if the user doesn't override them
1688 * with -min and -max options.
1689 */
1690 for (linkPtr = Blt_ChainFirstLink(graphPtr->elements.displayList);
1691 linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
1692 elemPtr = Blt_ChainGetValue(linkPtr);
1693 if (!elemPtr->hidden) {
1694 (*elemPtr->procsPtr->extentsProc) (elemPtr, &exts);
1695 GetDataLimits(elemPtr->axes.x, exts.left, exts.right);
1696 GetDataLimits(elemPtr->axes.y, exts.top, exts.bottom);
1697 }
1698 }
1699 /*
1700 * Step 3: Now that we know the range of data values for each axis,
1701 * set axis limits and compute a sweep to generate tick values.
1702 */
1703 for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor);
1704 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
1705 axisPtr = (Axis *)Blt_GetHashValue(hPtr);
1706 FixAxisRange(axisPtr);
1707
1708 /* Calculate min/max tick (major/minor) layouts */
1709 min = axisPtr->min;
1710 max = axisPtr->max;
1711 if ((DEFINED(axisPtr->scrollMin)) && (min < axisPtr->scrollMin)) {
1712 min = axisPtr->scrollMin;
1713 }
1714 if ((DEFINED(axisPtr->scrollMax)) && (max > axisPtr->scrollMax)) {
1715 max = axisPtr->scrollMax;
1716 }
1717 if (axisPtr->logScale) {
1718 LogScaleAxis(axisPtr, min, max);
1719 } else {
1720 LinearScaleAxis(axisPtr, min, max);
1721 }
1722
1723 if ((axisPtr->flags & (AXIS_DIRTY | AXIS_ONSCREEN)) ==
1724 (AXIS_DIRTY | AXIS_ONSCREEN)) {
1725 graphPtr->flags |= REDRAW_BACKING_STORE;
1726 }
1727 }
1728
1729 graphPtr->flags &= ~RESET_AXES;
1730
1731 /*
1732 * When any axis changes, we need to layout the entire graph.
1733 */
1734 graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED |
1735 MAP_ALL | REDRAW_WORLD);
1736 }
1737
1738 /*
1739 * ----------------------------------------------------------------------
1740 *
1741 * ResetTextStyles --
1742 *
1743 * Configures axis attributes (font, line width, label, etc) and
1744 * allocates a new (possibly shared) graphics context. Line cap
1745 * style is projecting. This is for the problem of when a tick
1746 * sits directly at the end point of the axis.
1747 *
1748 * Results:
1749 * The return value is a standard Tcl result.
1750 *
1751 * Side Effects:
1752 * Axis resources are allocated (GC, font). Axis layout is
1753 * deferred until the height and width of the window are known.
1754 *
1755 * ----------------------------------------------------------------------
1756 */
1757 static void
ResetTextStyles(graphPtr,axisPtr)1758 ResetTextStyles(graphPtr, axisPtr)
1759 Graph *graphPtr;
1760 Axis *axisPtr;
1761 {
1762 GC newGC;
1763 XGCValues gcValues;
1764 unsigned long gcMask;
1765
1766 Blt_ResetTextStyle(graphPtr->tkwin, &axisPtr->titleTextStyle);
1767 Blt_ResetTextStyle(graphPtr->tkwin, &axisPtr->tickTextStyle);
1768 Blt_ResetTextStyle(graphPtr->tkwin, &axisPtr->limitsTextStyle);
1769
1770 gcMask = (GCForeground | GCLineWidth | GCCapStyle);
1771 gcValues.foreground = axisPtr->tickTextStyle.color->pixel;
1772 gcValues.line_width = LineWidth(axisPtr->lineWidth);
1773 gcValues.cap_style = CapProjecting;
1774
1775 newGC = Tk_GetGC(graphPtr->tkwin, gcMask, &gcValues);
1776 if (axisPtr->tickGC != NULL) {
1777 Tk_FreeGC(graphPtr->display, axisPtr->tickGC);
1778 }
1779 axisPtr->tickGC = newGC;
1780 }
1781
1782 /*
1783 * ----------------------------------------------------------------------
1784 *
1785 * DestroyAxis --
1786 *
1787 * Results:
1788 * None.
1789 *
1790 * Side effects:
1791 * Resources (font, color, gc, labels, etc.) associated with the
1792 * axis are deallocated.
1793 *
1794 * ----------------------------------------------------------------------
1795 */
1796 static void
DestroyAxis(graphPtr,axisPtr)1797 DestroyAxis(graphPtr, axisPtr)
1798 Graph *graphPtr;
1799 Axis *axisPtr;
1800 {
1801 int flags;
1802
1803 flags = Blt_GraphType(graphPtr);
1804 Tk_FreeOptions(configSpecs, (char *)axisPtr, graphPtr->display, flags);
1805 if (graphPtr->bindTable != NULL) {
1806 Blt_DeleteBindings(graphPtr->bindTable, axisPtr);
1807 }
1808 if (axisPtr->linkPtr != NULL) {
1809 Blt_ChainDeleteLink(axisPtr->chainPtr, axisPtr->linkPtr);
1810 }
1811 if (axisPtr->name != NULL) {
1812 Blt_Free(axisPtr->name);
1813 }
1814 if (axisPtr->hashPtr != NULL) {
1815 Blt_DeleteHashEntry(&graphPtr->axes.table, axisPtr->hashPtr);
1816 }
1817 Blt_FreeTextStyle(graphPtr->display, &axisPtr->titleTextStyle);
1818 Blt_FreeTextStyle(graphPtr->display, &axisPtr->limitsTextStyle);
1819 Blt_FreeTextStyle(graphPtr->display, &axisPtr->tickTextStyle);
1820
1821 if (axisPtr->tickGC != NULL) {
1822 Tk_FreeGC(graphPtr->display, axisPtr->tickGC);
1823 }
1824 if (axisPtr->t1Ptr != NULL) {
1825 Blt_Free(axisPtr->t1Ptr);
1826 }
1827 if (axisPtr->t2Ptr != NULL) {
1828 Blt_Free(axisPtr->t2Ptr);
1829 }
1830 if (axisPtr->limitsFormats != NULL) {
1831 Blt_Free(axisPtr->limitsFormats);
1832 }
1833 FreeLabels(axisPtr->tickLabels);
1834 Blt_ChainDestroy(axisPtr->tickLabels);
1835 if (axisPtr->segments != NULL) {
1836 Blt_Free(axisPtr->segments);
1837 }
1838 if (axisPtr->tags != NULL) {
1839 Blt_Free(axisPtr->tags);
1840 }
1841 Blt_Free(axisPtr);
1842 }
1843
1844 static double titleRotate[4] = /* Rotation for each axis title */
1845 {
1846 0.0, 90.0, 0.0, 270.0
1847 };
1848
1849 /*
1850 * ----------------------------------------------------------------------
1851 *
1852 * AxisOffsets --
1853 *
1854 * Determines the sites of the axis, major and minor ticks,
1855 * and title of the axis.
1856 *
1857 * Results:
1858 * None.
1859 *
1860 * ----------------------------------------------------------------------
1861 */
1862 static void
AxisOffsets(graphPtr,axisPtr,margin,axisOffset,infoPtr)1863 AxisOffsets(graphPtr, axisPtr, margin, axisOffset, infoPtr)
1864 Graph *graphPtr;
1865 Axis *axisPtr;
1866 int margin;
1867 int axisOffset;
1868 AxisInfo *infoPtr;
1869 {
1870 int pad; /* Offset of axis from interior region. This
1871 * includes a possible border and the axis
1872 * line width. */
1873 int p;
1874 int majorOffset, minorOffset, labelOffset;
1875 int offset;
1876 int x, y;
1877
1878 axisPtr->titleTextStyle.theta = titleRotate[margin];
1879
1880 majorOffset = minorOffset = 0;
1881 labelOffset = AXIS_TITLE_PAD;
1882 if (axisPtr->lineWidth > 0) {
1883 majorOffset = ABS(axisPtr->tickLength);
1884 minorOffset = 10 * majorOffset / 15;
1885 labelOffset = majorOffset + AXIS_TITLE_PAD + axisPtr->lineWidth / 2;
1886 }
1887 /* Adjust offset for the interior border width and the line width */
1888 pad = axisPtr->lineWidth + 1;
1889 if (graphPtr->plotBorderWidth > 0) {
1890 pad += graphPtr->plotBorderWidth + 1;
1891 }
1892 offset = axisOffset + 1 + pad;
1893 if ((margin == MARGIN_LEFT) || (margin == MARGIN_TOP)) {
1894 majorOffset = -majorOffset;
1895 minorOffset = -minorOffset;
1896 labelOffset = -labelOffset;
1897 }
1898 /*
1899 * Pre-calculate the x-coordinate positions of the axis, tick labels, and
1900 * the individual major and minor ticks.
1901 */
1902 p = 0; /* Suppress compiler warning */
1903
1904 switch (margin) {
1905 case MARGIN_TOP:
1906 p = graphPtr->top - axisOffset - pad;
1907 if (axisPtr->titleAlternate) {
1908 x = graphPtr->right + AXIS_TITLE_PAD;
1909 y = graphPtr->top - axisOffset - (axisPtr->height / 2);
1910 axisPtr->titleTextStyle.anchor = TK_ANCHOR_W;
1911 } else {
1912 x = (graphPtr->right + graphPtr->left) / 2;
1913 y = graphPtr->top - axisOffset - axisPtr->height - AXIS_TITLE_PAD;
1914 axisPtr->titleTextStyle.anchor = TK_ANCHOR_N;
1915 }
1916 axisPtr->tickTextStyle.anchor = TK_ANCHOR_S;
1917 offset = axisPtr->borderWidth + axisPtr->lineWidth / 2;
1918 axisPtr->region.left = graphPtr->hOffset - offset - 2;
1919 axisPtr->region.right = graphPtr->hOffset + graphPtr->hRange +
1920 offset - 1;
1921 axisPtr->region.top = p + labelOffset - 1;
1922 axisPtr->region.bottom = p;
1923 axisPtr->titlePos.x = x;
1924 axisPtr->titlePos.y = y;
1925 break;
1926
1927 case MARGIN_BOTTOM:
1928 p = graphPtr->bottom + axisOffset + pad;
1929 if (axisPtr->titleAlternate) {
1930 x = graphPtr->right + AXIS_TITLE_PAD;
1931 y = graphPtr->bottom + axisOffset + (axisPtr->height / 2);
1932 axisPtr->titleTextStyle.anchor = TK_ANCHOR_W;
1933 } else {
1934 x = (graphPtr->right + graphPtr->left) / 2;
1935 y = graphPtr->bottom + axisOffset + axisPtr->height +
1936 AXIS_TITLE_PAD;
1937 axisPtr->titleTextStyle.anchor = TK_ANCHOR_S;
1938 }
1939 axisPtr->tickTextStyle.anchor = TK_ANCHOR_N;
1940 offset = axisPtr->borderWidth + axisPtr->lineWidth / 2;
1941 axisPtr->region.left = graphPtr->hOffset - offset - 2;
1942 axisPtr->region.right = graphPtr->hOffset + graphPtr->hRange +
1943 offset - 1;
1944
1945 axisPtr->region.top = graphPtr->bottom + axisOffset +
1946 axisPtr->lineWidth - axisPtr->lineWidth / 2;
1947 axisPtr->region.bottom = graphPtr->bottom + axisOffset +
1948 axisPtr->lineWidth + labelOffset + 1;
1949 axisPtr->titlePos.x = x;
1950 axisPtr->titlePos.y = y;
1951 break;
1952
1953 case MARGIN_LEFT:
1954 p = graphPtr->left - axisOffset - pad;
1955 if (axisPtr->titleAlternate) {
1956 x = graphPtr->left - axisOffset - (axisPtr->width / 2);
1957 y = graphPtr->top - AXIS_TITLE_PAD;
1958 axisPtr->titleTextStyle.anchor = TK_ANCHOR_SW;
1959 } else {
1960 x = graphPtr->left - axisOffset - axisPtr->width -
1961 graphPtr->plotBorderWidth;
1962 y = (graphPtr->bottom + graphPtr->top) / 2;
1963 axisPtr->titleTextStyle.anchor = TK_ANCHOR_W;
1964 }
1965 axisPtr->tickTextStyle.anchor = TK_ANCHOR_E;
1966 axisPtr->region.left = graphPtr->left - offset + labelOffset - 1;
1967 axisPtr->region.right = graphPtr->left - offset + 2;
1968
1969 offset = axisPtr->borderWidth + axisPtr->lineWidth / 2;
1970 axisPtr->region.top = graphPtr->vOffset - offset - 2;
1971 axisPtr->region.bottom = graphPtr->vOffset + graphPtr->vRange +
1972 offset - 1;
1973 axisPtr->titlePos.x = x;
1974 axisPtr->titlePos.y = y;
1975 break;
1976
1977 case MARGIN_RIGHT:
1978 p = graphPtr->right + axisOffset + pad;
1979 if (axisPtr->titleAlternate) {
1980 x = graphPtr->right + axisOffset + (axisPtr->width / 2);
1981 y = graphPtr->top - AXIS_TITLE_PAD;
1982 axisPtr->titleTextStyle.anchor = TK_ANCHOR_SE;
1983 } else {
1984 x = graphPtr->right + axisOffset + axisPtr->width +
1985 AXIS_TITLE_PAD;
1986 y = (graphPtr->bottom + graphPtr->top) / 2;
1987 axisPtr->titleTextStyle.anchor = TK_ANCHOR_E;
1988 }
1989 axisPtr->tickTextStyle.anchor = TK_ANCHOR_W;
1990
1991 axisPtr->region.left = graphPtr->right + axisOffset +
1992 axisPtr->lineWidth - axisPtr->lineWidth / 2;
1993 axisPtr->region.right = graphPtr->right + axisOffset +
1994 labelOffset + axisPtr->lineWidth + 1;
1995
1996 offset = axisPtr->borderWidth + axisPtr->lineWidth / 2;
1997 axisPtr->region.top = graphPtr->vOffset - offset - 2;
1998 axisPtr->region.bottom = graphPtr->vOffset + graphPtr->vRange +
1999 offset - 1;
2000 axisPtr->titlePos.x = x;
2001 axisPtr->titlePos.y = y;
2002 break;
2003
2004 case MARGIN_NONE:
2005 break;
2006 }
2007 infoPtr->axis = p - (axisPtr->lineWidth / 2);
2008 infoPtr->t1 = p + majorOffset;
2009 infoPtr->t2 = p + minorOffset;
2010 infoPtr->label = p + labelOffset;
2011
2012 if (axisPtr->tickLength < 0) {
2013 int hold;
2014
2015 hold = infoPtr->t1;
2016 infoPtr->t1 = infoPtr->axis;
2017 infoPtr->axis = hold;
2018 }
2019 }
2020
2021 static void
MakeAxisLine(graphPtr,axisPtr,line,segPtr)2022 MakeAxisLine(graphPtr, axisPtr, line, segPtr)
2023 Graph *graphPtr;
2024 Axis *axisPtr; /* Axis information */
2025 int line;
2026 Segment2D *segPtr;
2027 {
2028 double min, max;
2029
2030 min = axisPtr->axisRange.min;
2031 max = axisPtr->axisRange.max;
2032 if (axisPtr->logScale) {
2033 min = EXP10(min);
2034 max = EXP10(max);
2035 }
2036 if (AxisIsHorizontal(graphPtr, axisPtr)) {
2037 segPtr->p.x = Blt_HMap(graphPtr, axisPtr, min);
2038 segPtr->q.x = Blt_HMap(graphPtr, axisPtr, max);
2039 segPtr->p.y = segPtr->q.y = line;
2040 } else {
2041 segPtr->q.x = segPtr->p.x = line;
2042 segPtr->p.y = Blt_VMap(graphPtr, axisPtr, min);
2043 segPtr->q.y = Blt_VMap(graphPtr, axisPtr, max);
2044 }
2045 }
2046
2047
2048 static void
MakeTick(graphPtr,axisPtr,value,tick,line,segPtr)2049 MakeTick(graphPtr, axisPtr, value, tick, line, segPtr)
2050 Graph *graphPtr;
2051 Axis *axisPtr;
2052 double value;
2053 int tick, line; /* Lengths of tick and axis line. */
2054 Segment2D *segPtr;
2055 {
2056 if (axisPtr->logScale) {
2057 value = EXP10(value);
2058 }
2059 if (AxisIsHorizontal(graphPtr, axisPtr)) {
2060 segPtr->p.x = segPtr->q.x = Blt_HMap(graphPtr, axisPtr, value);
2061 segPtr->p.y = line;
2062 segPtr->q.y = tick;
2063 } else {
2064 segPtr->p.x = line;
2065 segPtr->p.y = segPtr->q.y = Blt_VMap(graphPtr, axisPtr, value);
2066 segPtr->q.x = tick;
2067 }
2068 }
2069
2070 /*
2071 * -----------------------------------------------------------------
2072 *
2073 * MapAxis --
2074 *
2075 * Pre-calculates positions of the axis, ticks, and labels (to be
2076 * used later when displaying the axis). Calculates the values
2077 * for each major and minor tick and checks to see if they are in
2078 * range (the outer ticks may be outside of the range of plotted
2079 * values).
2080 *
2081 * Line segments for the minor and major ticks are saved into one
2082 * XSegment array so that they can be drawn by a single
2083 * XDrawSegments call. The positions of the tick labels are also
2084 * computed and saved.
2085 *
2086 * Results:
2087 * None.
2088 *
2089 * Side Effects:
2090 * Line segments and tick labels are saved and used later to draw
2091 * the axis.
2092 *
2093 * -----------------------------------------------------------------
2094 */
2095 static void
MapAxis(graphPtr,axisPtr,offset,margin)2096 MapAxis(graphPtr, axisPtr, offset, margin)
2097 Graph *graphPtr;
2098 Axis *axisPtr;
2099 int offset;
2100 int margin;
2101 {
2102 int arraySize;
2103 int nMajorTicks, nMinorTicks;
2104 AxisInfo info;
2105 Segment2D *segments;
2106 Segment2D *segPtr;
2107
2108 AxisOffsets(graphPtr, axisPtr, margin, offset, &info);
2109
2110 /* Save all line coordinates in an array of line segments. */
2111
2112 if (axisPtr->segments != NULL) {
2113 Blt_Free(axisPtr->segments);
2114 }
2115 nMajorTicks = nMinorTicks = 0;
2116 if (axisPtr->t1Ptr != NULL) {
2117 nMajorTicks = axisPtr->t1Ptr->nTicks;
2118 }
2119 if (axisPtr->t2Ptr != NULL) {
2120 nMinorTicks = axisPtr->t2Ptr->nTicks;
2121 }
2122 arraySize = 1 + (nMajorTicks * (nMinorTicks + 1));
2123 segments = Blt_Malloc(arraySize * sizeof(Segment2D));
2124 assert(segments);
2125
2126 segPtr = segments;
2127 if (axisPtr->lineWidth > 0) {
2128 /* Axis baseline */
2129 MakeAxisLine(graphPtr, axisPtr, info.axis, segPtr);
2130 segPtr++;
2131 }
2132 if (axisPtr->showTicks) {
2133 double t1, t2;
2134 double labelPos;
2135 register int i, j;
2136 int isHoriz;
2137 TickLabel *labelPtr;
2138 Blt_ChainLink *linkPtr;
2139 Segment2D seg;
2140
2141 isHoriz = AxisIsHorizontal(graphPtr, axisPtr);
2142 for (i = 0; i < axisPtr->t1Ptr->nTicks; i++) {
2143 t1 = axisPtr->t1Ptr->values[i];
2144 /* Minor ticks */
2145 for (j = 0; j < axisPtr->t2Ptr->nTicks; j++) {
2146 t2 = t1 +
2147 (axisPtr->majorSweep.step * axisPtr->t2Ptr->values[j]);
2148 if (InRange(t2, &axisPtr->axisRange)) {
2149 MakeTick(graphPtr, axisPtr, t2, info.t2, info.axis,
2150 segPtr);
2151 segPtr++;
2152 }
2153 }
2154 if (!InRange(t1, &axisPtr->axisRange)) {
2155 continue;
2156 }
2157 /* Major tick */
2158 MakeTick(graphPtr, axisPtr, t1, info.t1, info.axis, segPtr);
2159 segPtr++;
2160 }
2161
2162 linkPtr = Blt_ChainFirstLink(axisPtr->tickLabels);
2163 labelPos = (double)info.label;
2164
2165 for (i = 0; i < axisPtr->t1Ptr->nTicks; i++) {
2166 t1 = axisPtr->t1Ptr->values[i];
2167 if (axisPtr->labelOffset) {
2168 t1 += axisPtr->majorSweep.step * 0.5;
2169 }
2170 if (!InRange(t1, &axisPtr->axisRange)) {
2171 continue;
2172 }
2173 labelPtr = Blt_ChainGetValue(linkPtr);
2174 linkPtr = Blt_ChainNextLink(linkPtr);
2175 MakeTick(graphPtr, axisPtr, t1, info.t1, info.axis, &seg);
2176 /* Save tick label X-Y position. */
2177 if (isHoriz) {
2178 labelPtr->anchorPos.x = seg.p.x;
2179 labelPtr->anchorPos.y = labelPos;
2180 } else {
2181 labelPtr->anchorPos.x = labelPos;
2182 labelPtr->anchorPos.y = seg.p.y;
2183 }
2184 }
2185 }
2186 if (AxisIsHorizontal(graphPtr, axisPtr)) {
2187 axisPtr->width = graphPtr->right - graphPtr->left;
2188 } else {
2189 axisPtr->height = graphPtr->bottom - graphPtr->top;
2190 }
2191 axisPtr->segments = segments;
2192 axisPtr->nSegments = segPtr - segments;
2193 assert(axisPtr->nSegments <= arraySize);
2194 }
2195
2196 /*
2197 *----------------------------------------------------------------------
2198 *
2199 * AdjustViewport --
2200 *
2201 * Adjusts the offsets of the viewport according to the scroll mode.
2202 * This is to accommodate both "listbox" and "canvas" style scrolling.
2203 *
2204 * "canvas" The viewport scrolls within the range of world
2205 * coordinates. This way the viewport always displays
2206 * a full page of the world. If the world is smaller
2207 * than the viewport, then (bizarrely) the world and
2208 * viewport are inverted so that the world moves up
2209 * and down within the viewport.
2210 *
2211 * "listbox" The viewport can scroll beyond the range of world
2212 * coordinates. Every entry can be displayed at the
2213 * top of the viewport. This also means that the
2214 * scrollbar thumb weirdly shrinks as the last entry
2215 * is scrolled upward.
2216 *
2217 * Results:
2218 * The corrected offset is returned.
2219 *
2220 *----------------------------------------------------------------------
2221 */
2222 static double
AdjustViewport(offset,windowSize)2223 AdjustViewport(offset, windowSize)
2224 double offset, windowSize;
2225 {
2226 /*
2227 * Canvas-style scrolling allows the world to be scrolled
2228 * within the window.
2229 */
2230 if (windowSize > 1.0) {
2231 if (windowSize < (1.0 - offset)) {
2232 offset = 1.0 - windowSize;
2233 }
2234 if (offset > 0.0) {
2235 offset = 0.0;
2236 }
2237 } else {
2238 if ((offset + windowSize) > 1.0) {
2239 offset = 1.0 - windowSize;
2240 }
2241 if (offset < 0.0) {
2242 offset = 0.0;
2243 }
2244 }
2245 return offset;
2246 }
2247
2248 static int
GetAxisScrollInfo(interp,argc,argv,offsetPtr,windowSize,scrollUnits)2249 GetAxisScrollInfo(interp, argc, argv, offsetPtr, windowSize, scrollUnits)
2250 Tcl_Interp *interp;
2251 int argc;
2252 char **argv;
2253 double *offsetPtr;
2254 double windowSize;
2255 double scrollUnits;
2256 {
2257 char c;
2258 unsigned int length;
2259 double offset;
2260 int count;
2261 double fract;
2262
2263 offset = *offsetPtr;
2264 c = argv[0][0];
2265 length = strlen(argv[0]);
2266 if ((c == 's') && (strncmp(argv[0], "scroll", length) == 0)) {
2267 /* assert(argc == 3); */
2268 if (argc != 3) {
2269 Tcl_AppendResult(interp, "expected arg", 0);
2270 return TCL_ERROR;
2271 }
2272 /* scroll number unit/page */
2273 if (Tcl_GetInt(interp, argv[1], &count) != TCL_OK) {
2274 return TCL_ERROR;
2275 }
2276 c = argv[2][0];
2277 length = strlen(argv[2]);
2278 if ((c == 'u') && (strncmp(argv[2], "units", length) == 0)) {
2279 fract = (double)count * scrollUnits;
2280 } else if ((c == 'p') && (strncmp(argv[2], "pages", length) == 0)) {
2281 /* A page is 90% of the view-able window. */
2282 fract = (double)count * windowSize * 0.9;
2283 } else {
2284 Tcl_AppendResult(interp, "unknown \"scroll\" units \"", argv[2],
2285 "\"", (char *)NULL);
2286 return TCL_ERROR;
2287 }
2288 offset += fract;
2289 } else if ((c == 'm') && (strncmp(argv[0], "moveto", length) == 0)) {
2290 /*assert(argc == 2);*/
2291 if (argc != 2) {
2292 Tcl_AppendResult(interp, "expected arg", 0);
2293 return TCL_ERROR;
2294 }
2295 /* moveto fraction */
2296 if (Tcl_GetDouble(interp, argv[1], &fract) != TCL_OK) {
2297 return TCL_ERROR;
2298 }
2299 offset = fract;
2300 } else {
2301 /* Treat like "scroll units" */
2302 if (Tcl_GetInt(interp, argv[0], &count) != TCL_OK) {
2303 return TCL_ERROR;
2304 }
2305 fract = (double)count * scrollUnits;
2306 offset += fract;
2307 /* CHECK THIS: return TCL_OK; */
2308 }
2309 *offsetPtr = AdjustViewport(offset, windowSize);
2310 return TCL_OK;
2311 }
2312
2313 /*
2314 * -----------------------------------------------------------------
2315 *
2316 * DrawAxis --
2317 *
2318 * Draws the axis, ticks, and labels onto the canvas.
2319 *
2320 * Initializes and passes text attribute information through
2321 * TextStyle structure.
2322 *
2323 * Results:
2324 * None.
2325 *
2326 * Side Effects:
2327 * Axis gets drawn on window.
2328 *
2329 * -----------------------------------------------------------------
2330 */
2331 static void
DrawAxis(graphPtr,drawable,axisPtr)2332 DrawAxis(graphPtr, drawable, axisPtr)
2333 Graph *graphPtr;
2334 Drawable drawable;
2335 Axis *axisPtr;
2336 {
2337 if (axisPtr->border != NULL) {
2338 Blt_Fill3DRectangle(graphPtr->tkwin, drawable, axisPtr->border,
2339 axisPtr->region.left + graphPtr->plotBorderWidth,
2340 axisPtr->region.top + graphPtr->plotBorderWidth,
2341 axisPtr->region.right - axisPtr->region.left,
2342 axisPtr->region.bottom - axisPtr->region.top,
2343 axisPtr->borderWidth, axisPtr->relief);
2344 }
2345 if (axisPtr->title != NULL) {
2346 Blt_DrawText(graphPtr->tkwin, drawable, axisPtr->title,
2347 &axisPtr->titleTextStyle, (int)axisPtr->titlePos.x,
2348 (int)axisPtr->titlePos.y);
2349 }
2350 if (axisPtr->scrollCmdPrefix != NULL) {
2351 double viewWidth, viewMin, viewMax;
2352 double worldWidth, worldMin, worldMax;
2353 double fract;
2354 int isHoriz;
2355
2356 worldMin = axisPtr->valueRange.min;
2357 worldMax = axisPtr->valueRange.max;
2358 if (DEFINED(axisPtr->scrollMin)) {
2359 worldMin = axisPtr->scrollMin;
2360 }
2361 if (DEFINED(axisPtr->scrollMax)) {
2362 worldMax = axisPtr->scrollMax;
2363 }
2364 viewMin = axisPtr->min;
2365 viewMax = axisPtr->max;
2366 if (viewMin < worldMin) {
2367 viewMin = worldMin;
2368 }
2369 if (viewMax > worldMax) {
2370 viewMax = worldMax;
2371 }
2372 if (axisPtr->logScale) {
2373 worldMin = log10(worldMin);
2374 worldMax = log10(worldMax);
2375 viewMin = log10(viewMin);
2376 viewMax = log10(viewMax);
2377 }
2378 worldWidth = worldMax - worldMin;
2379 viewWidth = viewMax - viewMin;
2380 isHoriz = AxisIsHorizontal(graphPtr, axisPtr);
2381
2382 if (isHoriz != axisPtr->descending) {
2383 fract = (viewMin - worldMin) / worldWidth;
2384 } else {
2385 fract = (worldMax - viewMax) / worldWidth;
2386 }
2387 fract = AdjustViewport(fract, viewWidth / worldWidth);
2388
2389 if (isHoriz != axisPtr->descending) {
2390 viewMin = (fract * worldWidth);
2391 axisPtr->min = viewMin + worldMin;
2392 axisPtr->max = axisPtr->min + viewWidth;
2393 viewMax = viewMin + viewWidth;
2394 if (axisPtr->logScale) {
2395 axisPtr->min = EXP10(axisPtr->min);
2396 axisPtr->max = EXP10(axisPtr->max);
2397 }
2398 Blt_UpdateScrollbar(graphPtr->interp, axisPtr->scrollCmdPrefix,
2399 (viewMin / worldWidth), (viewMax / worldWidth));
2400 } else {
2401 viewMax = (fract * worldWidth);
2402 axisPtr->max = worldMax - viewMax;
2403 axisPtr->min = axisPtr->max - viewWidth;
2404 viewMin = viewMax + viewWidth;
2405 if (axisPtr->logScale) {
2406 axisPtr->min = EXP10(axisPtr->min);
2407 axisPtr->max = EXP10(axisPtr->max);
2408 }
2409 Blt_UpdateScrollbar(graphPtr->interp, axisPtr->scrollCmdPrefix,
2410 (viewMax / worldWidth), (viewMin / worldWidth));
2411 }
2412 }
2413 if (axisPtr->showTicks) {
2414 register Blt_ChainLink *linkPtr;
2415 TickLabel *labelPtr;
2416
2417 for (linkPtr = Blt_ChainFirstLink(axisPtr->tickLabels); linkPtr != NULL;
2418 linkPtr = Blt_ChainNextLink(linkPtr)) {
2419 /* Draw major tick labels */
2420 labelPtr = Blt_ChainGetValue(linkPtr);
2421 Blt_DrawText(graphPtr->tkwin, drawable, labelPtr->string,
2422 &axisPtr->tickTextStyle, (int)labelPtr->anchorPos.x,
2423 (int)labelPtr->anchorPos.y);
2424 }
2425 }
2426 if ((axisPtr->nSegments > 0) && (axisPtr->lineWidth > 0)) {
2427 /* Draw the tick marks and axis line. */
2428 Blt_Draw2DSegments(graphPtr->display, drawable, axisPtr->tickGC,
2429 axisPtr->segments, axisPtr->nSegments);
2430 }
2431 }
2432
2433 /*
2434 * -----------------------------------------------------------------
2435 *
2436 * AxisToPostScript --
2437 *
2438 * Generates PostScript output to draw the axis, ticks, and
2439 * labels.
2440 *
2441 * Initializes and passes text attribute information through
2442 * TextStyle structure.
2443 *
2444 * Results:
2445 * None.
2446 *
2447 * Side Effects:
2448 * PostScript output is left in graphPtr->interp->result;
2449 *
2450 * -----------------------------------------------------------------
2451 */
2452 /* ARGSUSED */
2453 static void
AxisToPostScript(psToken,axisPtr)2454 AxisToPostScript(psToken, axisPtr)
2455 PsToken psToken;
2456 Axis *axisPtr;
2457 {
2458 if (axisPtr->title != NULL) {
2459 Blt_TextToPostScript(psToken, axisPtr->title, &axisPtr->titleTextStyle,
2460 axisPtr->titlePos.x, axisPtr->titlePos.y);
2461 }
2462 if (axisPtr->showTicks) {
2463 register Blt_ChainLink *linkPtr;
2464 TickLabel *labelPtr;
2465
2466 for (linkPtr = Blt_ChainFirstLink(axisPtr->tickLabels);
2467 linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
2468 labelPtr = Blt_ChainGetValue(linkPtr);
2469 Blt_TextToPostScript(psToken, labelPtr->string,
2470 &axisPtr->tickTextStyle, labelPtr->anchorPos.x,
2471 labelPtr->anchorPos.y);
2472 }
2473 }
2474 if ((axisPtr->nSegments > 0) && (axisPtr->lineWidth > 0)) {
2475 Blt_LineAttributesToPostScript(psToken, axisPtr->tickTextStyle.color,
2476 axisPtr->lineWidth, (Blt_Dashes *)NULL, CapButt, JoinMiter);
2477 Blt_2DSegmentsToPostScript(psToken, axisPtr->segments,
2478 axisPtr->nSegments);
2479 }
2480 }
2481
2482 static void
MakeGridLine(graphPtr,axisPtr,value,segPtr)2483 MakeGridLine(graphPtr, axisPtr, value, segPtr)
2484 Graph *graphPtr;
2485 Axis *axisPtr;
2486 double value;
2487 Segment2D *segPtr;
2488 {
2489 if (axisPtr->logScale) {
2490 value = EXP10(value);
2491 }
2492 /* Grid lines run orthogonally to the axis */
2493 if (AxisIsHorizontal(graphPtr, axisPtr)) {
2494 segPtr->p.y = graphPtr->top;
2495 segPtr->q.y = graphPtr->bottom;
2496 segPtr->p.x = segPtr->q.x = Blt_HMap(graphPtr, axisPtr, value);
2497 } else {
2498 segPtr->p.x = graphPtr->left;
2499 segPtr->q.x = graphPtr->right;
2500 segPtr->p.y = segPtr->q.y = Blt_VMap(graphPtr, axisPtr, value);
2501 }
2502 }
2503
2504 /*
2505 *----------------------------------------------------------------------
2506 *
2507 * Blt_GetAxisSegments --
2508 *
2509 * Assembles the grid lines associated with an axis. Generates
2510 * tick positions if necessary (this happens when the axis is
2511 * not a logical axis too).
2512 *
2513 * Results:
2514 * None.
2515 *
2516 *----------------------------------------------------------------------
2517 */
2518 void
Blt_GetAxisSegments(graphPtr,axisPtr,segPtrPtr,nSegmentsPtr)2519 Blt_GetAxisSegments(graphPtr, axisPtr, segPtrPtr, nSegmentsPtr)
2520 Graph *graphPtr;
2521 Axis *axisPtr;
2522 Segment2D **segPtrPtr;
2523 int *nSegmentsPtr;
2524 {
2525 int needed;
2526 Ticks *t1Ptr, *t2Ptr;
2527 register int i;
2528 double value;
2529 Segment2D *segments, *segPtr;
2530
2531 *nSegmentsPtr = 0;
2532 *segPtrPtr = NULL;
2533 if (axisPtr == NULL) {
2534 return;
2535 }
2536 t1Ptr = axisPtr->t1Ptr;
2537 if (t1Ptr == NULL) {
2538 t1Ptr = GenerateTicks(&axisPtr->majorSweep);
2539 }
2540 t2Ptr = axisPtr->t2Ptr;
2541 if (t2Ptr == NULL) {
2542 t2Ptr = GenerateTicks(&axisPtr->minorSweep);
2543 }
2544
2545 needed = t1Ptr->nTicks;
2546 if (graphPtr->gridPtr->minorGrid) {
2547 needed += (t1Ptr->nTicks * t2Ptr->nTicks);
2548 }
2549 if (needed == 0) {
2550 return;
2551 }
2552 segments = Blt_Malloc(sizeof(Segment2D) * needed);
2553 if (segments == NULL) {
2554 return; /* Can't allocate memory for grid. */
2555 }
2556
2557 segPtr = segments;
2558 for (i = 0; i < t1Ptr->nTicks; i++) {
2559 value = t1Ptr->values[i];
2560 if (graphPtr->gridPtr->minorGrid) {
2561 register int j;
2562 double subValue;
2563
2564 for (j = 0; j < t2Ptr->nTicks; j++) {
2565 subValue = value +
2566 (axisPtr->majorSweep.step * t2Ptr->values[j]);
2567 if (InRange(subValue, &axisPtr->axisRange)) {
2568 MakeGridLine(graphPtr, axisPtr, subValue, segPtr);
2569 segPtr++;
2570 }
2571 }
2572 }
2573 if (InRange(value, &axisPtr->axisRange)) {
2574 MakeGridLine(graphPtr, axisPtr, value, segPtr);
2575 segPtr++;
2576 }
2577 }
2578
2579 if (t1Ptr != axisPtr->t1Ptr) {
2580 Blt_Free(t1Ptr); /* Free generated ticks. */
2581 }
2582 if (t2Ptr != axisPtr->t2Ptr) {
2583 Blt_Free(t2Ptr); /* Free generated ticks. */
2584 }
2585 *nSegmentsPtr = segPtr - segments;
2586 assert(*nSegmentsPtr <= needed);
2587 *segPtrPtr = segments;
2588 }
2589
2590 /*
2591 *----------------------------------------------------------------------
2592 *
2593 * GetAxisGeometry --
2594 *
2595 * Results:
2596 * None.
2597 *
2598 *----------------------------------------------------------------------
2599 */
2600 static void
GetAxisGeometry(graphPtr,axisPtr)2601 GetAxisGeometry(graphPtr, axisPtr)
2602 Graph *graphPtr;
2603 Axis *axisPtr;
2604 {
2605 int height;
2606
2607 FreeLabels(axisPtr->tickLabels);
2608 height = 0;
2609 if (axisPtr->lineWidth > 0) {
2610 /* Leave room for axis baseline (and pad) */
2611 height += axisPtr->lineWidth + 2;
2612 }
2613 if (axisPtr->showTicks) {
2614 int pad;
2615 register int i, nLabels;
2616 int lw, lh;
2617 double x, x2;
2618 int maxWidth, maxHeight;
2619 TickLabel *labelPtr;
2620
2621 SweepTicks(axisPtr);
2622
2623 if (axisPtr->t1Ptr->nTicks < 0) {
2624 fprintf(stderr, "%s major ticks can't be %d\n",
2625 axisPtr->name, axisPtr->t1Ptr->nTicks);
2626 abort();
2627 }
2628 if (axisPtr->t1Ptr->nTicks > MAXTICKS) {
2629 fprintf(stderr, "too big, %s major ticks can't be %d\n",
2630 axisPtr->name, axisPtr->t1Ptr->nTicks);
2631 abort();
2632 }
2633
2634 maxHeight = maxWidth = 0;
2635 nLabels = 0;
2636 for (i = 0; i < axisPtr->t1Ptr->nTicks; i++) {
2637 x2 = x = axisPtr->t1Ptr->values[i];
2638 if (axisPtr->labelOffset) {
2639 x2 += axisPtr->majorSweep.step * 0.5;
2640 }
2641 if (!InRange(x2, &axisPtr->axisRange)) {
2642 continue;
2643 }
2644 labelPtr = MakeLabel(graphPtr, axisPtr, x);
2645 Blt_ChainAppend(axisPtr->tickLabels, labelPtr);
2646 nLabels++;
2647 /*
2648 * Get the dimensions of each tick label.
2649 * Remember tick labels can be multi-lined and/or rotated.
2650 */
2651 Blt_GetTextExtents(&axisPtr->tickTextStyle, labelPtr->string,
2652 &lw, &lh);
2653 labelPtr->width = lw;
2654 labelPtr->height = lh;
2655
2656 if (axisPtr->tickTextStyle.theta > 0.0) {
2657 double rotWidth, rotHeight;
2658
2659 Blt_GetBoundingBox(lw, lh, axisPtr->tickTextStyle.theta,
2660 &rotWidth, &rotHeight, (Point2D *)NULL);
2661 lw = ROUND(rotWidth);
2662 lh = ROUND(rotHeight);
2663 }
2664 if (maxWidth < lw) {
2665 maxWidth = lw;
2666 }
2667 if (maxHeight < lh) {
2668 maxHeight = lh;
2669 }
2670 }
2671 assert(nLabels <= axisPtr->t1Ptr->nTicks);
2672
2673 /* Because the axis cap style is "CapProjecting", we need to
2674 * account for an extra 1.5 linewidth at the end of each
2675 * line. */
2676
2677 pad = ((axisPtr->lineWidth * 15) / 10);
2678
2679 if (AxisIsHorizontal(graphPtr, axisPtr)) {
2680 height += maxHeight + pad;
2681 } else {
2682 height += maxWidth + pad;
2683 }
2684 if (axisPtr->lineWidth > 0) {
2685 /* Distance from axis line to tick label. */
2686 height += AXIS_TITLE_PAD;
2687 height += ABS(axisPtr->tickLength);
2688 }
2689 }
2690
2691 if (axisPtr->title != NULL) {
2692 if (axisPtr->titleAlternate) {
2693 if (height < axisPtr->titleHeight) {
2694 height = axisPtr->titleHeight;
2695 }
2696 } else {
2697 height += axisPtr->titleHeight + AXIS_TITLE_PAD;
2698 }
2699 }
2700
2701 /* Correct for orientation of the axis. */
2702 if (AxisIsHorizontal(graphPtr, axisPtr)) {
2703 axisPtr->height = height;
2704 } else {
2705 axisPtr->width = height;
2706 }
2707 }
2708
2709 /*
2710 *----------------------------------------------------------------------
2711 *
2712 * GetMarginGeometry --
2713 *
2714 * Examines all the axes in the given margin and determines the
2715 * area required to display them.
2716 *
2717 * Note: For multiple axes, the titles are displayed in another
2718 * margin. So we must keep track of the widest title.
2719 *
2720 * Results:
2721 * Returns the width or height of the margin, depending if it
2722 * runs horizontally along the graph or vertically.
2723 *
2724 * Side Effects:
2725 * The area width and height set in the margin. Note again that
2726 * this may be corrected later (mulitple axes) to adjust for
2727 * the longest title in another margin.
2728 *
2729 *----------------------------------------------------------------------
2730 */
2731 static int
GetMarginGeometry(graphPtr,marginPtr)2732 GetMarginGeometry(graphPtr, marginPtr)
2733 Graph *graphPtr;
2734 Margin *marginPtr;
2735 {
2736 Blt_ChainLink *linkPtr;
2737 Axis *axisPtr;
2738 int width, height;
2739 int isHoriz;
2740 int length, count;
2741
2742 isHoriz = HORIZMARGIN(marginPtr);
2743 /* Count the number of visible axes. */
2744 count = 0;
2745 length = width = height = 0;
2746 for (linkPtr = Blt_ChainFirstLink(marginPtr->axes); linkPtr != NULL;
2747 linkPtr = Blt_ChainNextLink(linkPtr)) {
2748 axisPtr = Blt_ChainGetValue(linkPtr);
2749 if ((!axisPtr->hidden) && (axisPtr->flags & AXIS_ONSCREEN)) {
2750 count++;
2751 if (graphPtr->flags & GET_AXIS_GEOMETRY) {
2752 GetAxisGeometry(graphPtr, axisPtr);
2753 }
2754 if ((axisPtr->titleAlternate) && (length < axisPtr->titleWidth)) {
2755 length = axisPtr->titleWidth;
2756 }
2757 if (isHoriz) {
2758 height += axisPtr->height;
2759 } else {
2760 width += axisPtr->width;
2761 }
2762 }
2763 }
2764 /* Enforce a minimum size for margins. */
2765 if (width < 3) {
2766 width = 3;
2767 }
2768 if (height < 3) {
2769 height = 3;
2770 }
2771 marginPtr->nAxes = count;
2772 marginPtr->axesTitleLength = length;
2773 marginPtr->width = width;
2774 marginPtr->height = height;
2775 marginPtr->axesOffset = (HORIZMARGIN(marginPtr)) ? height : width;
2776 return marginPtr->axesOffset;
2777 }
2778
2779 /*
2780 *----------------------------------------------------------------------
2781 *
2782 * ComputeMargins --
2783 *
2784 * Computes the size of the margins and the plotting area. We
2785 * first compute the space needed for the axes in each margin.
2786 * Then how much space the legend will occupy. Finally, if the
2787 * user has requested a margin size, we override the computed
2788 * value.
2789 *
2790 * Results:
2791 *
2792 *---------------------------------------------------------------------- */
2793 static void
ComputeMargins(graphPtr)2794 ComputeMargins(graphPtr)
2795 Graph *graphPtr;
2796 {
2797 int left, right, top, bottom;
2798 int width, height;
2799 int insets;
2800
2801 /*
2802 * Step 1: Compute the amount of space needed to display the
2803 * axes (there many be 0 or more) associated with the
2804 * margin.
2805 */
2806 top = GetMarginGeometry(graphPtr, &graphPtr->topMargin);
2807 bottom = GetMarginGeometry(graphPtr, &graphPtr->bottomMargin);
2808 left = GetMarginGeometry(graphPtr, &graphPtr->leftMargin);
2809 right = GetMarginGeometry(graphPtr, &graphPtr->rightMargin);
2810
2811 /*
2812 * Step 2: Add the graph title height to the top margin.
2813 */
2814 if (graphPtr->title != NULL) {
2815 top += graphPtr->titleTextStyle.height;
2816 }
2817 insets = 2 * (graphPtr->inset + graphPtr->plotBorderWidth);
2818
2819 /*
2820 * Step 3: Use the current estimate of the plot area to compute
2821 * the legend size. Add it to the proper margin.
2822 */
2823 width = graphPtr->width - (insets + left + right);
2824 height = graphPtr->height - (insets + top + bottom);
2825 Blt_MapLegend(graphPtr->legend, width, height);
2826 if (!Blt_LegendIsHidden(graphPtr->legend)) {
2827 switch (Blt_LegendSite(graphPtr->legend)) {
2828 case LEGEND_RIGHT:
2829 right += Blt_LegendWidth(graphPtr->legend) + 2;
2830 break;
2831 case LEGEND_LEFT:
2832 left += Blt_LegendWidth(graphPtr->legend) + 2;
2833 break;
2834 case LEGEND_TOP:
2835 top += Blt_LegendHeight(graphPtr->legend) + 2;
2836 break;
2837 case LEGEND_BOTTOM:
2838 bottom += Blt_LegendHeight(graphPtr->legend) + 2;
2839 break;
2840 case LEGEND_XY:
2841 case LEGEND_PLOT:
2842 case LEGEND_WINDOW:
2843 /* Do nothing. */
2844 break;
2845 }
2846 }
2847
2848 /*
2849 * Recompute the plotarea, now accounting for the legend.
2850 */
2851 width = graphPtr->width - (insets + left + right);
2852 height = graphPtr->height - (insets + top + bottom);
2853
2854 /*
2855 * Step 5: If necessary, correct for the requested plot area
2856 * aspect ratio.
2857 */
2858 if (graphPtr->aspect > 0.0) {
2859 double ratio;
2860
2861 /*
2862 * Shrink one dimension of the plotarea to fit the requested
2863 * width/height aspect ratio.
2864 */
2865 ratio = (double)width / (double)height;
2866 if (ratio > graphPtr->aspect) {
2867 int scaledWidth;
2868
2869 /* Shrink the width. */
2870 scaledWidth = (int)(height * graphPtr->aspect);
2871 if (scaledWidth < 1) {
2872 scaledWidth = 1;
2873 }
2874 right += (width - scaledWidth); /* Add the difference to
2875 * the right margin. */
2876 /* CHECK THIS: width = scaledWidth; */
2877 } else {
2878 int scaledHeight;
2879
2880 /* Shrink the height. */
2881 scaledHeight = (int)(width / graphPtr->aspect);
2882 if (scaledHeight < 1) {
2883 scaledHeight = 1;
2884 }
2885 top += (height - scaledHeight); /* Add the difference to
2886 * the top margin. */
2887 /* CHECK THIS: height = scaledHeight; */
2888 }
2889 }
2890
2891 /*
2892 * Step 6: If there's multiple axes in a margin, the axis
2893 * titles will be displayed in the adjoining marging.
2894 * Make sure there's room for the longest axis titles.
2895 */
2896
2897 if (top < graphPtr->leftMargin.axesTitleLength) {
2898 top = graphPtr->leftMargin.axesTitleLength;
2899 }
2900 if (right < graphPtr->bottomMargin.axesTitleLength) {
2901 right = graphPtr->bottomMargin.axesTitleLength;
2902 }
2903 if (top < graphPtr->rightMargin.axesTitleLength) {
2904 top = graphPtr->rightMargin.axesTitleLength;
2905 }
2906 if (right < graphPtr->topMargin.axesTitleLength) {
2907 right = graphPtr->topMargin.axesTitleLength;
2908 }
2909
2910 /*
2911 * Step 7: Override calculated values with requested margin
2912 * sizes.
2913 */
2914
2915 graphPtr->leftMargin.width = left;
2916 graphPtr->rightMargin.width = right;
2917 graphPtr->topMargin.height = top;
2918 graphPtr->bottomMargin.height = bottom;
2919
2920 if (graphPtr->leftMargin.reqSize > 0) {
2921 graphPtr->leftMargin.width = graphPtr->leftMargin.reqSize;
2922 }
2923 if (graphPtr->rightMargin.reqSize > 0) {
2924 graphPtr->rightMargin.width = graphPtr->rightMargin.reqSize;
2925 }
2926 if (graphPtr->topMargin.reqSize > 0) {
2927 graphPtr->topMargin.height = graphPtr->topMargin.reqSize;
2928 }
2929 if (graphPtr->bottomMargin.reqSize > 0) {
2930 graphPtr->bottomMargin.height = graphPtr->bottomMargin.reqSize;
2931 }
2932 }
2933
2934 /*
2935 * -----------------------------------------------------------------
2936 *
2937 * Blt_LayoutMargins --
2938 *
2939 * Calculate the layout of the graph. Based upon the data,
2940 * axis limits, X and Y titles, and title height, determine
2941 * the cavity left which is the plotting surface. The first
2942 * step get the data and axis limits for calculating the space
2943 * needed for the top, bottom, left, and right margins.
2944 *
2945 * 1) The LEFT margin is the area from the left border to the
2946 * Y axis (not including ticks). It composes the border
2947 * width, the width an optional Y axis label and its padding,
2948 * and the tick numeric labels. The Y axis label is rotated
2949 * 90 degrees so that the width is the font height.
2950 *
2951 * 2) The RIGHT margin is the area from the end of the graph
2952 * to the right window border. It composes the border width,
2953 * some padding, the font height (this may be dubious. It
2954 * appears to provide a more even border), the max of the
2955 * legend width and 1/2 max X tick number. This last part is
2956 * so that the last tick label is not clipped.
2957 *
2958 * Window Width
2959 * ___________________________________________________________
2960 * | | | |
2961 * | | TOP height of title | |
2962 * | | | |
2963 * | | x2 title | |
2964 * | | | |
2965 * | | height of x2-axis | |
2966 * |__________|_______________________________|_______________| W
2967 * | | -plotpady | | i
2968 * |__________|_______________________________|_______________| n
2969 * | | top right | | d
2970 * | | | | o
2971 * | LEFT | | RIGHT | w
2972 * | | | |
2973 * | y | Free area = 104% | y2 | H
2974 * | | Plotting surface = 100% | | e
2975 * | t | Tick length = 2 + 2% | t | i
2976 * | i | | i | g
2977 * | t | | t legend| h
2978 * | l | | l width| t
2979 * | e | | e |
2980 * | height| |height |
2981 * | of | | of |
2982 * | y-axis| |y2-axis |
2983 * | | | |
2984 * | |origin 0,0 | |
2985 * |__________|_left___________________bottom___|_______________|
2986 * | |-plotpady | |
2987 * |__________|_______________________________|_______________|
2988 * | | (xoffset, yoffset) | |
2989 * | | | |
2990 * | | height of x-axis | |
2991 * | | | |
2992 * | | BOTTOM x title | |
2993 * |__________|_______________________________|_______________|
2994 *
2995 * 3) The TOP margin is the area from the top window border to the top
2996 * of the graph. It composes the border width, twice the height of
2997 * the title font (if one is given) and some padding between the
2998 * title.
2999 *
3000 * 4) The BOTTOM margin is area from the bottom window border to the
3001 * X axis (not including ticks). It composes the border width, the height
3002 * an optional X axis label and its padding, the height of the font
3003 * of the tick labels.
3004 *
3005 * The plotting area is between the margins which includes the X and Y axes
3006 * including the ticks but not the tick numeric labels. The length of
3007 * the ticks and its padding is 5% of the entire plotting area. Hence the
3008 * entire plotting area is scaled as 105% of the width and height of the
3009 * area.
3010 *
3011 * The axis labels, ticks labels, title, and legend may or may not be
3012 * displayed which must be taken into account.
3013 *
3014 *
3015 * -----------------------------------------------------------------
3016 */
3017 void
Blt_LayoutMargins(graphPtr)3018 Blt_LayoutMargins(graphPtr)
3019 Graph *graphPtr;
3020 {
3021 int width, height;
3022 int titleY;
3023 int left, right, top, bottom;
3024
3025 ComputeMargins(graphPtr);
3026 left = graphPtr->leftMargin.width + graphPtr->inset +
3027 graphPtr->plotBorderWidth;
3028 right = graphPtr->rightMargin.width + graphPtr->inset +
3029 graphPtr->plotBorderWidth;
3030 top = graphPtr->topMargin.height + graphPtr->inset +
3031 graphPtr->plotBorderWidth;
3032 bottom = graphPtr->bottomMargin.height + graphPtr->inset +
3033 graphPtr->plotBorderWidth;
3034
3035 /* Based upon the margins, calculate the space left for the graph. */
3036 width = graphPtr->width - (left + right);
3037 height = graphPtr->height - (top + bottom);
3038 if (width < 1) {
3039 width = 1;
3040 }
3041 if (height < 1) {
3042 height = 1;
3043 }
3044 graphPtr->left = left;
3045 graphPtr->right = left + width;
3046 graphPtr->bottom = top + height;
3047 graphPtr->top = top;
3048
3049 graphPtr->vOffset = top + graphPtr->padTop;
3050 graphPtr->vRange = height - PADDING(graphPtr->padY);
3051 graphPtr->hOffset = left + graphPtr->padLeft;
3052 graphPtr->hRange = width - PADDING(graphPtr->padX);
3053
3054 if (graphPtr->vRange < 1) {
3055 graphPtr->vRange = 1;
3056 }
3057 if (graphPtr->hRange < 1) {
3058 graphPtr->hRange = 1;
3059 }
3060 graphPtr->hScale = 1.0 / (double)graphPtr->hRange;
3061 graphPtr->vScale = 1.0 / (double)graphPtr->vRange;
3062
3063 /*
3064 * Calculate the placement of the graph title so it is centered within the
3065 * space provided for it in the top margin
3066 */
3067 titleY = graphPtr->titleTextStyle.height;
3068 graphPtr->titleY = (titleY / 2) + graphPtr->inset;
3069 graphPtr->titleX = (graphPtr->right + graphPtr->left) / 2;
3070
3071 }
3072
3073 /*
3074 * ----------------------------------------------------------------------
3075 *
3076 * ConfigureAxis --
3077 *
3078 * Configures axis attributes (font, line width, label, etc).
3079 *
3080 * Results:
3081 * The return value is a standard Tcl result.
3082 *
3083 * Side Effects:
3084 * Axis layout is deferred until the height and width of the
3085 * window are known.
3086 *
3087 * ----------------------------------------------------------------------
3088 */
3089
3090 static int
ConfigureAxis(graphPtr,axisPtr)3091 ConfigureAxis(graphPtr, axisPtr)
3092 Graph *graphPtr;
3093 Axis *axisPtr;
3094 {
3095 char errMsg[200];
3096
3097 /* Check the requested axis limits. Can't allow -min to be greater
3098 * than -max, or have undefined log scale limits. */
3099 if (((DEFINED(axisPtr->reqMin)) && (DEFINED(axisPtr->reqMax))) &&
3100 (axisPtr->reqMin >= axisPtr->reqMax)) {
3101 sprintf(errMsg, "impossible limits (min %g >= max %g) for axis \"%s\"",
3102 axisPtr->reqMin, axisPtr->reqMax, axisPtr->name);
3103 Tcl_AppendResult(graphPtr->interp, errMsg, (char *)NULL);
3104 /* Bad values, turn on axis auto-scaling */
3105 axisPtr->reqMin = axisPtr->reqMax = VALUE_UNDEFINED;
3106 return TCL_ERROR;
3107 }
3108 if ((axisPtr->logScale) && (DEFINED(axisPtr->reqMin)) &&
3109 (axisPtr->reqMin <= 0.0)) {
3110 sprintf(errMsg, "bad logscale limits (min=%g,max=%g) for axis \"%s\"",
3111 axisPtr->reqMin, axisPtr->reqMax, axisPtr->name);
3112 Tcl_AppendResult(graphPtr->interp, errMsg, (char *)NULL);
3113 /* Bad minimum value, turn on auto-scaling */
3114 axisPtr->reqMin = VALUE_UNDEFINED;
3115 return TCL_ERROR;
3116 }
3117 axisPtr->tickTextStyle.theta = FMOD(axisPtr->tickTextStyle.theta, 360.0);
3118 if (axisPtr->tickTextStyle.theta < 0.0) {
3119 axisPtr->tickTextStyle.theta += 360.0;
3120 }
3121 ResetTextStyles(graphPtr, axisPtr);
3122
3123 axisPtr->titleWidth = axisPtr->titleHeight = 0;
3124 if (axisPtr->title != NULL) {
3125 int w, h;
3126
3127 Blt_GetTextExtents(&axisPtr->titleTextStyle, axisPtr->title, &w, &h);
3128 axisPtr->titleWidth = (short int)w;
3129 axisPtr->titleHeight = (short int)h;
3130 }
3131
3132 /*
3133 * Don't bother to check what configuration options have changed.
3134 * Almost every option changes the size of the plotting area
3135 * (except for -color and -titlecolor), requiring the graph and
3136 * its contents to be completely redrawn.
3137 *
3138 * Recompute the scale and offset of the axis in case -min, -max
3139 * options have changed.
3140 */
3141 graphPtr->flags |= REDRAW_WORLD;
3142 if (!Blt_ConfigModified(configSpecs, graphPtr->interp, "-*color", "-background", "-bg",
3143 (char *)NULL)) {
3144 graphPtr->flags |= (MAP_WORLD | RESET_AXES);
3145 axisPtr->flags |= AXIS_DIRTY;
3146 }
3147 Blt_EventuallyRedrawGraph(graphPtr);
3148
3149 return TCL_OK;
3150 }
3151
3152 /*
3153 * ----------------------------------------------------------------------
3154 *
3155 * CreateAxis --
3156 *
3157 * Create and initialize a structure containing information to
3158 * display a graph axis.
3159 *
3160 * Results:
3161 * The return value is a standard Tcl result.
3162 *
3163 * ----------------------------------------------------------------------
3164 */
3165 static Axis *
CreateAxis(graphPtr,name,margin)3166 CreateAxis(graphPtr, name, margin)
3167 Graph *graphPtr;
3168 char *name; /* Identifier for axis. */
3169 int margin;
3170 {
3171 Axis *axisPtr;
3172 Blt_HashEntry *hPtr;
3173 int isNew;
3174
3175 if (name[0] == '-') {
3176 Tcl_AppendResult(graphPtr->interp, "name of axis \"", name,
3177 "\" can't start with a '-'", (char *)NULL);
3178 return NULL;
3179 }
3180 hPtr = Blt_CreateHashEntry(&graphPtr->axes.table, name, &isNew);
3181 if (!isNew) {
3182 axisPtr = (Axis *)Blt_GetHashValue(hPtr);
3183 if (!axisPtr->deletePending) {
3184 Tcl_AppendResult(graphPtr->interp, "axis \"", name,
3185 "\" already exists in \"", Tk_PathName(graphPtr->tkwin), "\"",
3186 (char *)NULL);
3187 return NULL;
3188 }
3189 axisPtr->deletePending = FALSE;
3190 } else {
3191 axisPtr = Blt_Calloc(1, sizeof(Axis));
3192 assert(axisPtr);
3193
3194 axisPtr->name = Blt_Strdup(name);
3195 axisPtr->hashPtr = hPtr;
3196 axisPtr->classUid = NULL;
3197 axisPtr->looseMin = axisPtr->looseMax = TICK_RANGE_TIGHT;
3198 axisPtr->reqNumMinorTicks = 2;
3199 axisPtr->scrollUnits = 10;
3200 axisPtr->showTicks = TRUE;
3201 axisPtr->reqMin = axisPtr->reqMax = VALUE_UNDEFINED;
3202 axisPtr->scrollMin = axisPtr->scrollMax = VALUE_UNDEFINED;
3203
3204 if ((graphPtr->classUid == bltBarElementUid) &&
3205 ((margin == MARGIN_TOP) || (margin == MARGIN_BOTTOM))) {
3206 axisPtr->reqStep = 1.0;
3207 axisPtr->reqNumMinorTicks = 0;
3208 }
3209 if ((margin == MARGIN_RIGHT) || (margin == MARGIN_TOP)) {
3210 axisPtr->hidden = TRUE;
3211 }
3212 Blt_InitTextStyle(&axisPtr->titleTextStyle);
3213 Blt_InitTextStyle(&axisPtr->limitsTextStyle);
3214 Blt_InitTextStyle(&axisPtr->tickTextStyle);
3215 axisPtr->tickLabels = Blt_ChainCreate();
3216 axisPtr->lineWidth = 1;
3217 axisPtr->tickTextStyle.padX.side1 = 2;
3218 axisPtr->tickTextStyle.padX.side2 = 2;
3219 Blt_SetHashValue(hPtr, axisPtr);
3220 }
3221 return axisPtr;
3222 }
3223
3224 static int
NameToAxis(graphPtr,name,axisPtrPtr)3225 NameToAxis(graphPtr, name, axisPtrPtr)
3226 Graph *graphPtr; /* Graph widget record. */
3227 char *name; /* Name of the axis to be searched for. */
3228 Axis **axisPtrPtr; /* (out) Pointer to found axis structure. */
3229 {
3230 Blt_HashEntry *hPtr;
3231
3232 hPtr = Blt_FindHashEntry(&graphPtr->axes.table, name);
3233 if (hPtr != NULL) {
3234 Axis *axisPtr;
3235
3236 axisPtr = (Axis *)Blt_GetHashValue(hPtr);
3237 if (!axisPtr->deletePending) {
3238 *axisPtrPtr = axisPtr;
3239 return TCL_OK;
3240 }
3241 }
3242 Tcl_AppendResult(graphPtr->interp, "can't find axis \"", name,
3243 "\" in \"", Tk_PathName(graphPtr->tkwin), "\"", (char *)NULL);
3244 *axisPtrPtr = NULL;
3245 return TCL_ERROR;
3246 }
3247
3248 static int
GetAxis(graphPtr,axisName,classUid,axisPtrPtr)3249 GetAxis(graphPtr, axisName, classUid, axisPtrPtr)
3250 Graph *graphPtr;
3251 char *axisName;
3252 Blt_Uid classUid;
3253 Axis **axisPtrPtr;
3254 {
3255 Axis *axisPtr;
3256
3257 if (NameToAxis(graphPtr, axisName, &axisPtr) != TCL_OK) {
3258 return TCL_ERROR;
3259 }
3260 if (classUid != NULL) {
3261 if ((axisPtr->refCount == 0) || (axisPtr->classUid == NULL)) {
3262 /* Set the axis type on the first use of it. */
3263 axisPtr->classUid = classUid;
3264 } else if (axisPtr->classUid != classUid) {
3265 Tcl_AppendResult(graphPtr->interp, "axis \"", axisName,
3266 "\" is already in use on an opposite ", axisPtr->classUid,
3267 "-axis", (char *)NULL);
3268 return TCL_ERROR;
3269 }
3270 axisPtr->refCount++;
3271 }
3272 *axisPtrPtr = axisPtr;
3273 return TCL_OK;
3274 }
3275
3276 static void
FreeAxis(graphPtr,axisPtr)3277 FreeAxis(graphPtr, axisPtr)
3278 Graph *graphPtr;
3279 Axis *axisPtr;
3280 {
3281 axisPtr->refCount--;
3282 if ((axisPtr->deletePending) && (axisPtr->refCount == 0)) {
3283 DestroyAxis(graphPtr, axisPtr);
3284 }
3285 }
3286
3287
3288 void
Blt_DestroyAxes(graphPtr)3289 Blt_DestroyAxes(graphPtr)
3290 Graph *graphPtr;
3291 {
3292 Blt_HashEntry *hPtr;
3293 Blt_HashSearch cursor;
3294 Axis *axisPtr;
3295 int i;
3296
3297 for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor);
3298 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
3299 axisPtr = (Axis *)Blt_GetHashValue(hPtr);
3300 axisPtr->hashPtr = NULL;
3301 DestroyAxis(graphPtr, axisPtr);
3302 }
3303 Blt_DeleteHashTable(&graphPtr->axes.table);
3304 for (i = 0; i < 4; i++) {
3305 Blt_ChainDestroy(graphPtr->axisChain[i]);
3306 }
3307 Blt_DeleteHashTable(&graphPtr->axes.tagTable);
3308 Blt_ChainDestroy(graphPtr->axes.displayList);
3309 }
3310
Blt_ConfigureAxes(graphPtr)3311 int Blt_ConfigureAxes(graphPtr)
3312 Graph *graphPtr;
3313 {
3314 Blt_HashEntry *hPtr;
3315 Blt_HashSearch cursor;
3316 Axis *axisPtr;
3317
3318 for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor);
3319 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
3320 axisPtr = (Axis *)Blt_GetHashValue(hPtr);
3321 ConfigureAxis(graphPtr, axisPtr);
3322 }
3323 return TCL_OK;
3324 }
3325
3326 int
Blt_DefaultAxes(graphPtr)3327 Blt_DefaultAxes(graphPtr)
3328 Graph *graphPtr;
3329 {
3330 register int i;
3331 Axis *axisPtr;
3332 Blt_Chain *chainPtr;
3333 static char *axisNames[4] = { "x", "y", "x2", "y2" } ;
3334 int flags;
3335
3336 flags = Blt_GraphType(graphPtr);
3337 for (i = 0; i < 4; i++) {
3338 chainPtr = Blt_ChainCreate();
3339 graphPtr->axisChain[i] = chainPtr;
3340
3341 /* Create a default axis for each chain. */
3342 axisPtr = CreateAxis(graphPtr, axisNames[i], i);
3343 if (axisPtr == NULL) {
3344 return TCL_ERROR;
3345 }
3346 axisPtr->refCount = 1; /* Default axes are assumed in use. */
3347 axisPtr->classUid = (i & 1) ? bltYAxisUid : bltXAxisUid;
3348 axisPtr->flags |= AXIS_ONSCREEN;
3349
3350 /*
3351 * Blt_ConfigureWidgetComponent creates a temporary child window
3352 * by the name of the axis. It's used so that the Tk routines
3353 * that access the X resource database can describe a single
3354 * component and not the entire graph.
3355 */
3356 if (Blt_ConfigureWidgetComponent(graphPtr->interp, graphPtr->tkwin,
3357 axisPtr->name, "Axis", configSpecs, 0, (char **)NULL,
3358 (char *)axisPtr, flags) != TCL_OK) {
3359 return TCL_ERROR;
3360 }
3361 if (ConfigureAxis(graphPtr, axisPtr) != TCL_OK) {
3362 return TCL_ERROR;
3363 }
3364 axisPtr->linkPtr = Blt_ChainAppend(chainPtr, axisPtr);
3365 axisPtr->chainPtr = chainPtr;
3366 }
3367 return TCL_OK;
3368 }
3369
3370
3371 /*----------------------------------------------------------------------
3372 *
3373 * BindOp --
3374 *
3375 * .g axis bind axisName sequence command
3376 *
3377 *----------------------------------------------------------------------
3378 */
3379 static int
BindOp(graphPtr,axisPtr,argc,argv)3380 BindOp(graphPtr, axisPtr, argc, argv)
3381 Graph *graphPtr;
3382 Axis *axisPtr;
3383 int argc;
3384 char **argv;
3385 {
3386 Tcl_Interp *interp = graphPtr->interp;
3387
3388 return Blt_ConfigureBindings(interp, graphPtr->bindTable,
3389 Blt_MakeAxisTag(graphPtr, axisPtr->name), argc, argv);
3390 }
3391
3392 /*
3393 * ----------------------------------------------------------------------
3394 *
3395 * CgetOp --
3396 *
3397 * Queries axis attributes (font, line width, label, etc).
3398 *
3399 * Results:
3400 * Return value is a standard Tcl result. If querying configuration
3401 * values, interp->result will contain the results.
3402 *
3403 * ----------------------------------------------------------------------
3404 */
3405 /* ARGSUSED */
3406 static int
CgetOp(graphPtr,axisPtr,argc,argv)3407 CgetOp(graphPtr, axisPtr, argc, argv)
3408 Graph *graphPtr;
3409 Axis *axisPtr;
3410 int argc; /* Not used. */
3411 char *argv[];
3412 {
3413 return Tk_ConfigureValue(graphPtr->interp, graphPtr->tkwin, configSpecs,
3414 (char *)axisPtr, argv[0], Blt_GraphType(graphPtr));
3415 }
3416
3417 /*
3418 * ----------------------------------------------------------------------
3419 *
3420 * ConfigureOp --
3421 *
3422 * Queries or resets axis attributes (font, line width, label, etc).
3423 *
3424 * Results:
3425 * Return value is a standard Tcl result. If querying configuration
3426 * values, interp->result will contain the results.
3427 *
3428 * Side Effects:
3429 * Axis resources are possibly allocated (GC, font). Axis layout is
3430 * deferred until the height and width of the window are known.
3431 *
3432 * ----------------------------------------------------------------------
3433 */
3434 static int
ConfigureOp(graphPtr,axisPtr,argc,argv)3435 ConfigureOp(graphPtr, axisPtr, argc, argv)
3436 Graph *graphPtr;
3437 Axis *axisPtr;
3438 int argc;
3439 CONST char *argv[];
3440 {
3441 int flags;
3442
3443 flags = TK_CONFIG_ARGV_ONLY | Blt_GraphType(graphPtr);
3444 if (argc == 0) {
3445 return Tk_ConfigureInfo(graphPtr->interp, graphPtr->tkwin, configSpecs,
3446 (char *)axisPtr, (char *)NULL, flags);
3447 } else if (argc == 1) {
3448 return Tk_ConfigureInfo(graphPtr->interp, graphPtr->tkwin, configSpecs,
3449 (char *)axisPtr, argv[0], flags);
3450 }
3451 if (Tk_ConfigureWidget(graphPtr->interp, graphPtr->tkwin, configSpecs,
3452 argc, argv, (char *)axisPtr, flags) != TCL_OK) {
3453 return TCL_ERROR;
3454 }
3455 if (ConfigureAxis(graphPtr, axisPtr) != TCL_OK) {
3456 return TCL_ERROR;
3457 }
3458 if (axisPtr->flags & AXIS_ONSCREEN) {
3459 if (!Blt_ConfigModified(configSpecs, graphPtr->interp, "-*color", "-background", "-bg",
3460 (char *)NULL)) {
3461 graphPtr->flags |= REDRAW_BACKING_STORE;
3462 }
3463 graphPtr->flags |= DRAW_MARGINS;
3464 Blt_EventuallyRedrawGraph(graphPtr);
3465 }
3466 return TCL_OK;
3467 }
3468
3469
3470 /*
3471 * ----------------------------------------------------------------------
3472 *
3473 * GetOp --
3474 *
3475 * Returns the name of the picked axis (using the axis
3476 * bind operation). Right now, the only name accepted is
3477 * "current".
3478 *
3479 * Results:
3480 * A standard Tcl result. The interpreter result will contain
3481 * the name of the axis.
3482 *
3483 * ----------------------------------------------------------------------
3484 */
3485 /*ARGSUSED*/
3486 static int
GetOp(graphPtr,argc,argv)3487 GetOp(graphPtr, argc, argv)
3488 Graph *graphPtr;
3489 int argc; /* Not used. */
3490 char *argv[];
3491 {
3492 Tcl_Interp *interp = graphPtr->interp;
3493 register Axis *axisPtr;
3494
3495 axisPtr = (Axis *)Blt_GetCurrentItem(graphPtr->bindTable);
3496 /* Report only on axes. */
3497 if ((axisPtr != NULL) &&
3498 ((axisPtr->classUid == bltXAxisUid) ||
3499 (axisPtr->classUid == bltYAxisUid) ||
3500 (axisPtr->classUid == NULL))) {
3501 char c;
3502
3503 c = argv[3][0];
3504 if ((c == 'c') && (strcmp(argv[3], "current") == 0)) {
3505 Tcl_SetResult(interp, axisPtr->name, TCL_VOLATILE);
3506 } else if ((c == 'd') && (strcmp(argv[3], "detail") == 0)) {
3507 Tcl_SetResult(interp, axisPtr->detail, TCL_VOLATILE);
3508 }
3509 }
3510 return TCL_OK;
3511 }
3512
3513 /*
3514 *--------------------------------------------------------------
3515 *
3516 * LimitsOp --
3517 *
3518 * This procedure returns a string representing the axis limits
3519 * of the graph. The format of the string is { left top right bottom}.
3520 *
3521 * Results:
3522 * Always returns TCL_OK. The interp->result field is
3523 * a list of the graph axis limits.
3524 *
3525 *--------------------------------------------------------------
3526 */
3527 /*ARGSUSED*/
3528 static int
LimitsOp(graphPtr,axisPtr,argc,argv)3529 LimitsOp(graphPtr, axisPtr, argc, argv)
3530 Graph *graphPtr;
3531 Axis *axisPtr;
3532 int argc; /* Not used. */
3533 char **argv; /* Not used. */
3534
3535 {
3536 Tcl_Interp *interp = graphPtr->interp;
3537 double min, max;
3538
3539 if (graphPtr->flags & RESET_AXES) {
3540 Blt_ResetAxes(graphPtr);
3541 }
3542 if (axisPtr->logScale) {
3543 min = EXP10(axisPtr->axisRange.min);
3544 max = EXP10(axisPtr->axisRange.max);
3545 } else {
3546 min = axisPtr->axisRange.min;
3547 max = axisPtr->axisRange.max;
3548 }
3549 Tcl_AppendElement(interp, Blt_Dtoa(interp, min));
3550 Tcl_AppendElement(interp, Blt_Dtoa(interp, max));
3551 return TCL_OK;
3552 }
3553
3554 /*
3555 * ----------------------------------------------------------------------
3556 *
3557 * InvTransformOp --
3558 *
3559 * Maps the given window coordinate into an axis-value.
3560 *
3561 * Results:
3562 * Returns a standard Tcl result. interp->result contains
3563 * the axis value. If an error occurred, TCL_ERROR is returned
3564 * and interp->result will contain an error message.
3565 *
3566 * ----------------------------------------------------------------------
3567 */
3568 /*ARGSUSED*/
3569 static int
InvTransformOp(graphPtr,axisPtr,argc,argv)3570 InvTransformOp(graphPtr, axisPtr, argc, argv)
3571 Graph *graphPtr;
3572 Axis *axisPtr;
3573 int argc; /* Not used. */
3574 char **argv;
3575 {
3576 int x; /* Integer window coordinate*/
3577 double y; /* Real graph coordinate */
3578
3579 if (graphPtr->flags & RESET_AXES) {
3580 Blt_ResetAxes(graphPtr);
3581 }
3582 if (Tcl_GetInt(graphPtr->interp, argv[0], &x) != TCL_OK) {
3583 return TCL_ERROR;
3584 }
3585 /*
3586 * Is the axis vertical or horizontal?
3587 *
3588 * Check the site where the axis was positioned. If the axis is
3589 * virtual, all we have to go on is how it was mapped to an
3590 * element (using either -mapx or -mapy options).
3591 */
3592 if (AxisIsHorizontal(graphPtr, axisPtr)) {
3593 y = Blt_InvHMap(graphPtr, axisPtr, (double)x);
3594 } else {
3595 y = Blt_InvVMap(graphPtr, axisPtr, (double)x);
3596 }
3597 Tcl_AppendElement(graphPtr->interp, Blt_Dtoa(graphPtr->interp, y));
3598 return TCL_OK;
3599 }
3600
3601 /*
3602 * ----------------------------------------------------------------------
3603 *
3604 * TransformOp --
3605 *
3606 * Maps the given axis-value to a window coordinate.
3607 *
3608 * Results:
3609 * Returns a standard Tcl result. interp->result contains
3610 * the window coordinate. If an error occurred, TCL_ERROR
3611 * is returned and interp->result will contain an error
3612 * message.
3613 *
3614 * ----------------------------------------------------------------------
3615 */
3616 /*ARGSUSED*/
3617 static int
TransformOp(graphPtr,axisPtr,argc,argv)3618 TransformOp(graphPtr, axisPtr, argc, argv)
3619 Graph *graphPtr;
3620 Axis *axisPtr; /* Axis */
3621 int argc; /* Not used. */
3622 char **argv;
3623 {
3624 double x;
3625
3626 if (graphPtr->flags & RESET_AXES) {
3627 Blt_ResetAxes(graphPtr);
3628 }
3629 if (Tcl_ExprDouble(graphPtr->interp, argv[0], &x) != TCL_OK) {
3630 return TCL_ERROR;
3631 }
3632 if (AxisIsHorizontal(graphPtr, axisPtr)) {
3633 x = Blt_HMap(graphPtr, axisPtr, x);
3634 } else {
3635 x = Blt_VMap(graphPtr, axisPtr, x);
3636 }
3637 Tcl_SetResult(graphPtr->interp, Blt_Itoa((int)x), TCL_VOLATILE);
3638 return TCL_OK;
3639 }
3640
3641 /*
3642 *--------------------------------------------------------------
3643 *
3644 * UseOp --
3645 *
3646 * Changes the virtual axis used by the logical axis.
3647 *
3648 * Results:
3649 * A standard Tcl result. If the named axis doesn't exist
3650 * an error message is put in interp->result.
3651 *
3652 * .g xaxis use "abc def gah"
3653 * .g xaxis use [lappend abc [.g axis use]]
3654 *
3655 *--------------------------------------------------------------
3656 */
3657 /*ARGSUSED*/
3658 static int
UseOp(graphPtr,axisPtr,argc,argv)3659 UseOp(graphPtr, axisPtr, argc, argv)
3660 Graph *graphPtr;
3661 Axis *axisPtr; /* Not used. */
3662 int argc;
3663 char **argv;
3664 {
3665 Blt_Chain *chainPtr;
3666 int nNames;
3667 char **names;
3668 Blt_ChainLink *linkPtr;
3669 int i;
3670 Blt_Uid classUid;
3671 int margin;
3672
3673 /* TODO: fix bug where "$g xaxis x2" leaves x unavailable. */
3674 margin = (int)argv[-1];
3675 chainPtr = graphPtr->margins[margin].axes;
3676 if (argc == 0) {
3677 for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr!= NULL;
3678 linkPtr = Blt_ChainNextLink(linkPtr)) {
3679 axisPtr = Blt_ChainGetValue(linkPtr);
3680 Tcl_AppendElement(graphPtr->interp, axisPtr->name);
3681 }
3682 return TCL_OK;
3683 }
3684 if ((margin == MARGIN_BOTTOM) || (margin == MARGIN_TOP)) {
3685 classUid = (graphPtr->inverted) ? bltYAxisUid : bltXAxisUid;
3686 } else {
3687 classUid = (graphPtr->inverted) ? bltXAxisUid : bltYAxisUid;
3688 }
3689 if (Tcl_SplitList(graphPtr->interp, argv[0], &nNames, &names) != TCL_OK) {
3690 return TCL_ERROR;
3691 }
3692 for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr!= NULL;
3693 linkPtr = Blt_ChainNextLink(linkPtr)) {
3694 axisPtr = Blt_ChainGetValue(linkPtr);
3695 axisPtr->linkPtr = NULL;
3696 axisPtr->flags &= ~AXIS_ONSCREEN;
3697 /* Clear the axis type if it's not currently used.*/
3698 if (axisPtr->refCount == 0) {
3699 axisPtr->classUid = NULL;
3700 }
3701 }
3702 Blt_ChainReset(chainPtr);
3703 for (i = 0; i < nNames; i++) {
3704 if (NameToAxis(graphPtr, names[i], &axisPtr) != TCL_OK) {
3705 Blt_Free(names);
3706 return TCL_ERROR;
3707 }
3708 if (axisPtr->classUid == NULL) {
3709 axisPtr->classUid = classUid;
3710 } else if (axisPtr->classUid != classUid) {
3711 Tcl_AppendResult(graphPtr->interp, "wrong type axis \"",
3712 axisPtr->name, "\": can't use ", classUid, " type axis.",
3713 (char *)NULL);
3714 Blt_Free(names);
3715 return TCL_ERROR;
3716 }
3717 if (axisPtr->linkPtr != NULL) {
3718 /* Move the axis from the old margin's "use" list to the new. */
3719 Blt_ChainUnlinkLink(axisPtr->chainPtr, axisPtr->linkPtr);
3720 Blt_ChainAppendLink(chainPtr, axisPtr->linkPtr);
3721 } else {
3722 axisPtr->linkPtr = Blt_ChainAppend(chainPtr, axisPtr);
3723 }
3724 axisPtr->chainPtr = chainPtr;
3725 axisPtr->flags |= AXIS_ONSCREEN;
3726 }
3727 graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED | RESET_AXES);
3728 /* When any axis changes, we need to layout the entire graph. */
3729 graphPtr->flags |= (MAP_WORLD | REDRAW_WORLD);
3730 Blt_EventuallyRedrawGraph(graphPtr);
3731
3732 Blt_Free(names);
3733 return TCL_OK;
3734 }
3735
3736 /*
3737 * ----------------------------------------------------------------------
3738 *
3739 * CreateVirtualOp --
3740 *
3741 * Creates a new axis.
3742 *
3743 * Results:
3744 * Returns a standard Tcl result.
3745 *
3746 * ----------------------------------------------------------------------
3747 */
3748 /*ARGSUSED*/
3749 static int
CreateVirtualOp(graphPtr,argc,argv)3750 CreateVirtualOp(graphPtr, argc, argv)
3751 Graph *graphPtr;
3752 int argc;
3753 char **argv;
3754 {
3755 Axis *axisPtr;
3756 int flags;
3757
3758 axisPtr = CreateAxis(graphPtr, argv[3], MARGIN_NONE);
3759 if (axisPtr == NULL) {
3760 return TCL_ERROR;
3761 }
3762 flags = Blt_GraphType(graphPtr);
3763 if (Blt_ConfigureWidgetComponent(graphPtr->interp, graphPtr->tkwin,
3764 axisPtr->name, "Axis", configSpecs, argc - 4, argv + 4,
3765 (char *)axisPtr, flags) != TCL_OK) {
3766 goto error;
3767 }
3768 if (ConfigureAxis(graphPtr, axisPtr) != TCL_OK) {
3769 goto error;
3770 }
3771 Tcl_SetResult(graphPtr->interp, axisPtr->name, TCL_VOLATILE);
3772 return TCL_OK;
3773 error:
3774 DestroyAxis(graphPtr, axisPtr);
3775 return TCL_ERROR;
3776 }
3777
3778 /*----------------------------------------------------------------------
3779 *
3780 * BindVirtualOp --
3781 *
3782 * .g axis bind axisName sequence command
3783 *
3784 *----------------------------------------------------------------------
3785 */
3786 /*ARGSUSED*/
3787 static int
BindVirtualOp(graphPtr,argc,argv)3788 BindVirtualOp(graphPtr, argc, argv)
3789 Graph *graphPtr;
3790 int argc;
3791 char **argv;
3792 {
3793 Tcl_Interp *interp = graphPtr->interp;
3794
3795 if (argc == 3) {
3796 Blt_HashEntry *hPtr;
3797 Blt_HashSearch cursor;
3798 char *tagName;
3799
3800 for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.tagTable, &cursor);
3801 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
3802 tagName = Blt_GetHashKey(&graphPtr->axes.tagTable, hPtr);
3803 Tcl_AppendElement(interp, tagName);
3804 }
3805 return TCL_OK;
3806 }
3807 return Blt_ConfigureBindings(interp, graphPtr->bindTable,
3808 Blt_MakeAxisTag(graphPtr, argv[3]), argc - 4, argv + 4);
3809 }
3810
3811
3812 /*
3813 * ----------------------------------------------------------------------
3814 *
3815 * CgetVirtualOp --
3816 *
3817 * Queries axis attributes (font, line width, label, etc).
3818 *
3819 * Results:
3820 * Return value is a standard Tcl result. If querying configuration
3821 * values, interp->result will contain the results.
3822 *
3823 * ----------------------------------------------------------------------
3824 */
3825 /* ARGSUSED */
3826 static int
CgetVirtualOp(graphPtr,argc,argv)3827 CgetVirtualOp(graphPtr, argc, argv)
3828 Graph *graphPtr;
3829 int argc;
3830 char *argv[];
3831 {
3832 Axis *axisPtr;
3833
3834 if (NameToAxis(graphPtr, argv[3], &axisPtr) != TCL_OK) {
3835 return TCL_ERROR;
3836 }
3837 return CgetOp(graphPtr, axisPtr, argc - 4, argv + 4);
3838 }
3839
3840 /*
3841 * ----------------------------------------------------------------------
3842 *
3843 * ConfigureVirtualOp --
3844 *
3845 * Queries or resets axis attributes (font, line width, label, etc).
3846 *
3847 * Results:
3848 * Return value is a standard Tcl result. If querying configuration
3849 * values, interp->result will contain the results.
3850 *
3851 * Side Effects:
3852 * Axis resources are possibly allocated (GC, font). Axis layout is
3853 * deferred until the height and width of the window are known.
3854 *
3855 * ----------------------------------------------------------------------
3856 */
3857 static int
ConfigureVirtualOp(graphPtr,argc,argv)3858 ConfigureVirtualOp(graphPtr, argc, argv)
3859 Graph *graphPtr;
3860 int argc;
3861 CONST char *argv[];
3862 {
3863 Axis *axisPtr;
3864 int nNames, nOpts;
3865 CONST char **options;
3866 register int i;
3867
3868 /* Figure out where the option value pairs begin */
3869 argc -= 3;
3870 argv += 3;
3871 for (i = 0; i < argc; i++) {
3872 if (argv[i][0] == '-') {
3873 break;
3874 }
3875 if (NameToAxis(graphPtr, (char *)argv[i], &axisPtr) != TCL_OK) {
3876 return TCL_ERROR;
3877 }
3878 }
3879 nNames = i; /* Number of pen names specified */
3880 nOpts = argc - i; /* Number of options specified */
3881 options = argv + i; /* Start of options in argv */
3882
3883 for (i = 0; i < nNames; i++) {
3884 if (NameToAxis(graphPtr, (char *)argv[i], &axisPtr) != TCL_OK) {
3885 return TCL_ERROR;
3886 }
3887 if (ConfigureOp(graphPtr, axisPtr, nOpts, options) != TCL_OK) {
3888 break;
3889 }
3890 }
3891 if (i < nNames) {
3892 return TCL_ERROR;
3893 }
3894 return TCL_OK;
3895 }
3896
3897 /*
3898 * ----------------------------------------------------------------------
3899 *
3900 * DeleteVirtualOp --
3901 *
3902 * Deletes one or more axes. The actual removal may be deferred
3903 * until the axis is no longer used by any element. The axis
3904 * can't be referenced by its name any longer and it may be
3905 * recreated.
3906 *
3907 * Results:
3908 * Returns a standard Tcl result.
3909 *
3910 * ----------------------------------------------------------------------
3911 */
3912 /*ARGSUSED*/
3913 static int
DeleteVirtualOp(graphPtr,argc,argv)3914 DeleteVirtualOp(graphPtr, argc, argv)
3915 Graph *graphPtr;
3916 int argc;
3917 char **argv;
3918 {
3919 register int i;
3920 Axis *axisPtr;
3921
3922 for (i = 3; i < argc; i++) {
3923 if (NameToAxis(graphPtr, argv[i], &axisPtr) != TCL_OK) {
3924 return TCL_ERROR;
3925 }
3926 axisPtr->deletePending = TRUE;
3927 if (axisPtr->refCount == 0) {
3928 DestroyAxis(graphPtr, axisPtr);
3929 }
3930 }
3931 return TCL_OK;
3932 }
3933
3934 /*
3935 * ----------------------------------------------------------------------
3936 *
3937 * InvTransformVirtualOp --
3938 *
3939 * Maps the given window coordinate into an axis-value.
3940 *
3941 * Results:
3942 * Returns a standard Tcl result. interp->result contains
3943 * the axis value. If an error occurred, TCL_ERROR is returned
3944 * and interp->result will contain an error message.
3945 *
3946 * ----------------------------------------------------------------------
3947 */
3948 /*ARGSUSED*/
3949 static int
InvTransformVirtualOp(graphPtr,argc,argv)3950 InvTransformVirtualOp(graphPtr, argc, argv)
3951 Graph *graphPtr;
3952 int argc; /* Not used. */
3953 char **argv;
3954 {
3955 Axis *axisPtr;
3956
3957 if (NameToAxis(graphPtr, argv[3], &axisPtr) != TCL_OK) {
3958 return TCL_ERROR;
3959 }
3960 return InvTransformOp(graphPtr, axisPtr, argc - 4, argv + 4);
3961 }
3962
3963 /*
3964 *--------------------------------------------------------------
3965 *
3966 * LimitsVirtualOp --
3967 *
3968 * This procedure returns a string representing the axis limits
3969 * of the graph. The format of the string is { left top right bottom}.
3970 *
3971 * Results:
3972 * Always returns TCL_OK. The interp->result field is
3973 * a list of the graph axis limits.
3974 *
3975 *--------------------------------------------------------------
3976 */
3977 /*ARGSUSED*/
3978 static int
LimitsVirtualOp(graphPtr,argc,argv)3979 LimitsVirtualOp(graphPtr, argc, argv)
3980 Graph *graphPtr;
3981 int argc; /* Not used. */
3982 char **argv; /* Not used. */
3983
3984 {
3985 Axis *axisPtr;
3986
3987 if (NameToAxis(graphPtr, argv[3], &axisPtr) != TCL_OK) {
3988 return TCL_ERROR;
3989 }
3990 return LimitsOp(graphPtr, axisPtr, argc - 4, argv + 4);
3991 }
3992
3993 /*
3994 * ----------------------------------------------------------------------
3995 *
3996 * NamesVirtualOp --
3997 *
3998 * Return a list of the names of all the axes.
3999 *
4000 * Results:
4001 * Returns a standard Tcl result.
4002 *
4003 * ----------------------------------------------------------------------
4004 */
4005
4006 /*ARGSUSED*/
4007 static int
NamesVirtualOp(graphPtr,argc,argv)4008 NamesVirtualOp(graphPtr, argc, argv)
4009 Graph *graphPtr;
4010 int argc; /* Not used. */
4011 char **argv; /* Not used. */
4012 {
4013 Blt_HashEntry *hPtr;
4014 Blt_HashSearch cursor;
4015 Axis *axisPtr;
4016 register int i;
4017
4018 for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor);
4019 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
4020 axisPtr = (Axis *)Blt_GetHashValue(hPtr);
4021 if (axisPtr->deletePending) {
4022 continue;
4023 }
4024 if (argc == 3) {
4025 Tcl_AppendElement(graphPtr->interp, axisPtr->name);
4026 continue;
4027 }
4028 for (i = 3; i < argc; i++) {
4029 if (Tcl_StringMatch(axisPtr->name, argv[i])) {
4030 Tcl_AppendElement(graphPtr->interp, axisPtr->name);
4031 break;
4032 }
4033 }
4034 }
4035 return TCL_OK;
4036 }
4037
4038 /*
4039 * ----------------------------------------------------------------------
4040 *
4041 * TransformVirtualOp --
4042 *
4043 * Maps the given axis-value to a window coordinate.
4044 *
4045 * Results:
4046 * Returns a standard Tcl result. interp->result contains
4047 * the window coordinate. If an error occurred, TCL_ERROR
4048 * is returned and interp->result will contain an error
4049 * message.
4050 *
4051 * ----------------------------------------------------------------------
4052 */
4053 /*ARGSUSED*/
4054 static int
TransformVirtualOp(graphPtr,argc,argv)4055 TransformVirtualOp(graphPtr, argc, argv)
4056 Graph *graphPtr;
4057 int argc; /* Not used. */
4058 char **argv;
4059 {
4060 Axis *axisPtr;
4061
4062 if (NameToAxis(graphPtr, argv[3], &axisPtr) != TCL_OK) {
4063 return TCL_ERROR;
4064 }
4065 return TransformOp(graphPtr, axisPtr, argc - 4, argv + 4);
4066 }
4067
4068 static int
ViewOp(graphPtr,argc,argv)4069 ViewOp(graphPtr, argc, argv)
4070 Graph *graphPtr;
4071 int argc;
4072 char **argv;
4073 {
4074 Axis *axisPtr;
4075 Tcl_Interp *interp = graphPtr->interp;
4076 double axisOffset, scrollUnits;
4077 double fract;
4078 double viewMin, viewMax, worldMin, worldMax;
4079 double viewWidth, worldWidth;
4080
4081 if (NameToAxis(graphPtr, argv[3], &axisPtr) != TCL_OK) {
4082 return TCL_ERROR;
4083 }
4084 worldMin = axisPtr->valueRange.min;
4085 worldMax = axisPtr->valueRange.max;
4086 /* Override data dimensions with user-selected limits. */
4087 if (DEFINED(axisPtr->scrollMin)) {
4088 worldMin = axisPtr->scrollMin;
4089 }
4090 if (DEFINED(axisPtr->scrollMax)) {
4091 worldMax = axisPtr->scrollMax;
4092 }
4093 viewMin = axisPtr->min;
4094 viewMax = axisPtr->max;
4095 /* Bound the view within scroll region. */
4096 if (viewMin < worldMin) {
4097 viewMin = worldMin;
4098 }
4099 if (viewMax > worldMax) {
4100 viewMax = worldMax;
4101 }
4102 if (axisPtr->logScale) {
4103 worldMin = log10(worldMin);
4104 worldMax = log10(worldMax);
4105 viewMin = log10(viewMin);
4106 viewMax = log10(viewMax);
4107 }
4108 worldWidth = worldMax - worldMin;
4109 viewWidth = viewMax - viewMin;
4110
4111 /* Unlike horizontal axes, vertical axis values run opposite of
4112 * the scrollbar first/last values. So instead of pushing the
4113 * axis minimum around, we move the maximum instead. */
4114
4115 if (AxisIsHorizontal(graphPtr, axisPtr) != axisPtr->descending) {
4116 axisOffset = viewMin - worldMin;
4117 scrollUnits = (double)axisPtr->scrollUnits * graphPtr->hScale;
4118 } else {
4119 axisOffset = worldMax - viewMax;
4120 scrollUnits = (double)axisPtr->scrollUnits * graphPtr->vScale;
4121 }
4122 if (argc == 4) {
4123 /* Note: Bound the fractions between 0.0 and 1.0 to support
4124 * "canvas"-style scrolling. */
4125 fract = axisOffset / worldWidth;
4126 Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0)));
4127 fract = (axisOffset + viewWidth) / worldWidth;
4128 Tcl_AppendElement(interp, Blt_Dtoa(interp, CLAMP(fract, 0.0, 1.0)));
4129 return TCL_OK;
4130 }
4131 fract = axisOffset / worldWidth;
4132 if (GetAxisScrollInfo(interp, argc - 4, argv + 4, &fract,
4133 viewWidth / worldWidth, scrollUnits) != TCL_OK) {
4134 return TCL_ERROR;
4135 }
4136 if (AxisIsHorizontal(graphPtr, axisPtr) != axisPtr->descending) {
4137 axisPtr->reqMin = (fract * worldWidth) + worldMin;
4138 axisPtr->reqMax = axisPtr->reqMin + viewWidth;
4139 } else {
4140 axisPtr->reqMax = worldMax - (fract * worldWidth);
4141 axisPtr->reqMin = axisPtr->reqMax - viewWidth;
4142 }
4143 if (axisPtr->logScale) {
4144 axisPtr->reqMin = EXP10(axisPtr->reqMin);
4145 axisPtr->reqMax = EXP10(axisPtr->reqMax);
4146 }
4147 graphPtr->flags |= (GET_AXIS_GEOMETRY | LAYOUT_NEEDED | RESET_AXES);
4148 Blt_EventuallyRedrawGraph(graphPtr);
4149 return TCL_OK;
4150 }
4151
4152 int
Blt_VirtualAxisOp(graphPtr,interp,argc,argv)4153 Blt_VirtualAxisOp(graphPtr, interp, argc, argv)
4154 Graph *graphPtr;
4155 Tcl_Interp *interp;
4156 int argc;
4157 char **argv;
4158 {
4159 Blt_Op proc;
4160 int result;
4161 static Blt_OpSpec axisOps[] =
4162 {
4163 {"bind", 1, (Blt_Op)BindVirtualOp, 3, 6,
4164 "axisName sequence command",},
4165 {"cget", 2, (Blt_Op)CgetVirtualOp, 5, 5, "axisName option",},
4166 {"configure", 2, (Blt_Op)ConfigureVirtualOp, 4, 0,
4167 "axisName ?axisName?... ?option value?...",},
4168 {"create", 2, (Blt_Op)CreateVirtualOp, 4, 0,
4169 "axisName ?option value?...",},
4170 {"delete", 1, (Blt_Op)DeleteVirtualOp, 3, 0, "?axisName?...",},
4171 {"get", 1, (Blt_Op)GetOp, 4, 4, "name",},
4172 {"invtransform", 1, (Blt_Op)InvTransformVirtualOp, 5, 5,
4173 "axisName value",},
4174 {"limits", 1, (Blt_Op)LimitsVirtualOp, 4, 4, "axisName",},
4175 {"names", 1, (Blt_Op)NamesVirtualOp, 3, 0, "?pattern?...",},
4176 {"transform", 1, (Blt_Op)TransformVirtualOp, 5, 5, "axisName value",},
4177 {"view", 1, (Blt_Op)ViewOp, 4, 7,
4178 "axisName ?moveto fract? ?scroll number what?",},
4179 };
4180 static int nAxisOps = sizeof(axisOps) / sizeof(Blt_OpSpec);
4181
4182 proc = Blt_GetOp(interp, nAxisOps, axisOps, BLT_OP_ARG2, argc, argv, 0);
4183 if (proc == NULL) {
4184 return TCL_ERROR;
4185 }
4186 result = (*proc) (graphPtr, argc, argv);
4187 return result;
4188 }
4189
4190 int
Blt_AxisOp(graphPtr,margin,argc,argv)4191 Blt_AxisOp(graphPtr, margin, argc, argv)
4192 Graph *graphPtr;
4193 int margin;
4194 int argc;
4195 char **argv;
4196 {
4197 int result;
4198 Blt_Op proc;
4199 Axis *axisPtr;
4200 static Blt_OpSpec axisOps[] =
4201 {
4202 {"bind", 1, (Blt_Op)BindOp, 2, 5, "sequence command",},
4203 {"cget", 2, (Blt_Op)CgetOp, 4, 4, "option",},
4204 {"configure", 2, (Blt_Op)ConfigureOp, 3, 0, "?option value?...",},
4205 {"invtransform", 1, (Blt_Op)InvTransformOp, 4, 4, "value",},
4206 {"limits", 1, (Blt_Op)LimitsOp, 3, 3, "",},
4207 {"transform", 1, (Blt_Op)TransformOp, 4, 4, "value",},
4208 {"use", 1, (Blt_Op)UseOp, 3, 4, "?axisName?",},
4209 };
4210 static int nAxisOps = sizeof(axisOps) / sizeof(Blt_OpSpec);
4211
4212 proc = Blt_GetOp(graphPtr->interp, nAxisOps, axisOps, BLT_OP_ARG2,
4213 argc, argv, 0);
4214 if (proc == NULL) {
4215 return TCL_ERROR;
4216 }
4217 if (proc == UseOp) {
4218 argv[2] = (char *)margin; /* Hack. Slide a reference to the margin in
4219 * the argument list. Needed only for UseOp.
4220 */
4221 result = (*proc)(graphPtr, NULL, argc - 3, argv +3);
4222 } else {
4223 axisPtr = Blt_GetFirstAxis(graphPtr->margins[margin].axes);
4224 if (axisPtr == NULL) {
4225 Tcl_AppendResult(graphPtr->interp, "bad axis", (char *)NULL);
4226 return TCL_ERROR;
4227 }
4228 result = (*proc)(graphPtr, axisPtr, argc - 3, argv + 3);
4229 }
4230 return result;
4231 }
4232
4233 void
Blt_MapAxes(graphPtr)4234 Blt_MapAxes(graphPtr)
4235 Graph *graphPtr;
4236 {
4237 Axis *axisPtr;
4238 Blt_Chain *chainPtr;
4239 Blt_ChainLink *linkPtr;
4240 register int margin;
4241 int offset;
4242
4243 for (margin = 0; margin < 4; margin++) {
4244 chainPtr = graphPtr->margins[margin].axes;
4245 offset = 0;
4246 for (linkPtr = Blt_ChainFirstLink(chainPtr); linkPtr != NULL;
4247 linkPtr = Blt_ChainNextLink(linkPtr)) {
4248 axisPtr = Blt_ChainGetValue(linkPtr);
4249 if ((!axisPtr->hidden) && (axisPtr->flags & AXIS_ONSCREEN)) {
4250 MapAxis(graphPtr, axisPtr, offset, margin);
4251 if (AxisIsHorizontal(graphPtr, axisPtr)) {
4252 offset += axisPtr->height;
4253 } else {
4254 offset += axisPtr->width;
4255 }
4256 }
4257 }
4258 }
4259 }
4260
4261 void
Blt_DrawAxes(graphPtr,drawable)4262 Blt_DrawAxes(graphPtr, drawable)
4263 Graph *graphPtr;
4264 Drawable drawable;
4265 {
4266 Axis *axisPtr;
4267 Blt_ChainLink *linkPtr;
4268 register int i;
4269
4270 for (i = 0; i < 4; i++) {
4271 for (linkPtr = Blt_ChainFirstLink(graphPtr->margins[i].axes);
4272 linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
4273 axisPtr = Blt_ChainGetValue(linkPtr);
4274 if ((!axisPtr->hidden) && (axisPtr->flags & AXIS_ONSCREEN)) {
4275 DrawAxis(graphPtr, drawable, axisPtr);
4276 }
4277 }
4278 }
4279 }
4280
4281 void
Blt_AxesToPostScript(graphPtr,psToken)4282 Blt_AxesToPostScript(graphPtr, psToken)
4283 Graph *graphPtr;
4284 PsToken psToken;
4285 {
4286 Axis *axisPtr;
4287 Blt_ChainLink *linkPtr;
4288 register int i;
4289
4290 for (i = 0; i < 4; i++) {
4291 for (linkPtr = Blt_ChainFirstLink(graphPtr->margins[i].axes);
4292 linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
4293 axisPtr = Blt_ChainGetValue(linkPtr);
4294 if ((!axisPtr->hidden) && (axisPtr->flags & AXIS_ONSCREEN)) {
4295 AxisToPostScript(psToken, axisPtr);
4296 }
4297 }
4298 }
4299 }
4300
4301
4302 /*
4303 * ----------------------------------------------------------------------
4304 *
4305 * Blt_DrawAxisLimits --
4306 *
4307 * Draws the min/max values of the axis in the plotting area.
4308 * The text strings are formatted according to the "sprintf"
4309 * format descriptors in the limitsFormats array.
4310 *
4311 * Results:
4312 * None.
4313 *
4314 * Side Effects:
4315 * Draws the numeric values of the axis limits into the outer
4316 * regions of the plotting area.
4317 *
4318 * ----------------------------------------------------------------------
4319 */
4320 void
Blt_DrawAxisLimits(graphPtr,drawable)4321 Blt_DrawAxisLimits(graphPtr, drawable)
4322 Graph *graphPtr;
4323 Drawable drawable;
4324 {
4325 Axis *axisPtr;
4326 Blt_HashEntry *hPtr;
4327 Blt_HashSearch cursor;
4328 Dim2D textDim;
4329 int isHoriz;
4330 char *minPtr, *maxPtr;
4331 char *minFormat, *maxFormat;
4332 char minString[200], maxString[200];
4333 int vMin, hMin, vMax, hMax;
4334
4335 #define SPACING 8
4336 vMin = vMax = graphPtr->left + graphPtr->padLeft + 2;
4337 hMin = hMax = graphPtr->bottom - graphPtr->padBottom - 2; /* Offsets */
4338
4339 for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor);
4340 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
4341 axisPtr = (Axis *)Blt_GetHashValue(hPtr);
4342
4343 if (axisPtr->nFormats == 0) {
4344 continue;
4345 }
4346 isHoriz = AxisIsHorizontal(graphPtr, axisPtr);
4347 minPtr = maxPtr = NULL;
4348 minFormat = maxFormat = axisPtr->limitsFormats[0];
4349 if (axisPtr->nFormats > 1) {
4350 maxFormat = axisPtr->limitsFormats[1];
4351 }
4352 if (minFormat[0] != '\0') {
4353 minPtr = minString;
4354 sprintf(minString, minFormat, axisPtr->axisRange.min);
4355 }
4356 if (maxFormat[0] != '\0') {
4357 maxPtr = maxString;
4358 sprintf(maxString, maxFormat, axisPtr->axisRange.max);
4359 }
4360 if (axisPtr->descending) {
4361 char *tmp;
4362
4363 tmp = minPtr, minPtr = maxPtr, maxPtr = tmp;
4364 }
4365 if (maxPtr != NULL) {
4366 if (isHoriz) {
4367 axisPtr->limitsTextStyle.theta = 90.0;
4368 axisPtr->limitsTextStyle.anchor = TK_ANCHOR_SE;
4369 Blt_DrawText2(graphPtr->tkwin, drawable, maxPtr,
4370 &axisPtr->limitsTextStyle, graphPtr->right, hMax, &textDim);
4371 hMax -= (textDim.height + SPACING);
4372 } else {
4373 axisPtr->limitsTextStyle.theta = 0.0;
4374 axisPtr->limitsTextStyle.anchor = TK_ANCHOR_NW;
4375 Blt_DrawText2(graphPtr->tkwin, drawable, maxPtr,
4376 &axisPtr->limitsTextStyle, vMax, graphPtr->top, &textDim);
4377 vMax += (textDim.width + SPACING);
4378 }
4379 }
4380 if (minPtr != NULL) {
4381 axisPtr->limitsTextStyle.anchor = TK_ANCHOR_SW;
4382 if (isHoriz) {
4383 axisPtr->limitsTextStyle.theta = 90.0;
4384 Blt_DrawText2(graphPtr->tkwin, drawable, minPtr,
4385 &axisPtr->limitsTextStyle, graphPtr->left, hMin, &textDim);
4386 hMin -= (textDim.height + SPACING);
4387 } else {
4388 axisPtr->limitsTextStyle.theta = 0.0;
4389 Blt_DrawText2(graphPtr->tkwin, drawable, minPtr,
4390 &axisPtr->limitsTextStyle, vMin, graphPtr->bottom, &textDim);
4391 vMin += (textDim.width + SPACING);
4392 }
4393 }
4394 } /* Loop on axes */
4395 }
4396
4397 void
Blt_AxisLimitsToPostScript(graphPtr,psToken)4398 Blt_AxisLimitsToPostScript(graphPtr, psToken)
4399 Graph *graphPtr;
4400 PsToken psToken;
4401 {
4402 Axis *axisPtr;
4403 Blt_HashEntry *hPtr;
4404 Blt_HashSearch cursor;
4405 double vMin, hMin, vMax, hMax;
4406 char string[200];
4407 int textWidth, textHeight;
4408 char *minFmt, *maxFmt;
4409
4410 #define SPACING 8
4411 vMin = vMax = graphPtr->left + graphPtr->padLeft + 2;
4412 hMin = hMax = graphPtr->bottom - graphPtr->padBottom - 2; /* Offsets */
4413 for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor);
4414 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
4415 axisPtr = (Axis *)Blt_GetHashValue(hPtr);
4416
4417 if (axisPtr->nFormats == 0) {
4418 continue;
4419 }
4420 minFmt = maxFmt = axisPtr->limitsFormats[0];
4421 if (axisPtr->nFormats > 1) {
4422 maxFmt = axisPtr->limitsFormats[1];
4423 }
4424 if (*maxFmt != '\0') {
4425 sprintf(string, maxFmt, axisPtr->axisRange.max);
4426 Blt_GetTextExtents(&axisPtr->tickTextStyle, string, &textWidth,
4427 &textHeight);
4428 if ((textWidth > 0) && (textHeight > 0)) {
4429 if (axisPtr->classUid == bltXAxisUid) {
4430 axisPtr->limitsTextStyle.theta = 90.0;
4431 axisPtr->limitsTextStyle.anchor = TK_ANCHOR_SE;
4432 Blt_TextToPostScript(psToken, string,
4433 &axisPtr->limitsTextStyle,
4434 (double)graphPtr->right, hMax);
4435 hMax -= (textWidth + SPACING);
4436 } else {
4437 axisPtr->limitsTextStyle.theta = 0.0;
4438 axisPtr->limitsTextStyle.anchor = TK_ANCHOR_NW;
4439 Blt_TextToPostScript(psToken, string,
4440 &axisPtr->limitsTextStyle, vMax, (double)graphPtr->top);
4441 vMax += (textWidth + SPACING);
4442 }
4443 }
4444 }
4445 if (*minFmt != '\0') {
4446 sprintf(string, minFmt, axisPtr->axisRange.min);
4447 Blt_GetTextExtents(&axisPtr->tickTextStyle, string, &textWidth,
4448 &textHeight);
4449 if ((textWidth > 0) && (textHeight > 0)) {
4450 axisPtr->limitsTextStyle.anchor = TK_ANCHOR_SW;
4451 if (axisPtr->classUid == bltXAxisUid) {
4452 axisPtr->limitsTextStyle.theta = 90.0;
4453 Blt_TextToPostScript(psToken, string,
4454 &axisPtr->limitsTextStyle,
4455 (double)graphPtr->left, hMin);
4456 hMin -= (textWidth + SPACING);
4457 } else {
4458 axisPtr->limitsTextStyle.theta = 0.0;
4459 Blt_TextToPostScript(psToken, string,
4460 &axisPtr->limitsTextStyle,
4461 vMin, (double)graphPtr->bottom);
4462 vMin += (textWidth + SPACING);
4463 }
4464 }
4465 }
4466 }
4467 }
4468
4469 Axis *
Blt_GetFirstAxis(chainPtr)4470 Blt_GetFirstAxis(chainPtr)
4471 Blt_Chain *chainPtr;
4472 {
4473 Blt_ChainLink *linkPtr;
4474
4475 linkPtr = Blt_ChainFirstLink(chainPtr);
4476 if (linkPtr == NULL) {
4477 return NULL;
4478 }
4479 return Blt_ChainGetValue(linkPtr);
4480 }
4481
4482 Axis *
Blt_NearestAxis(graphPtr,x,y)4483 Blt_NearestAxis(graphPtr, x, y)
4484 Graph *graphPtr;
4485 int x, y; /* Point to be tested */
4486 {
4487 register Blt_HashEntry *hPtr;
4488 Blt_HashSearch cursor;
4489 Axis *axisPtr;
4490 int width, height;
4491 double rotWidth, rotHeight;
4492 Point2D bbox[5];
4493
4494 for (hPtr = Blt_FirstHashEntry(&graphPtr->axes.table, &cursor);
4495 hPtr != NULL; hPtr = Blt_NextHashEntry(&cursor)) {
4496 axisPtr = (Axis *)Blt_GetHashValue(hPtr);
4497 if ((axisPtr->hidden) || (!(axisPtr->flags & AXIS_ONSCREEN))) {
4498 continue; /* Don't check hidden axes or axes
4499 * that are virtual. */
4500 }
4501 if (axisPtr->showTicks) {
4502 register Blt_ChainLink *linkPtr;
4503 TickLabel *labelPtr;
4504 Point2D t;
4505
4506 for (linkPtr = Blt_ChainFirstLink(axisPtr->tickLabels);
4507 linkPtr != NULL; linkPtr = Blt_ChainNextLink(linkPtr)) {
4508 labelPtr = Blt_ChainGetValue(linkPtr);
4509 Blt_GetBoundingBox(labelPtr->width, labelPtr->height,
4510 axisPtr->tickTextStyle.theta, &rotWidth, &rotHeight, bbox);
4511 width = ROUND(rotWidth);
4512 height = ROUND(rotHeight);
4513 t = Blt_TranslatePoint(&labelPtr->anchorPos, width, height,
4514 axisPtr->tickTextStyle.anchor);
4515 t.x = x - t.x - (width * 0.5);
4516 t.y = y - t.y - (height * 0.5);
4517
4518 bbox[4] = bbox[0];
4519 if (Blt_PointInPolygon(&t, bbox, 5)) {
4520 axisPtr->detail = "label";
4521 return axisPtr;
4522 }
4523 }
4524 }
4525 if (axisPtr->title != NULL) { /* and then the title string. */
4526 Point2D t;
4527
4528 Blt_GetTextExtents(&axisPtr->titleTextStyle, axisPtr->title,&width,
4529 &height);
4530 Blt_GetBoundingBox(width, height, axisPtr->titleTextStyle.theta,
4531 &rotWidth, &rotHeight, bbox);
4532 width = ROUND(rotWidth);
4533 height = ROUND(rotHeight);
4534 t = Blt_TranslatePoint(&axisPtr->titlePos, width, height,
4535 axisPtr->titleTextStyle.anchor);
4536 /* Translate the point so that the 0,0 is the upper left
4537 * corner of the bounding box. */
4538 t.x = x - t.x - (width / 2);
4539 t.y = y - t.y - (height / 2);
4540
4541 bbox[4] = bbox[0];
4542 if (Blt_PointInPolygon(&t, bbox, 5)) {
4543 axisPtr->detail = "title";
4544 return axisPtr;
4545 }
4546 }
4547 if (axisPtr->lineWidth > 0) { /* Check for the axis region */
4548 if (PointInRegion(&axisPtr->region, x, y)) {
4549 axisPtr->detail = "line";
4550 return axisPtr;
4551 }
4552 }
4553 }
4554 return NULL;
4555 }
4556
4557 ClientData
Blt_MakeAxisTag(graphPtr,tagName)4558 Blt_MakeAxisTag(graphPtr, tagName)
4559 Graph *graphPtr;
4560 char *tagName;
4561 {
4562 Blt_HashEntry *hPtr;
4563 int isNew;
4564
4565 hPtr = Blt_CreateHashEntry(&graphPtr->axes.tagTable, tagName, &isNew);
4566 assert(hPtr);
4567 return Blt_GetHashKey(&graphPtr->axes.tagTable, hPtr);
4568 }
4569