source: trunk/kitgen/8.x/blt/generic/bltGrAxis.c@ 176

Last change on this file since 176 was 175, checked in by demin, 12 years ago

initial commit

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