xref: /plan9/sys/src/cmd/grap/ticks.c (revision 3e12c5d1bb89fc02707907988834ef147769ddaf)
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <math.h>
5 #include "grap.h"
6 #include "y.tab.h"
7 
8 #define	MAXTICK	200
9 int	ntick	= 0;
10 double	tickval[MAXTICK];	/* tick values (one axis at a time */
11 char	*tickstr[MAXTICK];	/* and labels */
12 
13 int	tside	= 0;
14 int	tlist	= 0;		/* 1 => explicit values given */
15 int	toffside = 0;		/* no ticks on these sides */
16 int	goffside = 0;		/* no ticks on grid on these sides */
17 int	tick_dir = OUT;
18 double	ticklen	= TICKLEN;	/* default tick length */
19 int	autoticks = LEFT|BOT;
20 int	autodir = 0;		/* set LEFT, etc. if automatic ticks go in */
21 
savetick(double f,char * s)22 void savetick(double f, char *s)	/* remember tick location and label */
23 {
24 	if (ntick >= MAXTICK)
25 		ERROR "too many ticks (%d)", MAXTICK FATAL;
26 	tickval[ntick] = f;
27 	tickstr[ntick] = s;
28 	ntick++;
29 }
30 
dflt_tick(double f)31 void dflt_tick(double f)
32 {
33 	if (f >= 0.0)
34 		savetick(f, tostring("%g"));
35 	else
36 		savetick(f, tostring("\\%g"));
37 }
38 
tickside(int n)39 void tickside(int n)	/* remember which side these ticks/gridlines go on */
40 {
41 	tside |= n;
42 }
43 
tickoff(int side)44 void tickoff(int side)	/* remember explicit sides */
45 {
46 	toffside |= side;
47 }
48 
gridtickoff(void)49 void gridtickoff(void)	/* turn grid ticks off on the side previously specified (ugh) */
50 {
51 	goffside = tside;
52 }
53 
setlist(void)54 void setlist(void)	/* remember that there was an explicit list */
55 {
56 	tlist = 1;
57 }
58 
tickdir(int dir,double val,int explicit)59 void tickdir(int dir, double val, int explicit)	/* remember in/out [expr] */
60 {
61 	tick_dir = dir;
62 	if (explicit)
63 		ticklen = val;
64 }
65 
ticks(void)66 void ticks(void)		/* set autoticks after ticks statement */
67 {
68 	/* was there an explicit "ticks [side] off"? */
69 	if (toffside)
70 		autoticks &= ~toffside;
71 	/* was there an explicit list? (eg "ticks at ..." or "ticks from ...") */
72 	if (tlist) {
73 		if (tside & (BOT|TOP))
74 			autoticks &= ~(BOT|TOP);
75 		if (tside & (LEFT|RIGHT))
76 			autoticks &= ~(LEFT|RIGHT);
77 	}
78 	/* was there a side without a list? (eg "ticks left in") */
79 	if (tside && !tlist) {
80 		if (tick_dir == IN)
81 			autodir |= tside;
82 		if (tside & (BOT|TOP))
83 			autoticks = (autoticks & ~(BOT|TOP)) | (tside & (BOT|TOP));
84 		if (tside & (LEFT|RIGHT))
85 			autoticks = (autoticks & ~(LEFT|RIGHT)) | (tside & (LEFT|RIGHT));
86 	}
87 	tlist = tside = toffside = goffside = 0;
88 	tick_dir = OUT;
89 }
90 
modfloor(double f,double t)91 double modfloor(double f, double t)
92 {
93 	t = fabs(t);
94 	return floor(f/t) * t;
95 }
96 
modceil(double f,double t)97 double modceil(double f, double t)
98 {
99 	t = fabs(t);
100 	return ceil(f/t) * t;
101 }
102 
103 double	xtmin, xtmax;	/* range of ticks */
104 double	ytmin, ytmax;
105 double	xquant, xmult;	/* quantization & scale for auto x ticks */
106 double	yquant, ymult;
107 double	lograt = 5;
108 
do_autoticks(Obj * p)109 void do_autoticks(Obj *p)	/* make set of ticks for default coord only */
110 {
111 	double x, xl, xu, q;
112 
113 	if (p == NULL)
114 		return;
115 	fprintf(tfd, "Autoticks:\t# x %g..%g, y %g..%g",
116 		p->pt.x, p->pt1.x, p->pt.y, p->pt1.y);
117 	fprintf(tfd, ";   xt %g,%g, yt %g,%g, xq,xm = %g,%g, yq,ym = %g,%g\n",
118 		xtmin, xtmax, ytmin, ytmax, xquant, xmult, yquant, ymult);
119 	if ((autoticks & (BOT|TOP)) && p->pt1.x >= p->pt.x) {	/* make x ticks */
120 		q = xquant;
121 		xl = p->pt.x;
122 		xu = p->pt1.x;
123 		if (xl >= xu)
124 			dflt_tick(xl);
125 		else if ((p->log & XFLAG) && xu/xl >= lograt) {
126 			for (x = q; x < xu; x *= 10) {
127 				logtick(x, xl, xu);
128 				if (xu/xl <= 100) {
129 					logtick(2*x, xl, xu);
130 					logtick(5*x, xl, xu);
131 				}
132 			}
133 		} else {
134 			xl = modceil(xtmin - q/100, q);
135 			xu = modfloor(xtmax + q/100, q) + q/2;
136 			for (x = xl; x <= xu; x += q)
137 				dflt_tick(x);
138 		}
139 		tside = autoticks & (BOT|TOP);
140 		ticklist(p, 0);
141 	}
142 	if ((autoticks & (LEFT|RIGHT)) && p->pt1.y >= p->pt.y) {	/* make y ticks */
143 		q = yquant;
144 		xl = p->pt.y;
145 		xu = p->pt1.y;
146 		if (xl >= xu)
147 			dflt_tick(xl);
148 		else if ((p->log & YFLAG) && xu/xl >= lograt) {
149 			for (x = q; x < xu; x *= 10) {
150 				logtick(x, xl, xu);
151 				if (xu/xl <= 100) {
152 					logtick(2*x, xl, xu);
153 					logtick(5*x, xl, xu);
154 				}
155 			}
156 		} else {
157 			xl = modceil(ytmin - q/100, q);
158 			xu = modfloor(ytmax + q/100, q) + q/2;
159 			for (x = xl; x <= xu; x += q)
160 				dflt_tick(x);
161 		}
162 		tside = autoticks & (LEFT|RIGHT);
163 		ticklist(p, 0);
164 	}
165 }
166 
logtick(double v,double lb,double ub)167 void logtick(double v, double lb, double ub)
168 {
169 	float slop = 1.0;	/* was 1.001 */
170 
171 	if (slop * lb <= v && ub >= slop * v)
172 		dflt_tick(v);
173 }
174 
setauto(void)175 Obj *setauto(void)	/* compute new min,max, and quant & mult */
176 {
177 	Obj *p, *q;
178 
179 	if ((q = lookup("lograt",0)) != NULL)
180 		lograt = q->fval;
181 	for (p = objlist; p; p = p->next)
182 		if (p->type == NAME && strcmp(p->name,dflt_coord) == 0)
183 			break;
184 	if (p) {
185 		if ((p->log & XFLAG) && p->pt1.x/p->pt.x >= lograt)
186 			autolog(p, 'x');
187 		else
188 			autoside(p, 'x');
189 		if ((p->log & YFLAG) && p->pt1.y/p->pt.y >= lograt)
190 			autolog(p, 'y');
191 		else
192 			autoside(p, 'y');
193 	}
194 	return p;
195 }
196 
autoside(Obj * p,int side)197 void autoside(Obj *p, int side)
198 {
199 	double r, s, d, ub, lb;
200 
201 	if (side == 'x') {
202 		xtmin = lb = p->pt.x;
203 		xtmax = ub = p->pt1.x;
204 	} else {
205 		ytmin = lb = p->pt.y;
206 		ytmax = ub = p->pt1.y;
207 	}
208 	if (ub <= lb)
209 		return;	/* cop out on little ranges */
210 	d = ub - lb;
211 	r = s = 1;
212 	while (d * s < 10)
213 		s *= 10;
214 	d *= s;
215 	while (10 * r < d)
216 		r *= 10;
217 	if (r > d/3)
218 		r /= 2;
219 	else if (r <= d/6)
220 		r *= 2;
221 	if (side == 'x') {
222 		xquant = r / s;
223 	} else {
224 		yquant = r / s;
225 	}
226 }
227 
autolog(Obj * p,int side)228 void autolog(Obj *p, int side)
229 {
230 	double r, s, t, ub, lb;
231 	int flg;
232 
233 	if (side == 'x') {
234 		xtmin = lb = p->pt.x;
235 		xtmax = ub = p->pt1.x;
236 		flg = p->coord & XFLAG;
237 	} else {
238 		ytmin = lb = p->pt.y;
239 		ytmax = ub = p->pt1.y;
240 		flg = p->coord & YFLAG;
241 	}
242 	for (s = 1; lb * s < 1; s *= 10)
243 		;
244 	lb *= s;
245 	ub *= s;
246 	for (r = 1; 10 * r < lb; r *= 10)
247 		;
248 	for (t = 1; t < ub; t *= 10)
249 		;
250 	if (side == 'x')
251 		xquant = r / s;
252 	else
253 		yquant = r / s;
254 	if (flg)
255 		return;
256 	if (ub / lb < 100) {
257 		if (lb >= 5 * r)
258 			r *= 5;
259 		else if (lb >= 2 * r)
260 			r *= 2;
261 		if (ub * 5 <= t)
262 			t /= 5;
263 		else if (ub * 2 <= t)
264 			t /= 2;
265 		if (side == 'x') {
266 			xtmin = r / s;
267 			xtmax = t / s;
268 		} else {
269 			ytmin = r / s;
270 			ytmax = t / s;
271 		}
272 	}
273 }
274 
iterator(double from,double to,int op,double by,char * fmt)275 void iterator(double from, double to, int op, double by, char *fmt)	/* create an iterator */
276 {
277 	double x;
278 
279 	/* should validate limits, etc. */
280 	/* punt for now */
281 
282 	dprintf("iterate from %g to %g by %g, op = %c, fmt=%s\n",
283 		from, to, by, op, fmt ? fmt : "");
284 	switch (op) {
285 	case '+':
286 	case ' ':
287 		for (x = from; x <= to + (SLOP-1) * by; x += by)
288 			if (fmt)
289 				savetick(x, tostring(fmt));
290 			else
291 				dflt_tick(x);
292 		break;
293 	case '-':
294 		for (x = from; x >= to; x -= by)
295 			if (fmt)
296 				savetick(x, tostring(fmt));
297 			else
298 				dflt_tick(x);
299 		break;
300 	case '*':
301 		for (x = from; x <= SLOP * to; x *= by)
302 			if (fmt)
303 				savetick(x, tostring(fmt));
304 			else
305 				dflt_tick(x);
306 		break;
307 	case '/':
308 		for (x = from; x >= to; x /= by)
309 			if (fmt)
310 				savetick(x, tostring(fmt));
311 			else
312 				dflt_tick(x);
313 		break;
314 	}
315 	if (fmt)
316 		free(fmt);
317 }
318 
ticklist(Obj * p,int explicit)319 void ticklist(Obj *p, int explicit)	/* fire out the accumulated ticks */
320 					/* 1 => list, 0 => auto */
321 {
322 	if (p == NULL)
323 		return;
324 	fprintf(tfd, "Ticks_%s:\n\tticklen = %g\n", p->name, ticklen);
325 	print_ticks(TICKS, explicit, p, "ticklen", "");
326 }
327 
print_ticks(int type,int explicit,Obj * p,char * lenstr,char * descstr)328 void print_ticks(int type, int explicit, Obj *p, char *lenstr, char *descstr)
329 {
330 	int i, logflag, inside;
331 	char buf[100];
332 	double tv;
333 
334 	for (i = 0; i < ntick; i++)	/* any ticks given explicitly? */
335 		if (tickstr[i] != NULL)
336 			break;
337 	if (i >= ntick && type == TICKS)	/* no, so use values */
338 		for (i = 0; i < ntick; i++) {
339 			if (tickval[i] >= 0.0)
340 				sprintf(buf, "%g", tickval[i]);
341 			else
342 				sprintf(buf, "\\-%g", -tickval[i]);
343 			tickstr[i] = tostring(buf);
344 		}
345 	else
346 		for (i = 0; i < ntick; i++) {
347 			if (tickstr[i] != NULL) {
348 				sprintf(buf, tickstr[i], tickval[i]);
349 				free(tickstr[i]);
350 				tickstr[i] = tostring(buf);
351 			}
352 		}
353 	logflag = sidelog(p->log, tside);
354 	for (i = 0; i < ntick; i++) {
355 		tv = tickval[i];
356 		halfrange(p, tside, tv);
357 		if (logflag) {
358 			if (tv <= 0.0)
359 				ERROR "can't take log of tick value %g", tv FATAL;
360 			logit(tv);
361 		}
362 		if (type == GRID)
363 			inside = LEFT|RIGHT|TOP|BOT;
364 		else if (explicit)
365 			inside = (tick_dir == IN) ? tside : 0;
366 		else
367 			inside = autodir;
368 		if (tside & BOT)
369 			maketick(type, p->name, BOT, inside, tv, tickstr[i], lenstr, descstr);
370 		if (tside & TOP)
371 			maketick(type, p->name, TOP, inside, tv, tickstr[i], lenstr, descstr);
372 		if (tside & LEFT)
373 			maketick(type, p->name, LEFT, inside, tv, tickstr[i], lenstr, descstr);
374 		if (tside & RIGHT)
375 			maketick(type, p->name, RIGHT, inside, tv, tickstr[i], lenstr, descstr);
376 		if (tickstr[i]) {
377 			free(tickstr[i]);
378 			tickstr[i] = NULL;
379 		}
380 	}
381 	ntick = 0;
382 }
383 
maketick(int type,char * name,int side,int inflag,double val,char * lab,char * lenstr,char * descstr)384 void maketick(int type, char *name, int side, int inflag, double val, char *lab, char *lenstr, char *descstr)
385 {
386 	char *sidestr, *td;
387 
388 	fprintf(tfd, "\tline %s ", descstr);
389 	inflag &= side;
390 	switch (side) {
391 	case BOT:
392 	case 0:
393 		td = inflag ? "up" : "down";
394 		fprintf(tfd, "%s %s from (x_%s(%g),0)", td, lenstr, name, val);
395 		break;
396 	case TOP:
397 		td = inflag ? "down" : "up";
398 		fprintf(tfd, "%s %s from (x_%s(%g),frameht)", td, lenstr, name, val);
399 		break;
400 	case LEFT:
401 		td = inflag ? "right" : "left";
402 		fprintf(tfd, "%s %s from (0,y_%s(%g))", td, lenstr, name, val);
403 		break;
404 	case RIGHT:
405 		td = inflag ? "left" : "right";
406 		fprintf(tfd, "%s %s from (framewid,y_%s(%g))", td, lenstr, name, val);
407 		break;
408 	}
409 	fprintf(tfd, "\n");
410 	if (type == GRID && (side & goffside))	/* wanted no ticks on grid */
411 		return;
412 	sidestr = tick_dir == IN ? "start" : "end";
413 	if (lab != NULL) {
414 		/* BUG: should fix size of lab here */
415 		double wid = strlen(lab)/7.5 + (tick_dir == IN ? 0 : 0.1);	/* estimate width at 15 chars/inch */
416 		switch (side) {
417 		case BOT: case 0:
418 			/* can drop "box invis" with new pic */
419 			fprintf(tfd, "\tbox invis \"%s\" ht .25 wid 0 with .n at last line.%s",
420 				lab, sidestr);
421 			break;
422 		case TOP:
423 			fprintf(tfd, "\tbox invis \"%s\" ht .2 wid 0 with .s at last line.%s",
424 				lab, sidestr);
425 			break;
426 		case LEFT:
427 			fprintf(tfd, "\t\"%s \" wid %.2f rjust at last line.%s",
428 				lab, wid, sidestr);
429 			break;
430 		case RIGHT:
431 			fprintf(tfd, "\t\" %s\" wid %.2f ljust at last line.%s",
432 				lab, wid, sidestr);
433 			break;
434 		}
435 		/* BUG: works only if "down x" comes before "at wherever" */
436 		lab_adjust();
437 		fprintf(tfd, "\n");
438 	}
439 }
440 
441 Attr	*grid_desc	= 0;
442 
griddesc(Attr * a)443 void griddesc(Attr *a)
444 {
445 	grid_desc = a;
446 }
447 
gridlist(Obj * p)448 void gridlist(Obj *p)
449 {
450 	char *framestr;
451 
452 	if ((tside & (BOT|TOP)) || tside == 0)
453 		framestr = "frameht";
454 	else
455 		framestr = "framewid";
456 	fprintf(tfd, "Grid_%s:\n", p->name);
457 	tick_dir = IN;
458 	print_ticks(GRID, 0, p, framestr, desc_str(grid_desc));
459 	if (grid_desc) {
460 		freeattr(grid_desc);
461 		grid_desc = 0;
462 	}
463 }
464 
desc_str(Attr * a)465 char *desc_str(Attr *a)	/* convert DOT to "dotted", etc. */
466 {
467 	static char buf[50], *p;
468 
469 	if (a == NULL)
470 		return p = "";
471 	switch (a->type) {
472 	case DOT:	p = "dotted"; break;
473 	case DASH:	p = "dashed"; break;
474 	case INVIS:	p = "invis"; break;
475 	default:	p = "";
476 	}
477 	if (a->fval != 0.0) {
478 		sprintf(buf, "%s %g", p, a->fval);
479 		return buf;
480 	} else
481 		return p;
482 }
483 
sidelog(int logflag,int side)484 sidelog(int logflag, int side)	/* figure out whether to scale a side */
485 {
486 	if ((logflag & XFLAG) && ((side & (BOT|TOP)) || side == 0))
487 		return 1;
488 	else if ((logflag & YFLAG) && (side & (LEFT|RIGHT)))
489 		return 1;
490 	else
491 		return 0;
492 }
493