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