1 /*
2     Gri - A language for scientific graphics programming
3     Copyright (C) 2010 Daniel Kelley
4 
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; version 3 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License along
16     with this program; if not, write to the Free Software Foundation, Inc.,
17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19 
20 //#define DEBUG_LABELLING 1
21 //#define DEBUG_LABELS 1
22 #include <string>
23 #include <string.h>
24 #if !defined(IS_MINGW32)
25 #include <strings.h>
26 #else
27 #define index strrchr
28 #endif
29 
30 #include <math.h>
31 #include <stdlib.h>
32 #include "gr.hh"
33 #include "GriPath.hh"
34 #include "extern.hh"
35 
36 #if defined(__DECCXX) || defined(OS_IS_BEOS)
37 extern "C" char *index(const char *s, int c);
38 #endif
39 
40 #define TIC_RATIO	0.5	/* (Length small tic) / (large tic) */
41 extern char     _grXAxisLabel[];
42 extern char     _grYAxisLabel[];
43 extern char     _grNumFormat_x[];
44 extern char     _grNumFormat_y[];
45 extern char     _grTempString[];
46 extern int      _grAxisStyle_x;
47 extern int      _grAxisStyle_y;
48 extern gr_axis_properties _grTransform_x;
49 extern gr_axis_properties _grTransform_y;
50 extern int      _grNeedBegin;
51 extern int      _grNumSubDiv_x;
52 extern int      _grNumSubDiv_y;
53 extern double   _grCmPerUser_x;
54 extern double   _grCmPerUser_y;
55 extern double   _grTicSize_cm;
56 
57 #define	round_down_to_10(x) 	(pow (10.0, floor (log10 ( (x) ))))
58 #define	round_to_1(x)		(floor (0.5 + (x) ))
59 
60 /* Some twiddles */
61 #define AXIS_TWIDDLE            0.005	/* .05mm overhang on axis */
62 #define SMALLNUM                (1.0e-3)	/* twiddle axis range */
63 #define SMALLERNUM              (1.0e-4)	/* twiddle axis range */
64 #define SMALLFONTSIZE           (1.0e-3)	/* too small to draw */
65 /*
66  * local functions
67  */
68 static bool     next_tic(double *next, double labelling, bool gave_labelling, double present, double final, double inc, gr_axis_properties axis_type, bool increasing);
69 
70 #if 0
71 static int      num_decimals(char *s);
72 static int      create_labels(double y, double yb, double yinc, double yt, double smallinc, char *label[], int *num_label);
73 #endif
74 
75 
76 // Calculate next tic location on axis.  Note that if the axis starts off
77 // ragged, this will assign to *next the first multiple of "inc". Returns
78 // true if more axis to do
79 static bool
next_tic(double * next,double labelling,bool gave_labelling,double present,double final,double inc,gr_axis_properties axis_type,bool increasing)80 next_tic(double *next, double labelling, bool gave_labelling, double present, double final, double inc, gr_axis_properties axis_type, bool increasing)
81 {
82 #if defined(DEBUG_LABELLING)
83         printf("%s:%d  next_tic(...,labelling=%f  gave_labelling=%s  present=%f  final=%f  inc=%f  increasing=%s  _x_gave_labelling=%s  _y_gave_labelling=%s\n", __FILE__,__LINE__,labelling,gave_labelling?"true":"false",present,final,inc,increasing?"true":"false", _x_gave_labelling?"true":"false", _y_gave_labelling?"true":"false");
84 #endif
85 	double          order_of_mag, mantissa;
86 	// Check to see if already ran out of axis.
87 	if (present >= final && increasing == true)
88 		return false;
89         if (present <= final && increasing == false)
90 		return false;
91         // Determine next tic to draw to, treating linear/log separately.
92 	switch (axis_type) {
93 	case gr_axis_LINEAR:
94 		//*next = inc * (1.0 + floor((SMALLERNUM * inc + (present - labelling)) / inc));
95                 if (gave_labelling) {
96                         *next = labelling + inc * (1 + floor(SMALLERNUM + (present - labelling) / inc));
97 #if defined(DEBUG_LABELLING)
98                         printf("%s:%d   next=%f  present=%f  inc=%f\n  %f  %f  %f %f\n",
99                                __FILE__, __LINE__, *next, present, inc,
100                                inc * (1.001 + floor((present - labelling) / inc)),
101                                 (1 + floor((present - labelling) / inc)),
102                                 (floor((present - labelling) / inc)),
103                                 (present - labelling) / inc);
104 #endif
105                 } else {
106                         *next = inc * (1.0 + floor((SMALLERNUM * inc + present) / inc));
107                 }
108 		break;
109 	case gr_axis_LOG:
110 		if (present <= 0.0) {
111 			err("zero or negative on log axis (internal error).");
112                         return false;
113                 }
114 		order_of_mag = round_down_to_10(present);
115 		mantissa = present / order_of_mag;
116 		if (increasing)
117 			mantissa = ceil(mantissa + SMALLNUM);
118 		else {
119 			if (mantissa <= 1.0)
120 				mantissa = 0.9;
121 			else
122 				mantissa = floor(mantissa - SMALLNUM);
123 		}
124 		*next = order_of_mag * mantissa;
125 		break;
126 	default:
127 		gr_Error("unknown axis type (internal error)");
128 	}
129 	// Set flag if this will overrun axis.
130 	if (increasing == true)
131 		return (*next <= final) ? true : false;
132 	else
133 		return (*next >= final) ? true : false;
134 }
135 
136 /*
137  * gr_drawxyaxes DESCRIPTION: Draws an x-y axis frame
138  */
139 void
gr_drawxyaxes(double xl,double xinc,double xr,double yb,double yinc,double yt)140 gr_drawxyaxes(double xl, double xinc, double xr, double yb, double yinc, double yt)
141 {
142 	double          old_fontsize_pt = gr_currentfontsize_pt();
143 	gr_drawxaxis(yb, xl, xinc, xr, xl, gr_axis_BOTTOM);
144 	gr_drawyaxis(xl, yb, yinc, yt, yb, gr_axis_LEFT);
145 	gr_setfontsize_pt(0.0);
146 	gr_drawxaxis(yt, xl, xinc, xr, xl, gr_axis_TOP);
147 	gr_drawyaxis(xr, yb, yinc, yt, yb, gr_axis_RIGHT);
148 	gr_setfontsize_pt(old_fontsize_pt);
149 }
150 
151 // DESCRIPTION: The axis extends from 'xl' to 'xr',with numbers placed at
152 // intervals of 'xinc'.
153 // If 'side'==BOTTOM/TOP this is an axis designed to appear at the
154 // bottom/top of a plotting region (ie, the numbers are below/above).
155 void
gr_drawxaxis(double y,double xl,double xinc,double xr,double xlabelling,gr_axis_properties side)156 gr_drawxaxis(double y, double xl, double xinc, double xr, double xlabelling, gr_axis_properties side)
157 {
158         //printf("gr_drawxaxis(..., xl=%f xlabelling=%f same=%d\n", xl, xlabelling, xl==xlabelling);
159 	bool user_gave_labels = (_x_labels.size() != 0);
160 #ifdef DEBUG_LABELS
161 	if (user_gave_labels) {
162 		printf("DEBUG: %s:%d x axis should have labels [", __FILE__,__LINE__);
163 		for (unsigned int i = 0; i < _x_labels.size(); i++)
164 			printf("'%s' ", _x_labels[i].c_str());
165 		printf("] at positions [");
166 		for (unsigned int i = 0; i < _x_label_positions.size(); i++)
167 			printf("%f ", _x_label_positions[i]);
168 		printf("]\n");
169 	}
170 #endif
171 	GriString label;
172 	std::string slabel;
173 	extern char     _xtype_map;
174 	double          CapHeight = gr_currentCapHeight_cm();
175 	double          angle = 0.0;	// angle of axis tics, labels, etc
176 #ifdef DEBUG_LABELS
177 	printf("DEBUG: %s:%d at top of gr_drawxaxis(), angle is %f\n", __FILE__, __LINE__, angle);
178 #endif
179 	bool            increasing = ((xr > xl) ? true : false);
180 	double          tic, tic_sml;	// length of tic marks
181 	double          xcm, ycm;	// tmp
182 	double          offset;	// for numbers
183 	double          present, next, final = xr, smallinc = 0.0;
184 	int             decade_between_labels;	// for log axes
185 	double          tmp1, tmp2;
186 	GriPath         axis_path;
187 	// XREF -- axis transform
188 	// Calculate size of large and small tic marks.
189 	extern bool     _grTicsPointIn;
190 	tic = ((side == gr_axis_LEFT && _grTicsPointIn == false)
191 	       || (side == gr_axis_RIGHT && _grTicsPointIn == true))
192 		? -_grTicSize_cm : _grTicSize_cm;
193 	if (_grTransform_x == gr_axis_LOG && _xsubdiv < 0)
194 		tic_sml = 0.0;
195 	else
196 		tic_sml = TIC_RATIO * tic;
197 	// Calculate offset = space for numbers.
198 	offset = 0.0;
199 	int old_linecap = _griState.line_cap();
200 	int old_linejoin = _griState.line_join();
201 	_griState.set_line_cap(0);
202 	_griState.set_line_join(0);
203 	switch (_grTransform_x) {
204 	case gr_axis_LINEAR:
205 	case gr_axis_LOG:
206 		if (side == gr_axis_BOTTOM) {
207 			if (strlen(_grNumFormat_x))
208 				offset -= 1.75 * CapHeight;
209 			offset -= ((_grTicsPointIn == false) ? _grTicSize_cm : 0.0);
210 		} else {
211 			if (strlen(_grNumFormat_x))
212 				offset += 0.75 * CapHeight;
213 			offset += ((_grTicsPointIn == false) ? _grTicSize_cm : 0.0);
214 		}
215 		break;
216 	default:
217 		gr_Error("unknown axis type (internal error)");
218 	}
219 	// Draw axis, moving from tic to tic.  Tics are advanced by smallinc for
220 	// linear axes and by something appropriate for log axes. Whenever the
221 	// current location should be a big tic, such a tic is drawn along with a
222 	// label.
223 	double xl_cm, y_cm;
224         int this_pass=0, pass_max=5000;
225 	switch (_grTransform_x) {
226 	case gr_axis_LINEAR:
227 		smallinc = xinc / _grNumSubDiv_x;
228 		// Twiddle axes to extend a bit beyond the requested
229 		// region, to prevent rounding problems.
230 		present = xl - xinc / 1.0e3;
231 		final   = xr + xinc / 1.0e3;
232 		// Draw x-axis, moving from tic to tic.  Tics are advanced by
233 		// smallinc for linear axes and by something appropriate for log
234 		// axes. Whenever the current location should be a big tic, such a
235 		// tic is drawn along with a label.
236 		gr_usertocm(xl, y, &xl_cm, &y_cm);
237 		axis_path.push_back(xl_cm, y_cm, 'm');
238 #if defined(DEBUG_LABELLING)
239                 printf("%s:%d  _x_gave_labelling=%d xlabelling=%f   present=%f\n", __FILE__, __LINE__, _x_gave_labelling, xlabelling, present);
240 #endif
241 		while (next_tic(&next, xlabelling, _x_gave_labelling, present, final, smallinc, _grTransform_x, increasing)) {
242                         if (this_pass++ > pass_max) {
243                                 extern bool _x_gave_labelling;
244                                 if (_x_gave_labelling) {
245                                         gr_Error("cannot draw x axis (internal error: problem dealing with 'labelling' keyword)");
246                                 } else {
247                                         gr_Error("cannot draw x axis (internal error)");
248                                 }
249                                 return;
250                         }
251 			// Determine angle of x-axis tics, for non-rectangular axes
252 			switch (_grTransform_x) {
253 			case gr_axis_LINEAR:
254 			case gr_axis_LOG:
255 				angle = atan2(1.0, 0.0); // angle for tics
256 				break;
257 			default:
258 				gr_Error("unknown axis type (internal error)");
259 				break;
260 			}
261 			gr_usertocm(next, y, &xcm, &ycm);
262 			axis_path.push_back(xcm, ycm, 'l');
263 			// Detect large tics on x axis
264 			if ((_x_gave_labelling && gr_multiple(next - xlabelling, xinc, 0.01 * smallinc))
265                             || (!_x_gave_labelling && gr_multiple(next, xinc, 0.01 * smallinc))) {
266 #if defined(DEBUG_LABELLING)
267                                 printf("%s:%d   next=%f\n", __FILE__, __LINE__, next);
268 #endif
269 				// draw large tic
270 				axis_path.push_back(xcm + tic * cos(angle), ycm + tic * sin(angle), 'l');
271 				if (gr_currentfontsize_pt() > SMALLFONTSIZE) {
272 					if (_xtype_map != ' ') {
273 						// It's a map, so figure the deg/min/seconds;
274 						// over-ride any existing format
275 						int             hour, min, sec;
276 						if (gr_multiple(next, 1.0, 1.0e-6)) {
277 							hour = (int)floor(1.0e-4 + fabs(next));
278 							if (next >= 0.0)
279 								sprintf(_grTempString,"%d$\\circ$%c",hour,_xtype_map);
280 							else
281 								sprintf(_grTempString,"-%d$\\circ$%c",hour,_xtype_map);
282 						} else if (gr_multiple(next, 1.0 / 60.0, 1.0e-7)) {
283 							hour = (int)floor(1.0e-4 + fabs(next));
284 							min = (int)floor(1e-5 + 60.0 * (fabs(next) - hour));
285 							if (next >= 0.0)
286 								sprintf(_grTempString,"%d$\\circ$%d'%c",hour,min,_xtype_map);
287 							else
288 								sprintf(_grTempString,"-%d$\\circ$%d'%c",hour,min,_xtype_map);
289 						} else if (gr_multiple(next, 1.0 / 3600.0, 1.0e-8)) {
290 							hour = (int)floor(1.0e-4 + fabs(next));
291 							min = (int)floor(1e-5 + 60.0 * (fabs(next) - hour));
292 							sec = (int)floor(1e-5 + 3600.0 * (fabs(next) - hour - min / 60.0));
293 							if (next >= 0.0)
294 								sprintf(_grTempString, "%d$\\circ$%d'%d\"%c",hour,min,sec,_xtype_map);
295 							else
296 								sprintf(_grTempString, "-%d$\\circ$%d'%d\"%c",hour,min,sec,_xtype_map);
297 						} else {
298 							sprintf(_grTempString,"%f$\\circ$%c",next,_xtype_map);
299 						}
300 					} else if (strlen(_grNumFormat_x)) {
301 						sprintf(_grTempString, _grNumFormat_x, next);
302 						if (get_flag("emulate_gre")) {
303 							char *e = index(_grTempString, int('E'));
304 							if (e != NULL) {
305 								std::string gs(_grTempString);
306 								size_t chop;
307 								if (STRING_NPOS != (chop = gs.find("E+0"))) {
308 									gs.replace(chop, 3, "$\\times10^{");
309 									gs.append("}$");
310 								} else if (STRING_NPOS != (chop = gs.find("E-0"))) {
311 									gs.replace(chop, 3, "$\\times10^{-");
312 									gs.append("}$");
313 								} else if (STRING_NPOS != (chop = gs.find("E+"))) {
314 									gs.replace(chop, 2, "$\\times10^{");
315 									gs.append("}$");
316 								} else if (STRING_NPOS != (chop = gs.find("E-"))) {
317 									gs.replace(chop, 2, "$\\times10^{-");
318 									gs.append("}$");
319 								} else if (STRING_NPOS != (chop = gs.find("E"))) {
320 									gs.replace(chop, 1, "$\\times10^{");
321 									gs.append("}$");
322 								}
323 								strcpy(_grTempString, gs.c_str());
324 							}
325 						}
326 					} else {
327 						*_grTempString = '\0';
328 					}
329 					angle = 0;
330 #ifdef DEBUG_LABELS
331 					printf("DEBUG: %s:%d after the loop, angle is %f\n", __FILE__, __LINE__, angle);
332 #endif
333 					if (!user_gave_labels) {
334 						slabel.assign(_grTempString);
335 						fix_negative_zero(slabel);
336 						label.fromSTR(slabel.c_str());
337 						label.draw(xcm - offset * sin(angle),
338 							   ycm + offset * cos(angle),
339 							   TEXT_CENTERED,
340 							   DEG_PER_RAD * angle);
341 					}
342 				}
343 			} else {
344 				// Small tic
345 				axis_path.push_back(xcm + tic_sml * cos(angle), ycm + tic_sml * sin(angle), 'l');
346 			}
347 			axis_path.push_back(gr_usertocm_x(next, y), gr_usertocm_y(next, y), 'l');
348 			present = next;
349 #if defined(DEBUG_LABELLING)
350                         printf("%s:%d   bottom of loop; present=%f\n", __FILE__, __LINE__, present);
351 #endif
352 		}
353 		if (user_gave_labels) {
354 			angle = 0;
355 			for (unsigned int i = 0; i < _x_labels.size(); i++) {
356 				if (BETWEEN(xl, xr, _x_label_positions[i])) {
357 					label.fromSTR(_x_labels[i].c_str()); // BUG: should interpolate into this string
358 					gr_usertocm(_x_label_positions[i], y, &xcm, &ycm);
359 #ifdef DEBUG_LABELS
360 					printf("DEBUG: %s:%d drawing %d-th label '%s' at x=%f angle=%f\n",__FILE__,__LINE__,i,_x_labels[i].c_str(),_x_label_positions[i],angle);
361 #endif
362 					label.draw(xcm - offset * sin(angle),
363 						   ycm + offset * cos(angle),
364 						   TEXT_CENTERED,
365 						   DEG_PER_RAD * angle);
366 				} else {
367 #ifdef DEBUG_LABELS
368 					printf("DEBUG: %s:%d SKIPPING %d-th label '%s' since x=%f\n",__FILE__,__LINE__,i,_x_labels[i].c_str(),_x_label_positions[i]);
369 #endif
370 				}
371 			}
372 		}
373 		// Finish by drawing to end of axis (in case there was no tic there).
374 		axis_path.push_back(gr_usertocm_x(final, y), gr_usertocm_y(final, y), 'l');
375 		axis_path.stroke(units_cm, _griState.linewidth_axis());
376 		break;
377 	case gr_axis_LOG:
378 		decade_between_labels = (int) floor(0.5 + xinc);
379 		gr_usertocm(xl, y, &xcm, &ycm);
380 		gr_cmtouser(xcm - AXIS_TWIDDLE, ycm, &tmp1, &tmp2);
381 		present = tmp1;
382 		axis_path.push_back(present, y, 'm');
383 #if defined(DEBUG_LABELLING)
384                 printf("%s:%d  _x_gave_labelling=%d xlabelling=%f   present=%f\n", __FILE__, __LINE__, _x_gave_labelling, xlabelling, present);
385 #endif
386 		while (next_tic(&next, xl, _x_gave_labelling, present, final, smallinc, _grTransform_x, increasing)) {
387 			double          tmp, next_log;
388 			double xuser, yuser;
389 			axis_path.push_back(next, y, 'l');
390 			next_log = log10(next);
391 			tmp = next_log - floor(next_log);
392 			gr_usertocm(next, y, &xcm, &ycm);
393 			if (-0.01 < tmp && tmp < 0.01) {
394 				// large tic & number
395 				gr_cmtouser(xcm, ycm+tic, &xuser, &yuser);
396 				axis_path.push_back(xuser, yuser, 'l');
397 				gr_cmtouser(xcm, ycm+offset, &xuser, &yuser);
398 				tmp = next_log - decade_between_labels * floor(next_log / decade_between_labels);
399 				if (!user_gave_labels
400                                     && gr_currentfontsize_pt() > SMALLFONTSIZE
401                                     && -0.01 < tmp / xinc
402                                     && tmp / xinc < 0.01
403                                     && strlen(_grNumFormat_x)) {
404 					// Draw "1" as a special case
405 					if (0.99 < next && next < 1.01)
406 						sprintf(_grTempString, "1");
407 					else
408 						sprintf(_grTempString, "$10^{%.0f}$", log10(next));
409 					slabel.assign(_grTempString);
410 					fix_negative_zero(slabel);
411 					label.fromSTR(slabel.c_str());
412 					label.draw(xcm, ycm + offset, TEXT_CENTERED, 0.0);
413 				}
414 			} else {
415 				// small tic
416 				gr_cmtouser(xcm, ycm+tic_sml,&xuser, &yuser);
417 				axis_path.push_back(xuser, yuser, 'l');
418 			}
419 			axis_path.push_back(next, y, 'm');
420 			present = next;
421 		}
422 		if (user_gave_labels) {
423 			angle = 0;
424 			for (unsigned int i = 0; i < _x_labels.size(); i++) {
425 				if (BETWEEN(xl, xr, _x_label_positions[i])) {
426 					label.fromSTR(_x_labels[i].c_str()); // BUG: should interpolate into this string
427 					gr_usertocm(_x_label_positions[i], y, &xcm, &ycm);
428 #ifdef DEBUG_LABELS
429 					printf("DEBUG: %s:%d drawing %d-th label '%s' at x=%f angle=%f\n",__FILE__,__LINE__,i,_x_labels[i].c_str(),_x_label_positions[i],angle);
430 #endif
431 					label.draw(xcm - offset * sin(angle), ycm + offset * cos(angle), TEXT_CENTERED, DEG_PER_RAD * angle);
432 				} else {
433 #ifdef DEBUG_LABELS
434 					//printf("DEBUG: %s:%d SKIPPING %d-th label '%s' since x=%f\n",__FILE__,__LINE__,i,_x_labels[i].c_str(),_x_label_positions[i]);
435 #endif
436 				}
437 			}
438 		}
439 		// Finish by drawing to end of axis (in case there was no tic there).
440 		axis_path.push_back(final, y, 'l');
441 		axis_path.stroke(units_user, _griState.linewidth_axis());
442 		break;
443 	default:
444 		gr_Error("unknown axis type (internal error)");
445 	}
446 	// Draw axis title.
447 	if (gr_currentfontsize_pt() > SMALLFONTSIZE) {
448 		label.fromSTR(_grXAxisLabel);
449 		switch (_grTransform_x) {
450 		case gr_axis_LINEAR:
451 			if (side == gr_axis_TOP) {
452 				double          xcm, ycm;
453 				gr_usertocm(0.5 * (xl + final), y, &xcm, &ycm);
454 				label.draw(xcm,
455 					   ycm + offset + 1.75 * CapHeight,
456 					   TEXT_CENTERED,
457 					   0.0);
458 			} else {
459 				double          xcm, ycm;
460 				gr_usertocm(0.5 * (xl + final), y, &xcm, &ycm);
461 				label.draw(xcm,
462 					   ycm + offset - 1.75 * CapHeight,
463 					   TEXT_CENTERED,
464 					   0.0);
465 			}
466 			break;
467 		case gr_axis_LOG:
468 			if (side == gr_axis_TOP) {
469 				double          xcm, ycm;
470 				gr_usertocm(sqrt(xl * final), y, &xcm, &ycm);
471 				label.draw(xcm,
472 					   ycm + offset + (1.75 + 0.75) * CapHeight,
473 					   TEXT_CENTERED,
474 					   0.0);
475 			} else {
476 				double          xcm, ycm;
477 				gr_usertocm(sqrt(xl * final), y, &xcm, &ycm);
478 				label.draw(xcm,
479 					   ycm + offset - 1.75 * CapHeight,
480 					   TEXT_CENTERED,
481 					   0.0);
482 			}
483 			break;
484 		default:
485 			gr_Error("unknown axis type (internal error)");
486 		}
487 	}
488 	_griState.set_line_cap(old_linecap);
489 	_griState.set_line_join(old_linejoin);
490 }
491 
492 #define FACTOR 1.35		// Kludge to scale fonts up
493 // Draw a y axis
494 void
gr_drawyaxis(double x,double yb,double yinc,double yt,double ylabelling,gr_axis_properties side)495 gr_drawyaxis(double x, double yb, double yinc, double yt, double ylabelling, gr_axis_properties side)
496 {
497 #if 1				// 2.9.x
498 	bool user_gave_labels = (_y_labels.size() != 0);
499 #endif // 2.9.x
500 	GriString label;
501 	std::string slabel;
502 	extern char     _ytype_map;
503 	double          CapHeight = gr_currentCapHeight_cm();
504 	double          angle = 0.0; // angle of axis tics, labels, etc
505 	bool            increasing = ((yt > yb) ? true : false);
506 	double          tic, tic_sml; // length of tic marks
507 	double          xcm, ycm; // used to step along axis
508 	double          xcm2, ycm2; // tmp, allowed to mess with
509 	double          labelx_cm, labely_cm; // where tic label will go
510 	double          offset;	// for numbers
511 	double          present, next, final = yt, smallinc = 0.0;
512 	int             decade_between_labels; // for log axes
513 	double          max_num_width_cm = 0.0;	// use for positioning label
514 	double          tmp0, tmp1, tmp2;
515 	GriPath         axis_path;
516 	// Calculate size of large and small tic marks.
517 	extern bool     _grTicsPointIn;
518 	tic = ((side == gr_axis_LEFT && _grTicsPointIn == true)
519 	       || (side == gr_axis_RIGHT && _grTicsPointIn == false))
520 		? _grTicSize_cm : -_grTicSize_cm;
521 	if (_grTransform_y == gr_axis_LOG && _ysubdiv < 0)
522 		tic_sml = 0.0;
523 	else
524 		tic_sml = TIC_RATIO * tic;
525 	// Calculate offset = space for numbers.
526 	if (side == gr_axis_LEFT) {
527 		if (_grTicsPointIn == true) {
528 			offset = -0.5 * FACTOR * CapHeight;
529 		} else {
530 			offset = -0.5 * FACTOR * CapHeight - _grTicSize_cm;
531 		}
532 	} else {
533 		if (_grTicsPointIn == true) {
534 			offset = 0.5 * FACTOR * CapHeight;
535 		} else {
536 			offset = 0.5 * FACTOR * CapHeight + _grTicSize_cm;
537 		}
538 	}
539 	int old_linecap = _griState.line_cap();
540 	int old_linejoin = _griState.line_join();
541 	_griState.set_line_cap(0);
542 	_griState.set_line_join(0);
543 	// Draw y-axis, moving from tic to tic.  Tics are advanced by smallinc
544 	// for linear axes and by something appropriate for log axes. Whenever
545 	// the current location should be a big tic, such a tic is drawn along
546 	// with a label.
547         int this_pass=0, pass_max=5000;
548 	switch (_grTransform_y) {
549 	case gr_axis_LINEAR:
550 		smallinc = yinc / _grNumSubDiv_y;
551 		present = yb - yinc / 1.0e3;
552 		final   = yt + yinc / 1.0e3;
553 		axis_path.push_back(gr_usertocm_x(x, yb), gr_usertocm_y(x, yb), 'm');
554 #if defined(DEBUG_LABELLING)
555                 printf("%s:%d  _y_gave_labelling=%d ylabelling=%f   present=%f\n", __FILE__, __LINE__, _y_gave_labelling, ylabelling, present);
556 #endif
557 		while (next_tic(&next, ylabelling, _y_gave_labelling, present, final, smallinc, _grTransform_y, increasing)) {
558 			axis_path.push_back(gr_usertocm_x(x, next), gr_usertocm_y(x, next), 'l');
559 			gr_usertocm(x, next, &xcm, &ycm);
560 			angle = 0.0;
561 			// Detect large tics on y axis
562 			if ((_y_gave_labelling && gr_multiple(next - ylabelling, yinc, 0.01 * smallinc))
563                             || (!_y_gave_labelling && gr_multiple(next, yinc, 0.01 * smallinc))) {
564 				double tmpx, tmpy;
565 				gr_cmtouser(xcm + tic * cos(angle), ycm + tic * sin(angle), &tmpx, &tmpy);
566 				axis_path.push_back(xcm + tic * cos(angle), ycm + tic * sin(angle), 'l');
567 				labelx_cm = xcm + offset * cos(angle);
568 				labely_cm = ycm + offset * sin(angle) - 0.5 * CapHeight;
569 				if (gr_currentfontsize_pt() > SMALLFONTSIZE) {
570 					if (_ytype_map != ' ') {
571 						// It's a map, so figure the deg/min/seconds;
572 						// over-ride any existing format
573 						int             hour, min, sec;
574 						if (gr_multiple(next, 1.0, 1.0e-6)) {
575 							hour = (int)floor(1.0e-4 + fabs(next));
576 							if (next >= 0.0)
577 								sprintf(_grTempString,"%d$\\circ$%c",hour,_ytype_map);
578 							else
579 								sprintf(_grTempString,"-%d$\\circ$%c",hour,_ytype_map);
580 						} else if (gr_multiple(next, 1.0 / 60.0, 1.0e-7)) {
581 							hour = (int)floor(1.0e-4 + fabs(next));
582 							min = (int)floor(1e-5 + 60.0 * (fabs(next) - hour));
583 							if (next >= 0.0)
584 								sprintf(_grTempString,"%d$\\circ$%d'%c",hour,min,_ytype_map);
585 							else
586 								sprintf(_grTempString,"-%d$\\circ$%d'%c",hour,min,_ytype_map);
587 						} else if (gr_multiple(next, 1.0 / 3600.0, 1.0e-8)) {
588 							hour = (int)floor(1.0e-4 + fabs(next));
589 							min = (int)floor(1e-5 + 60.0 * (fabs(next) - hour));
590 							sec = (int)floor(1e-5 + 3600.0 * (fabs(next) - hour - min / 60.0));
591 							if (next >= 0.0)
592 								sprintf(_grTempString, "%d$\\circ$%d'%d\"%c",hour,min,sec,_ytype_map);
593 							else
594 								sprintf(_grTempString, "-%d$\\circ$%d'%d\"%c",hour,min,sec,_ytype_map);
595 						} else {
596 							sprintf(_grTempString,"%f$\\circ$%c",next,_ytype_map);
597 						}
598 					} else if (strlen(_grNumFormat_y)) {
599 						if (get_flag("emulate_gre")) {
600 							sprintf(_grTempString, _grNumFormat_y, next);
601 							char *e = index(_grTempString, int('E'));
602 							if (e != NULL) {
603 								std::string gs(_grTempString);
604 								size_t chop;
605 								if (STRING_NPOS != (chop = gs.find("E+0"))) {
606 									gs.replace(chop, 3, "$\\times10^{");
607 									gs.append("}$");
608 								} else if (STRING_NPOS != (chop = gs.find("E-0"))) {
609 									gs.replace(chop, 3, "$\\times10^{-");
610 									gs.append("}$");
611 								} else if (STRING_NPOS != (chop = gs.find("E+"))) {
612 									gs.replace(chop, 2, "$\\times10^{");
613 									gs.append("}$");
614 								} else if (STRING_NPOS != (chop = gs.find("E-"))) {
615 									gs.replace(chop, 2, "$\\times10^{-");
616 									gs.append("}$");
617 								} else if (STRING_NPOS != (chop = gs.find("E"))) {
618 									gs.replace(chop, 1, "$\\times10^{");
619 									gs.append("}$");
620 								}
621 								strcpy(_grTempString, gs.c_str());
622 							}
623 						} else {
624 							sprintf(_grTempString, _grNumFormat_y, next);
625 						}
626 					} else {
627 						*_grTempString = '\0';
628 					}
629 					if (!user_gave_labels) { // 2.9.x
630 						slabel.assign(_grTempString);
631 						fix_negative_zero(slabel);
632 						label.fromSTR(slabel.c_str());
633 						if (side == gr_axis_LEFT)
634 							label.draw(labelx_cm, labely_cm, TEXT_RJUST, angle * DEG_PER_RAD);
635 						else
636 							label.draw(labelx_cm, labely_cm, TEXT_LJUST, angle * DEG_PER_RAD);
637 					}
638 					// Keep track of maximum width of axis numbers, so that
639 					// axis label can be offset right amount.
640 					gr_stringwidth(_grTempString, &tmp0, &tmp1, &tmp2);
641 					if (tmp0 > max_num_width_cm)
642 						max_num_width_cm = tmp0;
643 				}
644 			} else {
645 				// Small tic
646 				axis_path.push_back(xcm + tic_sml * cos(angle), ycm + tic_sml * sin(angle), 'l');
647 			}
648 			axis_path.push_back(gr_usertocm_x(x, next), gr_usertocm_y(x, next), 'l');
649 			present = next;
650 		}
651 #if 1				// 2.9.x
652 		if (user_gave_labels) {
653 		  //printf("labels...\n");
654 			for (unsigned int i = 0; i < _y_labels.size(); i++) {
655 				label.fromSTR(_y_labels[i].c_str()); // BUG: should interpolate into this string
656 				gr_usertocm(x, _y_label_positions[i], &xcm, &ycm);
657 				labelx_cm = xcm + offset * cos(angle);
658 				labely_cm = ycm + offset * sin(angle) - 0.5 * CapHeight;
659 				//printf("%f %f %f %f\n", ycm, offset*sin(angle),CapHeight, labely_cm);
660 				if (side == gr_axis_LEFT)
661 					label.draw(labelx_cm, labely_cm, TEXT_RJUST, DEG_PER_RAD * angle);
662 				else
663 					label.draw(labelx_cm, labely_cm, TEXT_LJUST, DEG_PER_RAD * angle);
664 				gr_stringwidth(_y_labels[i].c_str(), &tmp0, &tmp1, &tmp2);
665 				if (tmp0 > max_num_width_cm)
666 					max_num_width_cm = tmp0;
667 			}
668 		}
669 #endif
670 		// Finish by drawing to end of axis (in case there was no tic there).
671 		axis_path.push_back(gr_usertocm_x(x, yt), gr_usertocm_y(x, yt), 'l');
672 		axis_path.stroke(units_cm, _griState.linewidth_axis());
673 		break;
674 	case gr_axis_LOG:
675 		decade_between_labels = (int) floor(0.5 + yinc);
676 		gr_usertocm(x, yb, &xcm, &ycm);
677 		gr_cmtouser(xcm, ycm - AXIS_TWIDDLE, &tmp1, &tmp2);
678 		present = tmp2;
679 		axis_path.push_back(x, present, 'm');
680                 if (_y_gave_labelling)  {
681                         err("cannot use 'labelling' parameter for logarithmic axis");
682                         return;
683                 }
684 #if defined(DEBUG_LABELLING)
685                 printf("%s:%d  _y_gave_labelling=%d ylabelling=%f   present=%f\n", __FILE__, __LINE__, _y_gave_labelling, ylabelling, present);
686 #endif
687 		while (next_tic(&next, yb, _y_gave_labelling, present, final, smallinc, _grTransform_y, increasing)) {
688                         if (this_pass++ > pass_max) {
689                                 extern bool _y_gave_labelling;
690                                 if (_y_gave_labelling) {
691                                         gr_Error("cannot draw y axis (internal error: cannot use 'labelling' keyword)");
692                                 } else {
693                                         gr_Error("cannot draw y axis (internal error)");
694                                 }
695                                 return;
696                         }
697 			double          tmp, next_log;
698 			axis_path.push_back(x, next, 'l');
699 			next_log = log10(next);
700 			tmp = next_log - floor(next_log);
701 			gr_usertocm(x, next, &xcm2, &ycm2);	// NOTE: not using (xcm,ycm)
702 			if (-0.01 < tmp && tmp < 0.01) {
703 				// large tic & number
704 				double xuser, yuser;
705 				gr_cmtouser(xcm2 + tic, ycm2, &xuser, &yuser);
706 				axis_path.push_back(xuser, yuser, 'l');
707 				gr_cmtouser(xcm2 + tic, ycm2 - 0.5 * FACTOR * CapHeight, &xuser, &yuser);
708 				tmp = next_log - decade_between_labels * floor(next_log / decade_between_labels);
709 				if (!user_gave_labels
710                                     && gr_currentfontsize_pt() > SMALLFONTSIZE
711                                     && -0.01 < tmp / yinc
712                                     && tmp / yinc< 0.01
713 				    && strlen(_grNumFormat_y)) {
714 					// Draw "1" as a special case
715 					if (0.99 < next && next < 1.01)
716 						sprintf(_grTempString, "1");
717 					else
718 						sprintf(_grTempString, "$10^{%.0f}$", log10(next));
719 					slabel.assign(_grTempString);
720 					fix_negative_zero(slabel);
721 					label.fromSTR(slabel.c_str());
722 					if (side == gr_axis_LEFT)
723 						label.draw(xcm2 + offset, ycm2 - 0.5 * CapHeight, TEXT_RJUST, 0.0);
724 					else
725 						label.draw(xcm2 + offset, ycm2 - 0.5 * CapHeight, TEXT_LJUST, 0.0);
726 					// Keep track of maximum width of axis numbers, so that
727 					// axis label can be offset right amount.
728 					gr_stringwidth(_grTempString, &tmp0, &tmp1, &tmp2);
729 					if (tmp0 > max_num_width_cm)
730 						max_num_width_cm = tmp0;
731 				}
732 			} else {
733 				// small tic
734 				double xuser, yuser;
735 				gr_cmtouser(xcm2 + tic_sml, ycm2, &xuser, &yuser);
736 				axis_path.push_back(xuser, yuser, 'l');
737 			}
738 			axis_path.push_back(x, next, 'l');
739 			present = next;
740 		}
741 		if (user_gave_labels) {
742 			angle = 0;
743 			for (unsigned int i = 0; i < _y_labels.size(); i++) {
744 				if (BETWEEN(yb, yt, _y_label_positions[i])) {
745 					label.fromSTR(_y_labels[i].c_str()); // BUG: should interpolate into this string
746 					gr_usertocm(x, _y_label_positions[i], &xcm, &ycm);
747 					labelx_cm = xcm + offset * cos(angle);
748 					labely_cm = ycm + offset * sin(angle) - 0.5 * CapHeight;
749 
750 #ifdef DEBUG_LABELS
751 					printf("DEBUG: %s:%d drawing %d-th label '%s' at y=%f angle=%f\n",__FILE__,__LINE__,i,_y_labels[i].c_str(),_y_label_positions[i],angle);
752 #endif
753 					if (side == gr_axis_LEFT)
754 						label.draw(labelx_cm, labely_cm, TEXT_RJUST, DEG_PER_RAD * angle);
755 					else
756 						label.draw(labelx_cm, labely_cm, TEXT_LJUST, DEG_PER_RAD * angle);
757 				} else {
758 #ifdef DEBUG_LABELS
759 					//printf("DEBUG: %s:%d SKIPPING %d-th label '%s' since x=%f\n",__FILE__,__LINE__,i,_y_labels[i].c_str(),_y_label_positions[i]);
760 #endif
761 				}
762 			}
763 		}
764 		// Finish by drawing to end of axis (in case there was no tic there).
765 		axis_path.push_back(x, final, 'l');
766 		axis_path.stroke(units_user, _griState.linewidth_axis());
767 		break;
768 	default:
769 		gr_Error("unknown axis type (internal error)");
770 	}
771 	// write label, rotated if necessary
772 	if (gr_currentfontsize_pt() > SMALLFONTSIZE) {
773 		// Start to calculate what x to put label at; this makes xcm be on
774 		// axis, so will have to shift depending on orientation of label.
775 		// Note: will now re-use 'angle' to mean angle of y axis
776 
777 		if (_grTransform_y == gr_axis_LOG) {
778 			double          x_cm, xx_cm, y_cm, yy_cm;
779 			gr_usertocm(x, sqrt(yb * yt), &x_cm, &y_cm);
780 			gr_usertocm(x, 0.001 + sqrt(yb * yt), &xx_cm, &yy_cm);
781 			angle = fabs(DEG_PER_RAD * atan2(yy_cm - y_cm, xx_cm - x_cm)); // abs() ensures from bottom to top
782 		} else {
783 			double          x_cm, xx_cm, y_cm, yy_cm;
784 			gr_usertocm(x, 0.5 * (yb + yt), &x_cm, &y_cm);
785 			gr_usertocm(x, 0.01 * yinc + 0.5 * (yb + yt), &xx_cm, &yy_cm);
786 			angle = DEG_PER_RAD * atan2(yy_cm - y_cm, xx_cm - x_cm);
787 		}
788 		xcm = 0.5 * (gr_usertocm_x(x, yb) + gr_usertocm_x(x, yt));
789 		ycm = 0.5 * (gr_usertocm_y(x, yb) + gr_usertocm_y(x, yt));
790 		// Need  at least max_num_width_cm, i.e., widest numeric label, plus
791 		// a little space (check against above).
792 		max_num_width_cm += FACTOR * CapHeight;
793 		// Need space for tics too
794 		max_num_width_cm += (_grTicsPointIn == true ? 0.0 : _grTicSize_cm);
795 		// Do by cases -- inelegant but flexible to change
796 		label.fromSTR(_grYAxisLabel);
797 		switch (_grAxisStyle_y) {
798 		default:
799 		case 0:		// label parallel to axis
800 			if (side == gr_axis_LEFT) {
801 				label.draw(xcm - max_num_width_cm,
802 					   ycm,
803 					   TEXT_CENTERED, angle);
804 			} else {
805 				label.draw(xcm + max_num_width_cm,
806 					   ycm,
807 					   TEXT_CENTERED, angle - 180);
808 			}
809 			break;
810 		case 1:			// horizontal label
811 			if (side == gr_axis_LEFT) {
812 				label.draw(xcm - max_num_width_cm,
813 					   ycm - 0.5 * CapHeight,
814 					   TEXT_RJUST, 90.0 - angle);
815 			} else {
816 				label.draw(xcm + max_num_width_cm,
817 					   ycm - 0.5 * CapHeight,
818 					   TEXT_LJUST, 90.0 - angle);
819 			}
820 			break;
821 		}
822 	}
823 	_griState.set_line_cap(old_linecap);
824 	_griState.set_line_join(old_linejoin);
825 }
826 
827 #if 0
828 /* UNUSED -- Delete later BUG ?? */
829 #define NUM_LABEL 100
830 /*
831  * create_labels - make clean labels for linear axes (clean by making same
832  * number of decimals)
833  */
834 static int
835 create_labels(double y, double yb, double yinc, double yt, double smallinc,
836 	      char *label[NUM_LABEL],
837 	      int *num_label)
838 {
839 	int             i, max_decimals = 0;
840 	double          zero = fabs(yt - yb) / 1.0e5;	/* store a number
841 							 * effectively 0 */
842 	*num_label = 0;
843 	do {
844 		if (gr_multiple(y, yinc, 0.01 * smallinc)) {
845 			if (fabs(y) < zero)
846 				sprintf(_grTempString, _grNumFormat_y, 0.0);
847 			else
848 				sprintf(_grTempString, _grNumFormat_y, y);
849 			/* reserve 10 extra characters for 0s which might be appended */
850 			GET_STORAGE(label[*num_label],
851 				    char,
852 				    10 + strlen(_grTempString));
853 			strcpy(label[*num_label], _grTempString);
854 			(*num_label)++;
855 		}
856 		y += smallinc;
857 	} while ((*num_label < NUM_LABEL)
858 		 && ((yinc > 0.0 && y <= yt)
859 		     || (yinc < 0.0 && y >= yt)));
860 	if (*num_label >= NUM_LABEL) {
861 		fprintf(stderr, "Internal error (graxes.c): %d > NUM_LABEL labels\n",
862 			*num_label);
863 		gri_exit(1);
864 	}
865 	for (i = 0; i < *num_label; i++) {
866 		int             j;
867 		if (max_decimals < (j = num_decimals(label[i])))
868 			max_decimals = j;
869 	}
870 	for (i = 0; i < *num_label; i++) {
871 		int             n = num_decimals(label[i]);
872 		int             l = strlen(label[i]);
873 		if (!n)			/* don't meddle if e/E/d/D present */
874 			continue;
875 		if (max_decimals > 0) {
876 			if (n < max_decimals) {
877 				int             j;
878 				if (n == 0) {
879 					*(label[i] + l++) = '.';
880 					n = 1;
881 				}
882 				for (j = max_decimals - n; j < max_decimals; j++)
883 					*(label[i] + l++) = '0';
884 			}
885 			*(label[i] + l) = '\0';
886 		}
887 	}
888 	return 1;
889 }
890 #endif
891 
892 #if 0
893 /*
894  * num_decimals(s) -- return number of places to right of decimal, but return
895  * 0 if there is an e/E/d/D in the number (in which case don't meddle
896  */
897 static int
898 num_decimals(char *s)
899 {
900 	char *            cp;
901 	int             j = 0, jMax = strlen(s);
902 	/*
903 	 * First search for e/E/d/D
904 	 */
905 	cp = s;
906 	do
907 		if (*cp == 'd' || *cp == 'D' || *cp == 'e' || *cp == 'E')
908 			return 0;
909 	while (*++cp != '\0');
910 	/*
911 	 * It doesn't have e/E/d/D
912 	 */
913 	cp = s + strlen(s) - 1;
914 	do
915 		if (*cp == '.')
916 			break;
917 	while (cp--, ++j < jMax);
918 	if (j >= jMax)
919 		j = 0;
920 	return j;
921 }
922 #endif
923