Blob


1 #include <u.h>
2 #include <libc.h>
3 #include <ctype.h>
4 #include <draw.h>
5 #include <event.h>
6 #include <cursor.h>
7 #include <stdio.h>
9 #define Never 0xffffffff /* Maximum ulong */
10 #define LOG2 0.301029995664
11 #define Button_bit(b) (1 << ((b)-1))
13 enum {
14 But1 = Button_bit(1),/* mouse buttons for events */
15 But2 = Button_bit(2),
16 But3 = Button_bit(3),
17 };
18 int cantmv = 1; /* disallow rotate and move? 0..1 */
19 int top_border, bot_border, lft_border, rt_border;
20 int lft_border0; /* lft_border for y-axis labels >0 */
21 int top_left, top_right; /* edges of top line free space */
22 int Mv_delay = 400; /* msec for button click vs. button hold down */
23 int Dotrad = 2; /* dot radius in pixels */
24 int framewd=1; /* line thickness for frame (pixels) */
25 int framesep=1; /* distance between frame and surrounding text */
26 int outersep=1; /* distance: surrounding text to screen edge */
27 Point sdigit; /* size of a digit in the font */
28 Point smaxch; /* assume any character in font fits in this */
29 double underscan = .05; /* fraction of frame initially unused per side */
30 double fuzz = 6; /* selection tolerance in pixels */
31 int tick_len = 15; /* length of axis label tick mark in pixels */
32 FILE* logfil = 0; /* dump selected points here if nonzero */
34 #define labdigs 3 /* allow this many sig digits in axis labels */
35 #define digs10pow 1000 /* pow(10,labdigs) */
36 #define axis_color clr_im(DLtblue)
41 /********************************* Utilities *********************************/
43 /* Prepend string s to null-terminated string in n-byte buffer buf[], truncating if
44 necessary and using a space to separate s from the rest of buf[].
45 */
46 char* str_insert(char* buf, char* s, int n)
47 {
48 int blen, slen = strlen(s) + 1;
49 if (slen >= n)
50 {strncpy(buf,s,n); buf[n-1]='\0'; return buf;}
51 blen = strlen(buf);
52 if (blen >= n-slen)
53 buf[blen=n-slen-1] = '\0';
54 memmove(buf+slen, buf, slen+blen+1);
55 memcpy(buf, s, slen-1);
56 buf[slen-1] = ' ';
57 return buf;
58 }
60 /* Alter string smain (without lengthening it) so as to remove the first occurrence of
61 ssub, assuming ssub is ASCII. Return nonzero (true) if string smain had to be changed.
62 In spite of the ASCII-centric appearance, I think this can handle UTF in smain.
63 */
64 int remove_substr(char* smain, char* ssub)
65 {
66 char *ss, *s = strstr(smain, ssub);
67 int n = strlen(ssub);
68 if (s==0)
69 return 0;
70 if (islower(s[n]))
71 s[0] ^= 32; /* probably tolower(s[0]) or toupper(s[0]) */
72 else {
73 for (ss=s+n; *ss!=0; s++, ss++)
74 *s = *ss;
75 *s = '\0';
76 }
77 return 1;
78 }
80 void adjust_border(Font* f)
81 {
82 int sep = framesep + outersep;
83 sdigit = stringsize(f, "8");
84 smaxch = stringsize(f, "MMMg");
85 smaxch.x = (smaxch.x + 3)/4;
86 lft_border0 = (1+labdigs)*sdigit.x + framewd + sep;
87 rt_border = (lft_border0 - sep)/2 + outersep;
88 bot_border = sdigit.y + framewd + sep;
89 top_border = smaxch.y + framewd + sep;
90 lft_border = lft_border0; /* this gets reset later */
91 }
94 int is_off_screen(Point p)
95 {
96 const Rectangle* r = &(screen->r);
97 return p.x-r->min.x<lft_border || r->max.x-p.x<rt_border
98 || p.y-r->min.y<=top_border || r->max.y-p.y<=bot_border;
99 }
102 Cursor bullseye =
104 {-7, -7},
106 0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
107 0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
108 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
109 0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8,
110 },
112 0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
113 0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
114 0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
115 0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00,
117 };
119 int get_1click(int but, Mouse* m, Cursor* curs)
121 if (curs)
122 esetcursor(curs);
123 while (m->buttons==0)
124 *m = emouse();
125 if (curs)
126 esetcursor(0);
127 return (m->buttons==Button_bit(but));
131 /* Wait until but goes up or until a mouse event's msec passes tlimit.
132 Return a boolean result that tells whether the button went up.
133 */
134 int lift_button(int but, Mouse* m, int tlimit)
136 do { *m = emouse();
137 if (m->msec >= tlimit)
138 return 0;
139 } while (m->buttons & Button_bit(but));
140 return 1;
144 /* Set *m to the last pending mouse event, or the first one where but is up.
145 If no mouse events are pending, wait for the next one.
146 */
147 void latest_mouse(int but, Mouse* m)
149 int bbit = Button_bit(but);
150 do { *m = emouse();
151 } while ((m->buttons & bbit) && ecanmouse());
156 /*********************************** Colors ***********************************/
158 enum { DOrange=0xffaa00FF, Dgray=0xbbbbbbFF, DDkgreen=0x009900FF,
159 DDkred=0xcc0000FF, DViolet=0x990099FF, DDkyellow=0xaaaa00FF,
160 DLtblue=0xaaaaffFF, DPink=0xffaaaaFF,
161 /* ndraw.h sets DBlack, DBlue, DRed, DYellow, DGreen,
162 DCyan, DMagenta, DWhite */
163 };
165 typedef struct color_ref {
166 ulong c; /* RGBA pixel color */
167 char* nam; /* ASCII name (matched to input, used in output)*/
168 Image* im; /* replicated solid-color image */
169 } color_ref;
171 color_ref clrtab[] = {
172 DRed, "Red", 0,
173 DPink, "Pink", 0,
174 DDkred, "Dkred", 0,
175 DOrange, "Orange", 0,
176 DYellow, "Yellow", 0,
177 DDkyellow, "Dkyellow", 0,
178 DGreen, "Green", 0,
179 DDkgreen, "Dkgreen", 0,
180 DCyan, "Cyan", 0,
181 DBlue, "Blue", 0,
182 DLtblue, "Ltblue", 0,
183 DMagenta, "Magenta", 0,
184 DViolet, "Violet", 0,
185 Dgray, "Gray", 0,
186 DBlack, "Black", 0,
187 DWhite, "White", 0,
188 DNofill, 0, 0 /* DNofill means "end of data" */
189 };
192 void init_clrtab(void)
194 int i;
195 Rectangle r = Rect(0,0,1,1);
196 for (i=0; clrtab[i].c!=DNofill; i++)
197 clrtab[i].im = allocimage(display, r, CMAP8, 1, clrtab[i].c);
198 /* should check for 0 result? */
202 int clrim_id(Image* clr)
204 int i;
205 for (i=0; clrtab[i].im!=clr; i++)
206 if (clrtab[i].c==DNofill)
207 exits("bad image color");
208 return i;
211 int clr_id(int clr)
213 int i;
214 for (i=0; clrtab[i].c!=clr; i++)
215 if (clrtab[i].c==DNofill)
216 exits("bad color");
217 return i;
220 #define clr_im(clr) clrtab[clr_id(clr)].im
223 /* This decides what color to use for a polyline based on the label it has in the
224 input file. Whichever color name comes first is the winner, otherwise return black.
225 */
226 Image* nam2clr(const char* nam, int *idxdest)
228 char *c, *cbest=(char*)nam;
229 int i, ibest=-1;
230 if (*nam!=0)
231 for (i=0; clrtab[i].nam!=0; i++) {
232 c = strstr(nam,clrtab[i].nam);
233 if (c!=0 && (ibest<0 || c<cbest))
234 {ibest=i; cbest=c;}
236 if (idxdest!=0)
237 *idxdest = (ibest<0) ? clr_id(DBlack) : ibest;
238 return (ibest<0) ? clr_im(DBlack) : clrtab[ibest].im;
241 /* A polyline is initial drawn in thick mode iff its label in the file contains "Thick" */
242 int nam2thick(const char* nam)
244 return strstr(nam,"Thick")==0 ? 0 : 1;
248 /* Alter string nam so that nam2thick() and nam2clr() agree with th and clr, using
249 buf[] (a buffer of length bufn) to store the result if it differs from nam.
250 We go to great pains to perform this alteration in a manner that will seem natural
251 to the user, i.e., we try removing a suitably isolated color name before inserting
252 a new one.
253 */
254 char* nam_with_thclr(char* nam, int th, Image* clr, char* buf, int bufn)
256 int clr0i, th0=nam2thick(nam);
257 Image* clr0 = nam2clr(nam, &clr0i);
258 char *clr0s;
259 if (th0==th && clr0==clr)
260 return nam;
261 clr0s = clrtab[clr0i].nam;
262 if (strlen(nam)<bufn) strcpy(buf,nam);
263 else {strncpy(buf,nam,bufn); buf[bufn-1]='\0';}
264 if (clr0 != clr)
265 remove_substr(buf, clr0s);
266 if (th0 > th)
267 while (remove_substr(buf, "Thick"))
268 /* do nothing */;
269 if (nam2clr(buf,0) != clr)
270 str_insert(buf, clrtab[clrim_id(clr)].nam, bufn);
271 if (th0 < th)
272 str_insert(buf, "Thick", bufn);
273 return buf;
278 /****************************** Data structures ******************************/
280 Image* mv_bkgd; /* Background image (usually 0) */
282 typedef struct fpoint {
283 double x, y;
284 } fpoint;
286 typedef struct frectangle {
287 fpoint min, max;
288 } frectangle;
290 frectangle empty_frect = {1e30, 1e30, -1e30, -1e30};
293 /* When *r2 is transformed by y=y-x*slant, might it intersect *r1 ?
294 */
295 int fintersects(const frectangle* r1, const frectangle* r2, double slant)
297 double x2min=r2->min.x, x2max=r2->max.x;
298 if (r1->max.x <= x2min || x2max <= r1->min.x)
299 return 0;
300 if (slant >=0)
301 {x2min*=slant; x2max*=slant;}
302 else {double t=x2min*slant; x2min=x2max*slant; x2max=t;}
303 return r1->max.y > r2->min.y-x2max && r2->max.y-x2min > r1->min.y;
306 int fcontains(const frectangle* r, fpoint p)
308 return r->min.x <=p.x && p.x<= r->max.x && r->min.y <=p.y && p.y<= r->max.y;
312 void grow_bb(frectangle* dest, const frectangle* r)
314 if (r->min.x < dest->min.x) dest->min.x=r->min.x;
315 if (r->min.y < dest->min.y) dest->min.y=r->min.y;
316 if (r->max.x > dest->max.x) dest->max.x=r->max.x;
317 if (r->max.y > dest->max.y) dest->max.y=r->max.y;
321 void slant_frect(frectangle *r, double sl)
323 r->min.y += sl*r->min.x;
324 r->max.y += sl*r->max.x;
328 fpoint fcenter(const frectangle* r)
330 fpoint c;
331 c.x = .5*(r->max.x + r->min.x);
332 c.y = .5*(r->max.y + r->min.y);
333 return c;
337 typedef struct fpolygon {
338 fpoint* p; /* a malloc'ed array */
339 int n; /* p[] has n elements: p[0..n] */
340 frectangle bb; /* bounding box */
341 char* nam; /* name of this polygon (malloc'ed) */
342 int thick; /* use 1+2*thick pixel wide lines */
343 Image* clr; /* Color to use when drawing this */
344 struct fpolygon* link;
345 } fpolygon;
347 typedef struct fpolygons {
348 fpolygon* p; /* the head of a linked list */
349 frectangle bb; /* overall bounding box */
350 frectangle disp; /* part being mapped onto screen->r */
351 double slant_ht; /* controls how disp is slanted */
352 } fpolygons;
355 fpolygons univ = { /* everything there is to display */
356 0,
357 1e30, 1e30, -1e30, -1e30,
358 0, 0, 0, 0,
359 2*1e30
360 };
363 void set_default_clrs(fpolygons* fps, fpolygon* fpstop)
365 fpolygon* fp;
366 for (fp=fps->p; fp!=0 && fp!=fpstop; fp=fp->link) {
367 fp->clr = nam2clr(fp->nam,0);
368 fp->thick = nam2thick(fp->nam);
373 void fps_invert(fpolygons* fps)
375 fpolygon *p, *r=0;
376 for (p=fps->p; p!=0;) {
377 fpolygon* q = p;
378 p = p->link;
379 q->link = r;
380 r = q;
382 fps->p = r;
386 void fp_remove(fpolygons* fps, fpolygon* fp)
388 fpolygon *q, **p = &fps->p;
389 while (*p!=fp)
390 if (*p==0)
391 return;
392 else p = &(*p)->link;
393 *p = fp->link;
394 fps->bb = empty_frect;
395 for (q=fps->p; q!=0; q=q->link)
396 grow_bb(&fps->bb, &q->bb);
400 /* The transform maps abstract fpoint coordinates (the ones used in the input)
401 to the current screen coordinates. The do_untransform() macros reverses this.
402 If univ.slant_ht is not the height of univ.disp, the actual region in the
403 abstract coordinates is a parallelogram inscribed in univ.disp with two
404 vertical edges and two slanted slanted edges: slant_ht>0 means that the
405 vertical edges have height slant_ht and the parallelogram touches the lower
406 left and upper right corners of univ.disp; slant_ht<0 refers to a parallelogram
407 of height -slant_ht that touches the other two corners of univ.disp.
408 NOTE: the ytransform macro assumes that tr->sl times the x coordinate has
409 already been subtracted from yy.
410 */
411 typedef struct transform {
412 double sl;
413 fpoint o, sc; /* (x,y):->(o.x+sc.x*x, o.y+sc.y*y+sl*x) */
414 } transform;
416 #define do_transform(d,tr,s) ((d)->x = (tr)->o.x + (tr)->sc.x*(s)->x, \
417 (d)->y = (tr)->o.y + (tr)->sc.y*(s)->y \
418 + (tr)->sl*(s)->x)
419 #define do_untransform(d,tr,s) ((d)->x = (.5+(s)->x-(tr)->o.x)/(tr)->sc.x, \
420 (d)->y = (.5+(s)->y-(tr)->sl*(d)->x-(tr)->o.y) \
421 /(tr)->sc.y)
422 #define xtransform(tr,xx) ((tr)->o.x + (tr)->sc.x*(xx))
423 #define ytransform(tr,yy) ((tr)->o.y + (tr)->sc.y*(yy))
424 #define dxuntransform(tr,xx) ((xx)/(tr)->sc.x)
425 #define dyuntransform(tr,yy) ((yy)/(tr)->sc.y)
428 transform cur_trans(void)
430 transform t;
431 Rectangle d = screen->r;
432 const frectangle* s = &univ.disp;
433 double sh = univ.slant_ht;
434 d.min.x += lft_border;
435 d.min.y += top_border;
436 d.max.x -= rt_border;
437 d.max.y -= bot_border;
438 t.sc.x = (d.max.x - d.min.x)/(s->max.x - s->min.x);
439 t.sc.y = -(d.max.y - d.min.y)/fabs(sh);
440 if (sh > 0) {
441 t.sl = -t.sc.y*(s->max.y-s->min.y-sh)/(s->max.x - s->min.x);
442 t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->max.x;
443 } else {
444 t.sl = t.sc.y*(s->max.y-s->min.y+sh)/(s->max.x - s->min.x);
445 t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->min.x;
447 t.o.x = d.min.x - t.sc.x*s->min.x;
448 return t;
452 double u_slant_amt(fpolygons *u)
454 double sh=u->slant_ht, dy=u->disp.max.y - u->disp.min.y;
455 double dx = u->disp.max.x - u->disp.min.x;
456 return (sh>0) ? (dy-sh)/dx : -(dy+sh)/dx;
460 /* Set *y0 and *y1 to the lower and upper bounds of the set of y-sl*x values that
461 *u says to display, where sl is the amount of slant.
462 */
463 double set_unslanted_y(fpolygons *u, double *y0, double *y1)
465 double yy1, sl=u_slant_amt(u);
466 if (u->slant_ht > 0) {
467 *y0 = u->disp.min.y - sl*u->disp.min.x;
468 yy1 = *y0 + u->slant_ht;
469 } else {
470 yy1 = u->disp.max.y - sl*u->disp.min.x;
471 *y0 = yy1 + u->slant_ht;
473 if (y1 != 0)
474 *y1 = yy1;
475 return sl;
481 /*************************** The region to display ****************************/
483 void nontrivial_interval(double *lo, double *hi)
485 if (*lo >= *hi) {
486 double mid = .5*(*lo + *hi);
487 double tweak = 1e-6 + 1e-6*fabs(mid);
488 *lo = mid - tweak;
489 *hi = mid + tweak;
494 void init_disp(void)
496 double dw = (univ.bb.max.x - univ.bb.min.x)*underscan;
497 double dh = (univ.bb.max.y - univ.bb.min.y)*underscan;
498 univ.disp.min.x = univ.bb.min.x - dw;
499 univ.disp.min.y = univ.bb.min.y - dh;
500 univ.disp.max.x = univ.bb.max.x + dw;
501 univ.disp.max.y = univ.bb.max.y + dh;
502 nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x);
503 nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y);
504 univ.slant_ht = univ.disp.max.y - univ.disp.min.y; /* means no slant */
508 void recenter_disp(Point c)
510 transform tr = cur_trans();
511 fpoint cc, off;
512 do_untransform(&cc, &tr, &c);
513 off.x = cc.x - .5*(univ.disp.min.x + univ.disp.max.x);
514 off.y = cc.y - .5*(univ.disp.min.y + univ.disp.max.y);
515 univ.disp.min.x += off.x;
516 univ.disp.min.y += off.y;
517 univ.disp.max.x += off.x;
518 univ.disp.max.y += off.y;
522 /* Find the upper-left and lower-right corners of the bounding box of the
523 parallelogram formed by untransforming the rectangle rminx, rminy, ... (given
524 in screen coordinates), and return the height of the parallelogram (negated
525 if it slopes downward).
526 */
527 double untransform_corners(double rminx, double rminy, double rmaxx, double rmaxy,
528 fpoint *ul, fpoint *lr)
530 fpoint r_ur, r_ul, r_ll, r_lr; /* corners of the given recangle */
531 fpoint ur, ll; /* untransformed versions of r_ur, r_ll */
532 transform tr = cur_trans();
533 double ht;
534 r_ur.x=rmaxx; r_ur.y=rminy;
535 r_ul.x=rminx; r_ul.y=rminy;
536 r_ll.x=rminx; r_ll.y=rmaxy;
537 r_lr.x=rmaxx; r_lr.y=rmaxy;
538 do_untransform(ul, &tr, &r_ul);
539 do_untransform(lr, &tr, &r_lr);
540 do_untransform(&ur, &tr, &r_ur);
541 do_untransform(&ll, &tr, &r_ll);
542 ht = ur.y - lr->y;
543 if (ll.x < ul->x)
544 ul->x = ll.x;
545 if (ur.y > ul->y)
546 ul->y = ur.y;
547 else ht = -ht;
548 if (ur.x > lr->x)
549 lr->x = ur.x;
550 if (ll.y < lr->y)
551 lr->y = ll.y;
552 return ht;
556 void disp_dozoom(double rminx, double rminy, double rmaxx, double rmaxy)
558 fpoint ul, lr;
559 double sh = untransform_corners(rminx, rminy, rmaxx, rmaxy, &ul, &lr);
560 if (ul.x==lr.x || ul.y==lr.y)
561 return;
562 univ.slant_ht = sh;
563 univ.disp.min.x = ul.x;
564 univ.disp.max.y = ul.y;
565 univ.disp.max.x = lr.x;
566 univ.disp.min.y = lr.y;
567 nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x);
568 nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y);
572 void disp_zoomin(Rectangle r)
574 disp_dozoom(r.min.x, r.min.y, r.max.x, r.max.y);
578 void disp_zoomout(Rectangle r)
580 double qminx, qminy, qmaxx, qmaxy;
581 double scx, scy;
582 Rectangle s = screen->r;
583 if (r.min.x==r.max.x || r.min.y==r.max.y)
584 return;
585 s.min.x += lft_border;
586 s.min.y += top_border;
587 s.max.x -= rt_border;
588 s.max.y -= bot_border;
589 scx = (s.max.x - s.min.x)/(r.max.x - r.min.x);
590 scy = (s.max.y - s.min.y)/(r.max.y - r.min.y);
591 qminx = s.min.x + scx*(s.min.x - r.min.x);
592 qmaxx = s.max.x + scx*(s.max.x - r.max.x);
593 qminy = s.min.y + scy*(s.min.y - r.min.y);
594 qmaxy = s.max.y + scy*(s.max.y - r.max.y);
595 disp_dozoom(qminx, qminy, qmaxx, qmaxy);
599 void expand2(double* a, double* b, double f)
601 double mid = .5*(*a + *b);
602 *a = mid + f*(*a - mid);
603 *b = mid + f*(*b - mid);
606 void disp_squareup(void)
608 double dx = univ.disp.max.x - univ.disp.min.x;
609 double dy = univ.disp.max.y - univ.disp.min.y;
610 dx /= screen->r.max.x - lft_border - screen->r.min.x - rt_border;
611 dy /= screen->r.max.y - bot_border - screen->r.min.y - top_border;
612 if (dx > dy)
613 expand2(&univ.disp.min.y, &univ.disp.max.y, dx/dy);
614 else expand2(&univ.disp.min.x, &univ.disp.max.x, dy/dx);
615 univ.slant_ht = univ.disp.max.y - univ.disp.min.y;
619 /* Slant so that p and q appear at the same height on the screen and the
620 screen contains the smallest possible superset of what its previous contents.
621 */
622 void slant_disp(fpoint p, fpoint q)
624 double yll, ylr, yul, yur; /* corner y coords of displayed parallelogram */
625 double sh, dy;
626 if (p.x == q.x)
627 return;
628 sh = univ.slant_ht;
629 if (sh > 0) {
630 yll=yul=univ.disp.min.y; yul+=sh;
631 ylr=yur=univ.disp.max.y; ylr-=sh;
632 } else {
633 yll=yul=univ.disp.max.y; yll+=sh;
634 ylr=yur=univ.disp.min.y; yur-=sh;
636 dy = (univ.disp.max.x-univ.disp.min.x)*(q.y - p.y)/(q.x - p.x);
637 dy -= ylr - yll;
638 if (dy > 0)
639 {yll-=dy; yur+=dy;}
640 else {yul-=dy; ylr+=dy;}
641 if (ylr > yll) {
642 univ.disp.min.y = yll;
643 univ.disp.max.y = yur;
644 univ.slant_ht = yur - ylr;
645 } else {
646 univ.disp.max.y = yul;
647 univ.disp.min.y = ylr;
648 univ.slant_ht = ylr - yur;
655 /******************************** Ascii input ********************************/
657 void set_fbb(fpolygon* fp)
659 fpoint lo=fp->p[0], hi=fp->p[0];
660 const fpoint *q, *qtop;
661 for (qtop=(q=fp->p)+fp->n; ++q<=qtop;) {
662 if (q->x < lo.x) lo.x=q->x;
663 if (q->y < lo.y) lo.y=q->y;
664 if (q->x > hi.x) hi.x=q->x;
665 if (q->y > hi.y) hi.y=q->y;
667 fp->bb.min = lo;
668 fp->bb.max = hi;
671 char* mystrdup(char* s)
673 char *r, *t = strrchr(s,'"');
674 if (t==0) {
675 t = s + strlen(s);
676 while (t>s && (t[-1]=='\n' || t[-1]=='\r'))
677 t--;
679 r = malloc(1+(t-s));
680 memcpy(r, s, t-s);
681 r[t-s] = 0;
682 return r;
685 int is_valid_label(char* lab)
687 char* t;
688 if (lab[0]=='"')
689 return (t=strrchr(lab,'"'))!=0 && t!=lab && strspn(t+1," \t\r\n")==strlen(t+1);
690 return strcspn(lab," \t")==strlen(lab);
693 /* Read a polyline and update the number of lines read. A zero result indicates bad
694 syntax if *lineno increases; otherwise it indicates end of file.
695 */
696 fpolygon* rd_fpoly(FILE* fin, int *lineno)
698 char buf[256], junk[2];
699 fpoint q;
700 fpolygon* fp;
701 int allocn;
702 if (!fgets(buf,256,fin))
703 return 0;
704 (*lineno)++;
705 if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2)
706 return 0;
707 fp = malloc(sizeof(fpolygon));
708 allocn = 16;
709 fp->p = malloc(allocn*sizeof(fpoint));
710 fp->p[0] = q;
711 fp->n = 0;
712 fp->nam = "";
713 fp->thick = 0;
714 fp->clr = clr_im(DBlack);
715 while (fgets(buf,256,fin)) {
716 (*lineno)++;
717 if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2) {
718 if (!is_valid_label(buf))
719 {free(fp->p); free(fp); return 0;}
720 fp->nam = (buf[0]=='"') ? buf+1 : buf;
721 break;
723 if (++(fp->n) == allocn)
724 fp->p = realloc(fp->p, (allocn<<=1)*sizeof(fpoint));
725 fp->p[fp->n] = q;
727 fp->nam = mystrdup(fp->nam);
728 set_fbb(fp);
729 fp->link = 0;
730 return fp;
734 /* Read input into *fps and return 0 or a line number where there's a syntax error */
735 int rd_fpolys(FILE* fin, fpolygons* fps)
737 fpolygon *fp, *fp0=fps->p;
738 int lineno=0, ok_upto=0;
739 while ((fp=rd_fpoly(fin,&lineno)) != 0) {
740 ok_upto = lineno;
741 fp->link = fps->p;
742 fps->p = fp;
743 grow_bb(&fps->bb, &fp->bb);
745 set_default_clrs(fps, fp0);
746 return (ok_upto==lineno) ? 0 : lineno;
750 /* Read input from file fnam and return an error line no., -1 for "can't open"
751 or 0 for success.
752 */
753 int doinput(char* fnam)
755 FILE* fin = strcmp(fnam,"-")==0 ? stdin : fopen(fnam, "r");
756 int errline_or0;
757 if (fin==0)
758 return -1;
759 errline_or0 = rd_fpolys(fin, &univ);
760 fclose(fin);
761 return errline_or0;
766 /******************************** Ascii output ********************************/
768 fpolygon* fp_reverse(fpolygon* fp)
770 fpolygon* r = 0;
771 while (fp!=0) {
772 fpolygon* q = fp->link;
773 fp->link = r;
774 r = fp;
775 fp = q;
777 return r;
780 void wr_fpoly(FILE* fout, const fpolygon* fp)
782 char buf[256];
783 int i;
784 for (i=0; i<=fp->n; i++)
785 fprintf(fout,"%.12g\t%.12g\n", fp->p[i].x, fp->p[i].y);
786 fprintf(fout,"\"%s\"\n", nam_with_thclr(fp->nam, fp->thick, fp->clr, buf, 256));
789 void wr_fpolys(FILE* fout, fpolygons* fps)
791 fpolygon* fp;
792 fps->p = fp_reverse(fps->p);
793 for (fp=fps->p; fp!=0; fp=fp->link)
794 wr_fpoly(fout, fp);
795 fps->p = fp_reverse(fps->p);
799 int dooutput(char* fnam)
801 FILE* fout = fopen(fnam, "w");
802 if (fout==0)
803 return 0;
804 wr_fpolys(fout, &univ);
805 fclose(fout);
806 return 1;
812 /************************ Clipping to screen rectangle ************************/
814 /* Find the t values, 0<=t<=1 for which x0+t*(x1-x0) is between xlo and xhi,
815 or return 0 to indicate no such t values exist. If returning 1, set *t0 and
816 *t1 to delimit the t interval.
817 */
818 int do_xory(double x0, double x1, double xlo, double xhi, double* t0, double* t1)
820 *t1 = 1.0;
821 if (x0<xlo) {
822 if (x1<xlo) return 0;
823 *t0 = (xlo-x0)/(x1-x0);
824 if (x1>xhi) *t1 = (xhi-x0)/(x1-x0);
825 } else if (x0>xhi) {
826 if (x1>xhi) return 0;
827 *t0 = (xhi-x0)/(x1-x0);
828 if (x1<xlo) *t1 = (xlo-x0)/(x1-x0);
829 } else {
830 *t0 = 0.0;
831 if (x1>xhi) *t1 = (xhi-x0)/(x1-x0);
832 else if (x1<xlo) *t1 = (xlo-x0)/(x1-x0);
833 else *t1 = 1.0;
835 return 1;
839 /* After mapping y to y-slope*x, what initial fraction of the *p to *q edge is
840 outside of *r? Note that the edge could start outside *r, pass through *r,
841 and wind up outside again.
842 */
843 double frac_outside(const fpoint* p, const fpoint* q, const frectangle* r,
844 double slope)
846 double t0, t1, tt0, tt1;
847 double px=p->x, qx=q->x;
848 if (!do_xory(px, qx, r->min.x, r->max.x, &t0, &t1))
849 return 1;
850 if (!do_xory(p->y-slope*px, q->y-slope*qx, r->min.y, r->max.y, &tt0, &tt1))
851 return 1;
852 if (tt0 > t0)
853 t0 = tt0;
854 if (t1<=t0 || tt1<=t0)
855 return 1;
856 return t0;
860 /* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find
861 the maximum tt such that F(0..tt) is all inside of r, assuming p0 is inside.
862 Coordinates are transformed by y=y-x*slope before testing against r.
863 */
864 double in_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope)
866 const fpoint* p = p0;
867 double px, py;
868 do if (++p > pn)
869 return pn - p0;
870 while (r.min.x<=(px=p->x) && px<=r.max.x
871 && r.min.y<=(py=p->y-slope*px) && py<=r.max.y);
872 return (p - p0) - frac_outside(p, p-1, &r, slope);
876 /* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find
877 the maximum tt such that F(0..tt) is all outside of *r. Coordinates are
878 transformed by y=y-x*slope before testing against r.
879 */
880 double out_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope)
882 const fpoint* p = p0;
883 double fr;
884 do { if (p->x < r.min.x)
885 do if (++p>pn) return pn-p0;
886 while (p->x <= r.min.x);
887 else if (p->x > r.max.x)
888 do if (++p>pn) return pn-p0;
889 while (p->x >= r.max.x);
890 else if (p->y-slope*p->x < r.min.y)
891 do if (++p>pn) return pn-p0;
892 while (p->y-slope*p->x <= r.min.y);
893 else if (p->y-slope*p->x > r.max.y)
894 do if (++p>pn) return pn-p0;
895 while (p->y-slope*p->x >= r.max.y);
896 else return p - p0;
897 } while ((fr=frac_outside(p-1,p,&r,slope)) == 1);
898 return (p - p0) + fr-1;
903 /*********************** Drawing frame and axis labels ***********************/
905 #define Nthous 7
906 #define Len_thous 30 /* bound on strlen(thous_nam[i]) */
907 char* thous_nam[Nthous] = {
908 "one", "thousand", "million", "billion",
909 "trillion", "quadrillion", "quintillion",
910 };
913 typedef struct lab_interval {
914 double sep; /* separation between tick marks */
915 double unit; /* power of 1000 divisor */
916 int logunit; /* log base 1000 of of this divisor */
917 double off; /* offset to subtract before dividing */
918 } lab_interval;
921 char* abbrev_num(double x, const lab_interval* iv)
923 static char buf[16];
924 double dx = x - iv->off;
925 dx = iv->sep * floor(dx/iv->sep + .5);
926 sprintf(buf,"%g", dx/iv->unit);
927 return buf;
931 double lead_digits(double n, double r) /* n truncated to power of 10 above r */
933 double rr = pow(10, ceil(log10(r)));
934 double nn = (n<rr) ? 0.0 : rr*floor(n/rr);
935 if (n+r-nn >= digs10pow) {
936 rr /= 10;
937 nn = (n<rr) ? 0.0 : rr*floor(n/rr);
939 return nn;
943 lab_interval next_larger(double s0, double xlo, double xhi)
945 double nlo, nhi;
946 lab_interval r;
947 r.logunit = (int) floor(log10(s0) + LOG2);
948 r.unit = pow(10, r.logunit);
949 nlo = xlo/r.unit;
950 nhi = xhi/r.unit;
951 if (nhi >= digs10pow)
952 r.off = r.unit*lead_digits(nlo, nhi-nlo);
953 else if (nlo <= -digs10pow)
954 r.off = -r.unit*lead_digits(-nhi, nhi-nlo);
955 else r.off = 0;
956 r.sep = (s0<=r.unit) ? r.unit : (s0<2*r.unit ? 2*r.unit : 5*r.unit);
957 switch (r.logunit%3) {
958 case 1: r.unit*=.1; r.logunit--;
959 break;
960 case -1: case 2:
961 r.unit*=10; r.logunit++;
962 break;
963 case -2: r.unit*=100; r.logunit+=2;
965 r.logunit /= 3;
966 return r;
970 double min_hsep(const transform* tr)
972 double s = (2+labdigs)*sdigit.x;
973 double ss = (univ.disp.min.x<0) ? s+sdigit.x : s;
974 return dxuntransform(tr, ss);
978 lab_interval mark_x_axis(const transform* tr)
980 fpoint p = univ.disp.min;
981 Point q, qtop, qbot, tmp;
982 double x0=univ.disp.min.x, x1=univ.disp.max.x;
983 double seps0, nseps, seps;
984 lab_interval iv = next_larger(min_hsep(tr), x0, x1);
985 set_unslanted_y(&univ, &p.y, 0);
986 q.y = ytransform(tr, p.y) + .5;
987 qtop.y = q.y - tick_len;
988 qbot.y = q.y + framewd + framesep;
989 seps0 = ceil(x0/iv.sep);
990 for (seps=0, nseps=floor(x1/iv.sep)-seps0; seps<=nseps; seps+=1) {
991 char* num = abbrev_num((p.x=iv.sep*(seps0+seps)), &iv);
992 Font* f = display->defaultfont;
993 q.x = qtop.x = qbot.x = xtransform(tr, p.x);
994 line(screen, qtop, q, Enddisc, Enddisc, 0, axis_color, q);
995 tmp = stringsize(f, num);
996 qbot.x -= tmp.x/2;
997 string(screen, qbot, display->black, qbot, f, num);
999 return iv;
1003 lab_interval mark_y_axis(const transform* tr)
1005 Font* f = display->defaultfont;
1006 fpoint p = univ.disp.min;
1007 Point q, qrt, qlft;
1008 double y0, y1, seps0, nseps, seps;
1009 lab_interval iv;
1010 set_unslanted_y(&univ, &y0, &y1);
1011 iv = next_larger(dyuntransform(tr,-f->height), y0, y1);
1012 q.x = xtransform(tr, p.x) - .5;
1013 qrt.x = q.x + tick_len;
1014 qlft.x = q.x - (framewd + framesep);
1015 seps0 = ceil(y0/iv.sep);
1016 for (seps=0, nseps=floor(y1/iv.sep)-seps0; seps<=nseps; seps+=1) {
1017 char* num = abbrev_num((p.y=iv.sep*(seps0+seps)), &iv);
1018 Point qq = stringsize(f, num);
1019 q.y = qrt.y = qlft.y = ytransform(tr, p.y);
1020 line(screen, qrt, q, Enddisc, Enddisc, 0, axis_color, q);
1021 qq.x = qlft.x - qq.x;
1022 qq.y = qlft.y - qq.y/2;
1023 string(screen, qq, display->black, qq, f, num);
1025 return iv;
1029 void lab_iv_info(const lab_interval *iv, double slant, char* buf, int *n)
1031 if (iv->off > 0)
1032 (*n) += sprintf(buf+*n,"-%.12g",iv->off);
1033 else if (iv->off < 0)
1034 (*n) += sprintf(buf+*n,"+%.12g",-iv->off);
1035 if (slant>0)
1036 (*n) += sprintf(buf+*n,"-%.6gx", slant);
1037 else if (slant<0)
1038 (*n) += sprintf(buf+*n,"+%.6gx", -slant);
1039 if (abs(iv->logunit) >= Nthous)
1040 (*n) += sprintf(buf+*n," in 1e%d units", 3*iv->logunit);
1041 else if (iv->logunit > 0)
1042 (*n) += sprintf(buf+*n," in %ss", thous_nam[iv->logunit]);
1043 else if (iv->logunit < 0)
1044 (*n) += sprintf(buf+*n," in %sths", thous_nam[-iv->logunit]);
1048 void draw_xy_ranges(const lab_interval *xiv, const lab_interval *yiv)
1050 Point p;
1051 char buf[2*(19+Len_thous+8)+50];
1052 int bufn = 0;
1053 buf[bufn++] = 'x';
1054 lab_iv_info(xiv, 0, buf, &bufn);
1055 bufn += sprintf(buf+bufn, "; y");
1056 lab_iv_info(yiv, u_slant_amt(&univ), buf, &bufn);
1057 buf[bufn] = '\0';
1058 p = stringsize(display->defaultfont, buf);
1059 top_left = screen->r.min.x + lft_border;
1060 p.x = top_right = screen->r.max.x - rt_border - p.x;
1061 p.y = screen->r.min.y + outersep;
1062 string(screen, p, display->black, p, display->defaultfont, buf);
1066 transform draw_frame(void)
1068 lab_interval x_iv, y_iv;
1069 transform tr;
1070 Rectangle r = screen->r;
1071 lft_border = (univ.disp.min.y<0) ? lft_border0+sdigit.x : lft_border0;
1072 tr = cur_trans();
1073 r.min.x += lft_border;
1074 r.min.y += top_border;
1075 r.max.x -= rt_border;
1076 r.max.y -= bot_border;
1077 border(screen, r, -framewd, axis_color, r.min);
1078 x_iv = mark_x_axis(&tr);
1079 y_iv = mark_y_axis(&tr);
1080 draw_xy_ranges(&x_iv, &y_iv);
1081 return tr;
1086 /*************************** Finding the selection ***************************/
1088 typedef struct pt_on_fpoly {
1089 fpoint p; /* the point */
1090 fpolygon* fp; /* the fpolygon it lies on */
1091 double t; /* how many knots from the beginning */
1092 } pt_on_fpoly;
1095 static double myx, myy;
1096 #define mydist(p,o,sl,xwt,ywt) (myx=(p).x-(o).x, myy=(p).y-sl*(p).x-(o).y, \
1097 xwt*myx*myx + ywt*myy*myy)
1099 /* At what fraction of the way from p0[0] to p0[1] is mydist(p,ctr,slant,xwt,ywt)
1100 minimized?
1102 double closest_time(const fpoint* p0, const fpoint* ctr, double slant,
1103 double xwt, double ywt)
1105 double p00y=p0[0].y-slant*p0[0].x, p01y=p0[1].y-slant*p0[1].x;
1106 double dx=p0[1].x-p0[0].x, dy=p01y-p00y;
1107 double x0=p0[0].x-ctr->x, y0=p00y-ctr->y;
1108 double bot = xwt*dx*dx + ywt*dy*dy;
1109 if (bot==0)
1110 return 0;
1111 return -(xwt*x0*dx + ywt*y0*dy)/bot;
1115 /* Scan the polygonal path of length len knots starting at p0, and find the
1116 point that the transformation y=y-x*slant makes closest to the center of *r,
1117 where *r itself defines the distance metric. Knots get higher priority than
1118 points between knots. If psel->t is negative, always update *psel; otherwise
1119 update *psel only if the scan can improve it. Return a boolean that says
1120 whether *psel was updated.
1121 Note that *r is a very tiny rectangle (tiny when converted screen pixels)
1122 such that anything in *r is considered close enough to match the mouse click.
1123 The purpose of this routine is to be careful in case there is a lot of hidden
1124 detail in the tiny rectangle *r.
1126 int improve_pt(fpoint* p0, double len, const frectangle* r, double slant,
1127 pt_on_fpoly* psel)
1129 fpoint ctr = fcenter(r);
1130 double x_wt=2/(r->max.x-r->min.x), y_wt=2/(r->max.y-r->min.y);
1131 double xwt=x_wt*x_wt, ywt=y_wt*y_wt;
1132 double d, dbest = (psel->t <0) ? 1e30 : mydist(psel->p,ctr,slant,xwt,ywt);
1133 double tt, dbest0 = dbest;
1134 fpoint pp;
1135 int ilen = (int) len;
1136 if (len==0 || ilen>0) {
1137 int i;
1138 for (i=(len==0 ? 0 : 1); i<=ilen; i++) {
1139 d = mydist(p0[i], ctr, slant, xwt, ywt);
1140 if (d < dbest)
1141 {psel->p=p0[i]; psel->t=i; dbest=d;}
1143 return (dbest < dbest0);
1145 tt = closest_time(p0, &ctr, slant, xwt, ywt);
1146 if (tt > len)
1147 tt = len;
1148 pp.x = p0[0].x + tt*(p0[1].x - p0[0].x);
1149 pp.y = p0[0].y + tt*(p0[1].y - p0[0].y);
1150 if (mydist(pp, ctr, slant, xwt, ywt) < dbest) {
1151 psel->p = pp;
1152 psel->t = tt;
1153 return 1;
1155 return 0;
1159 /* Test *fp against *r after transforming by y=y-x*slope, and set *psel accordingly.
1161 void select_in_fpoly(fpolygon* fp, const frectangle* r, double slant,
1162 pt_on_fpoly* psel)
1164 fpoint *p0=fp->p, *pn=fp->p+fp->n;
1165 double l1, l2;
1166 if (p0==pn)
1167 {improve_pt(p0, 0, r, slant, psel); psel->fp=fp; return;}
1168 while ((l1=out_length(p0,pn,*r,slant)) < pn-p0) {
1169 fpoint p0sav;
1170 int i1 = (int) l1;
1171 p0+=i1; l1-=i1;
1172 p0sav = *p0;
1173 p0[0].x += l1*(p0[1].x - p0[0].x);
1174 p0[0].y += l1*(p0[1].y - p0[0].y);
1175 l2 = in_length(p0, pn, *r, slant);
1176 if (improve_pt(p0, l2, r, slant, psel)) {
1177 if (l1==0 && psel->t!=((int) psel->t)) {
1178 psel->t = 0;
1179 psel->p = *p0;
1180 } else if (psel->t < 1)
1181 psel->t += l1*(1 - psel->t);
1182 psel->t += p0 - fp->p;
1183 psel->fp = fp;
1185 *p0 = p0sav;
1186 p0 += (l2>0) ? ((int) ceil(l2)) : 1;
1191 /* Test all the fpolygons against *r after transforming by y=y-x*slope, and return
1192 the resulting selection, if any.
1194 pt_on_fpoly* select_in_univ(const frectangle* r, double slant)
1196 static pt_on_fpoly answ;
1197 fpolygon* fp;
1198 answ.t = -1;
1199 for (fp=univ.p; fp!=0; fp=fp->link)
1200 if (fintersects(r, &fp->bb, slant))
1201 select_in_fpoly(fp, r, slant, &answ);
1202 if (answ.t < 0)
1203 return 0;
1204 return &answ;
1209 /**************************** Using the selection ****************************/
1211 pt_on_fpoly cur_sel; /* current selection if cur_sel.t>=0 */
1212 pt_on_fpoly prev_sel; /* previous selection if prev_sel.t>=0 (for slant) */
1213 Image* sel_bkg = 0; /* what's behind the red dot */
1216 void clear_txt(void)
1218 Rectangle r;
1219 r.min = screen->r.min;
1220 r.min.x += lft_border;
1221 r.min.y += outersep;
1222 r.max.x = top_left;
1223 r.max.y = r.min.y + smaxch.y;
1224 draw(screen, r, display->white, display->opaque, r.min);
1225 top_left = r.min.x;
1229 Rectangle sel_dot_box(const transform* tr)
1231 Point ctr;
1232 Rectangle r;
1233 if (tr==0)
1234 ctr.x = ctr.y = Dotrad;
1235 else do_transform(&ctr, tr, &cur_sel.p);
1236 r.min.x=ctr.x-Dotrad; r.max.x=ctr.x+Dotrad+1;
1237 r.min.y=ctr.y-Dotrad; r.max.y=ctr.y+Dotrad+1;
1238 return r;
1242 void unselect(const transform* tr)
1244 transform tra;
1245 if (sel_bkg==0)
1246 sel_bkg = allocimage(display, sel_dot_box(0), CMAP8, 0, DWhite);
1247 clear_txt();
1248 if (cur_sel.t < 0)
1249 return;
1250 prev_sel = cur_sel;
1251 if (tr==0)
1252 {tra=cur_trans(); tr=&tra;}
1253 draw(screen, sel_dot_box(tr), sel_bkg, display->opaque, ZP);
1254 cur_sel.t = -1;
1258 /* Text at top right is written first and this low-level routine clobbers it if
1259 the new top-left text would overwrite it. However, users of this routine should
1260 try to keep the new text short enough to avoid this.
1262 void show_mytext(char* msg)
1264 Point tmp, pt = screen->r.min;
1265 int siz;
1266 tmp = stringsize(display->defaultfont, msg);
1267 siz = tmp.x;
1268 pt.x=top_left; pt.y+=outersep;
1269 if (top_left+siz > top_right) {
1270 Rectangle r;
1271 r.min.y = pt.y;
1272 r.min.x = top_right;
1273 r.max.y = r.min.y + smaxch.y;
1274 r.max.x = top_left+siz;
1275 draw(screen, r, display->white, display->opaque, r.min);
1276 top_right = top_left+siz;
1278 string(screen, pt, display->black, ZP, display->defaultfont, msg);
1279 top_left += siz;
1283 double rnd(double x, double tol) /* round to enough digits for accuracy tol */
1285 double t = pow(10, floor(log10(tol)));
1286 return t * floor(x/t + .5);
1289 double t_tol(double xtol, double ytol)
1291 int t = (int) floor(cur_sel.t);
1292 fpoint* p = cur_sel.fp->p;
1293 double dx, dy;
1294 if (t==cur_sel.t)
1295 return 1;
1296 dx = fabs(p[t+1].x - p[t].x);
1297 dy = fabs(p[t+1].y - p[t].y);
1298 xtol /= (xtol>dx) ? xtol : dx;
1299 ytol /= (ytol>dy) ? ytol : dy;
1300 return (xtol<ytol) ? xtol : ytol;
1303 void say_where(const transform* tr)
1305 double xtol=dxuntransform(tr,1), ytol=dyuntransform(tr,-1);
1306 char buf[100];
1307 int n, nmax = (top_right - top_left)/smaxch.x;
1308 if (nmax >= 100)
1309 nmax = 100-1;
1310 n = sprintf(buf,"(%.14g,%.14g) at t=%.14g",
1311 rnd(cur_sel.p.x,xtol), rnd(cur_sel.p.y,ytol),
1312 rnd(cur_sel.t, t_tol(xtol,ytol)));
1313 if (cur_sel.fp->nam[0] != 0)
1314 sprintf(buf+n," %.*s", nmax-n-1, cur_sel.fp->nam);
1315 show_mytext(buf);
1319 void reselect(const transform* tr) /* uselect(); set cur_sel; call this */
1321 Point pt2, pt3;
1322 fpoint p2;
1323 transform tra;
1324 if (cur_sel.t < 0)
1325 return;
1326 if (tr==0)
1327 {tra=cur_trans(); tr=&tra;}
1328 do_transform(&p2, tr, &cur_sel.p);
1329 if (fabs(p2.x)+fabs(p2.y)>1e8 || (pt2.x=p2.x, pt2.y=p2.y, is_off_screen(pt2)))
1330 {cur_sel.t= -1; return;}
1331 pt3.x=pt2.x-Dotrad; pt3.y=pt2.y-Dotrad;
1332 draw(sel_bkg, sel_dot_box(0), screen, display->opaque, pt3);
1333 fillellipse(screen, pt2, Dotrad, Dotrad, clr_im(DRed), pt2);
1334 say_where(tr);
1338 void do_select(Point pt)
1340 transform tr = cur_trans();
1341 fpoint pt1, pt2, ctr;
1342 frectangle r;
1343 double slant;
1344 pt_on_fpoly* psel;
1345 unselect(&tr);
1346 do_untransform(&ctr, &tr, &pt);
1347 pt1.x=pt.x-fuzz; pt1.y=pt.y+fuzz;
1348 pt2.x=pt.x+fuzz; pt2.y=pt.y-fuzz;
1349 do_untransform(&r.min, &tr, &pt1);
1350 do_untransform(&r.max, &tr, &pt2);
1351 slant = u_slant_amt(&univ);
1352 slant_frect(&r, -slant);
1353 psel = select_in_univ(&r, slant);
1354 if (psel==0)
1355 return;
1356 if (logfil!=0) {
1357 fprintf(logfil,"%.14g\t%.14g\n", psel->p.x, psel->p.y);
1358 fflush(logfil);
1360 cur_sel = *psel;
1361 reselect(&tr);
1365 /***************************** Prompting for text *****************************/
1367 void unshow_mytext(char* msg)
1369 Rectangle r;
1370 Point siz = stringsize(display->defaultfont, msg);
1371 top_left -= siz.x;
1372 r.min.y = screen->r.min.y + outersep;
1373 r.min.x = top_left;
1374 r.max.y = r.min.y + siz.y;
1375 r.max.x = r.min.x + siz.x;
1376 draw(screen, r, display->white, display->opaque, r.min);
1380 /* Show the given prompt and read a line of user input. The text appears at the
1381 top left. If it runs into the top right text, we stop echoing but let the user
1382 continue typing blind if he wants to.
1384 char* prompt_text(char* prompt)
1386 static char buf[200];
1387 int n0, n=0, nshown=0;
1388 Rune c;
1389 unselect(0);
1390 show_mytext(prompt);
1391 while (n<200-1-UTFmax && (c=ekbd())!='\n') {
1392 if (c=='\b') {
1393 buf[n] = 0;
1394 if (n > 0)
1395 do n--;
1396 while (n>0 && (buf[n-1]&0xc0)==0x80);
1397 if (n < nshown)
1398 {unshow_mytext(buf+n); nshown=n;}
1399 } else {
1400 n0 = n;
1401 n += runetochar(buf+n, &c);
1402 buf[n] = 0;
1403 if (nshown==n0 && top_right-top_left >= smaxch.x)
1404 {show_mytext(buf+n0); nshown=n;}
1407 buf[n] = 0;
1408 while (ecanmouse())
1409 emouse();
1410 return buf;
1414 /**************************** Redrawing the screen ****************************/
1416 /* Let p0 and its successors define a piecewise-linear function of a paramter t,
1417 and draw the 0<=t<=n1 portion using transform *tr.
1419 void draw_fpts(const fpoint* p0, double n1, const transform* tr, int thick,
1420 Image* clr)
1422 int n = (int) n1;
1423 const fpoint* p = p0 + n;
1424 fpoint pp;
1425 Point qq, q;
1426 if (n1 > n) {
1427 pp.x = p[0].x + (n1-n)*(p[1].x - p[0].x);
1428 pp.y = p[0].y + (n1-n)*(p[1].y - p[0].y);
1429 } else pp = *p--;
1430 do_transform(&qq, tr, &pp);
1431 if (n1==0)
1432 fillellipse(screen, qq, 1+thick, 1+thick, clr, qq);
1433 for (; p>=p0; p--) {
1434 do_transform(&q, tr, p);
1435 line(screen, qq, q, Enddisc, Enddisc, thick, clr, qq);
1436 qq = q;
1440 void draw_1fpoly(const fpolygon* fp, const transform* tr, Image* clr,
1441 const frectangle *udisp, double slant)
1443 fpoint *p0=fp->p, *pn=fp->p+fp->n;
1444 double l1, l2;
1445 if (p0==pn && fcontains(udisp,*p0))
1446 {draw_fpts(p0, 0, tr, fp->thick, clr); return;}
1447 while ((l1=out_length(p0,pn,*udisp,slant)) < pn-p0) {
1448 fpoint p0sav;
1449 int i1 = (int) l1;
1450 p0+=i1; l1-=i1;
1451 p0sav = *p0;
1452 p0[0].x += l1*(p0[1].x - p0[0].x);
1453 p0[0].y += l1*(p0[1].y - p0[0].y);
1454 l2 = in_length(p0, pn, *udisp, slant);
1455 draw_fpts(p0, l2, tr, fp->thick, clr);
1456 *p0 = p0sav;
1457 p0 += (l2>0) ? ((int) ceil(l2)) : 1;
1462 double get_clip_data(const fpolygons *u, frectangle *r)
1464 double slant = set_unslanted_y((fpolygons*)u, &r->min.y, &r->max.y);
1465 r->min.x = u->disp.min.x;
1466 r->max.x = u->disp.max.x;
1467 return slant;
1471 void draw_fpoly(const fpolygon* fp, const transform* tr, Image* clr)
1473 frectangle r;
1474 double slant = get_clip_data(&univ, &r);
1475 draw_1fpoly(fp, tr, clr, &r, slant);
1479 void eresized(int new)
1481 transform tr;
1482 fpolygon* fp;
1483 frectangle clipr;
1484 double slant;
1485 if(new && getwindow(display, Refmesg) < 0) {
1486 fprintf(stderr,"can't reattach to window\n");
1487 exits("reshap");
1489 draw(screen, screen->r, display->white, display->opaque, screen->r.min);
1490 tr = draw_frame();
1491 slant = get_clip_data(&univ, &clipr);
1492 for (fp=univ.p; fp!=0; fp=fp->link)
1493 if (fintersects(&clipr, &fp->bb, slant))
1494 draw_1fpoly(fp, &tr, fp->clr, &clipr, slant);
1495 reselect(0);
1496 if (mv_bkgd!=0 && mv_bkgd->repl==0) {
1497 freeimage(mv_bkgd);
1498 mv_bkgd = display->white;
1500 flushimage(display, 1);
1506 /********************************* Recoloring *********************************/
1508 int draw_palette(int n) /* n is number of colors; returns patch dy */
1510 int y0 = screen->r.min.y + top_border;
1511 int dy = (screen->r.max.y - bot_border - y0)/n;
1512 Rectangle r;
1513 int i;
1514 r.min.y = y0;
1515 r.min.x = screen->r.max.x - rt_border + framewd;
1516 r.max.y = y0 + dy;
1517 r.max.x = screen->r.max.x;
1518 for (i=0; i<n; i++) {
1519 draw(screen, r, clrtab[i].im, display->opaque, r.min);
1520 r.min.y = r.max.y;
1521 r.max.y += dy;
1523 return dy;
1527 Image* palette_color(Point pt, int dy, int n)
1528 { /* mouse at pt, patch size dy, n colors */
1529 int yy;
1530 if (screen->r.max.x - pt.x > rt_border - framewd)
1531 return 0;
1532 yy = pt.y - (screen->r.min.y + top_border);
1533 if (yy<0 || yy>=n*dy)
1534 return 0;
1535 return clrtab[yy/dy].im;
1539 void all_set_clr(fpolygons* fps, Image* clr)
1541 fpolygon* p;
1542 for (p=fps->p; p!=0; p=p->link)
1543 p->clr = clr;
1547 void do_recolor(int but, Mouse* m, int alluniv)
1549 int nclr = clr_id(DWhite);
1550 int dy = draw_palette(nclr);
1551 Image* clr;
1552 if (!get_1click(but, m, 0)) {
1553 eresized(0);
1554 return;
1556 clr = palette_color(m->xy, dy, nclr);
1557 if (clr != 0) {
1558 if (alluniv)
1559 all_set_clr(&univ, clr);
1560 else cur_sel.fp->clr = clr;
1562 eresized(0);
1563 lift_button(but, m, Never);
1567 /****************************** Move and rotate ******************************/
1569 void prepare_mv(const fpolygon* fp)
1571 Rectangle r = screen->r;
1572 Image* scr0;
1573 int dt = 1 + fp->thick;
1574 r.min.x+=lft_border-dt; r.min.y+=top_border-dt;
1575 r.max.x-=rt_border-dt; r.max.y-=bot_border-dt;
1576 if (mv_bkgd!=0 && mv_bkgd->repl==0)
1577 freeimage(mv_bkgd);
1578 mv_bkgd = allocimage(display, r, CMAP8, 0, DNofill);
1579 if (mv_bkgd==0)
1580 mv_bkgd = display->white;
1581 else { transform tr = cur_trans();
1582 draw(mv_bkgd, r, screen, display->opaque, r.min);
1583 draw(mv_bkgd, sel_dot_box(&tr), sel_bkg, display->opaque, ZP);
1584 scr0 = screen;
1585 screen = mv_bkgd;
1586 draw_fpoly(fp, &tr, display->white);
1587 screen = scr0;
1592 void move_fp(fpolygon* fp, double dx, double dy)
1594 fpoint *p, *pn=fp->p+fp->n;
1595 for (p=fp->p; p<=pn; p++) {
1596 (p->x) += dx;
1597 (p->y) += dy;
1599 (fp->bb.min.x)+=dx; (fp->bb.min.y)+=dy;
1600 (fp->bb.max.x)+=dx; (fp->bb.max.y)+=dy;
1604 void rotate_fp(fpolygon* fp, fpoint o, double theta)
1606 double s=sin(theta), c=cos(theta);
1607 fpoint *p, *pn=fp->p+fp->n;
1608 for (p=fp->p; p<=pn; p++) {
1609 double x=p->x-o.x, y=p->y-o.y;
1610 (p->x) = o.x + c*x - s*y;
1611 (p->y) = o.y + s*x + c*y;
1613 set_fbb(fp);
1617 /* Move the selected fpolygon so the selected point tracks the mouse, and return
1618 the total amount of movement. Button but has already been held down for at
1619 least Mv_delay milliseconds and the mouse might have moved some distance.
1621 fpoint do_move(int but, Mouse* m)
1623 transform tr = cur_trans();
1624 int bbit = Button_bit(but);
1625 fpolygon* fp = cur_sel.fp;
1626 fpoint loc, loc0=cur_sel.p;
1627 double tsav = cur_sel.t;
1628 unselect(&tr);
1629 do { latest_mouse(but, m);
1630 (fp->thick)++; /* line() DISAGREES WITH ITSELF */
1631 draw_fpoly(fp, &tr, mv_bkgd);
1632 (fp->thick)--;
1633 do_untransform(&loc, &tr, &m->xy);
1634 move_fp(fp, loc.x-cur_sel.p.x, loc.y-cur_sel.p.y);
1635 cur_sel.p = loc;
1636 draw_fpoly(fp, &tr, fp->clr);
1637 } while (m->buttons & bbit);
1638 cur_sel.t = tsav;
1639 reselect(&tr);
1640 loc.x -= loc0.x;
1641 loc.y -= loc0.y;
1642 return loc;
1646 double dir_angle(const Point* pt, const transform* tr)
1648 fpoint p;
1649 double dy, dx;
1650 do_untransform(&p, tr, pt);
1651 dy=p.y-cur_sel.p.y; dx=p.x-cur_sel.p.x;
1652 return (dx==0 && dy==0) ? 0.0 : atan2(dy, dx);
1656 /* Rotate the selected fpolygon around the selection point so as to track the
1657 direction angle from the selected point to m->xy. Stop when button but goes
1658 up and return the total amount of rotation in radians.
1660 double do_rotate(int but, Mouse* m)
1662 transform tr = cur_trans();
1663 int bbit = Button_bit(but);
1664 fpolygon* fp = cur_sel.fp;
1665 double theta0 = dir_angle(&m->xy, &tr);
1666 double th, theta = theta0;
1667 do { latest_mouse(but, m);
1668 (fp->thick)++; /* line() DISAGREES WITH ITSELF */
1669 draw_fpoly(fp, &tr, mv_bkgd);
1670 (fp->thick)--;
1671 th = dir_angle(&m->xy, &tr);
1672 rotate_fp(fp, cur_sel.p, th-theta);
1673 theta = th;
1674 draw_fpoly(fp, &tr, fp->clr);
1675 } while (m->buttons & bbit);
1676 unselect(&tr);
1677 cur_sel = prev_sel;
1678 reselect(&tr);
1679 return theta - theta0;
1684 /********************************* Edit menu *********************************/
1686 typedef enum e_index {
1687 Erecolor, Ethick, Edelete, Eundo, Erotate, Eoptions,
1688 Emove
1689 } e_index;
1691 char* e_items[Eoptions+1];
1693 Menu e_menu = {e_items, 0, 0};
1696 typedef struct e_action {
1697 e_index typ; /* What type of action */
1698 fpolygon* fp; /* fpolygon the action applies to */
1699 Image* clr; /* color to use if typ==Erecolor */
1700 double amt; /* rotation angle or line thickness */
1701 fpoint pt; /* movement vector or rotation center */
1702 struct e_action* link; /* next in a stack */
1703 } e_action;
1705 e_action* unact = 0; /* heads a linked list of actions */
1706 e_action* do_undo(e_action*); /* pop off an e_action and (un)do it */
1707 e_action* save_act(e_action*,e_index); /* append new e_action for status quo */
1710 void save_mv(fpoint movement)
1712 unact = save_act(unact, Emove);
1713 unact->pt = movement;
1717 void init_e_menu(void)
1719 char* u = "can't undo";
1720 e_items[Erecolor] = "recolor";
1721 e_items[Edelete] = "delete";
1722 e_items[Erotate] = "rotate";
1723 e_items[Eoptions-cantmv] = 0;
1724 e_items[Ethick] = (cur_sel.fp->thick >0) ? "thin" : "thick";
1725 if (unact!=0)
1726 switch (unact->typ) {
1727 case Erecolor: u="uncolor"; break;
1728 case Ethick: u=(unact->fp->thick==0) ? "unthin" : "unthicken";
1729 break;
1730 case Edelete: u="undelete"; break;
1731 case Emove: u="unmove"; break;
1732 case Erotate: u="unrotate"; break;
1734 e_items[Eundo] = u;
1738 void do_emenu(int but, Mouse* m)
1740 int h;
1741 if (cur_sel.t < 0)
1742 return;
1743 init_e_menu();
1744 h = emenuhit(but, m, &e_menu);
1745 switch(h) {
1746 case Ethick: unact = save_act(unact, h);
1747 cur_sel.fp->thick ^= 1;
1748 eresized(0);
1749 break;
1750 case Edelete: unact = save_act(unact, h);
1751 fp_remove(&univ, cur_sel.fp);
1752 unselect(0);
1753 eresized(0);
1754 break;
1755 case Erecolor: unact = save_act(unact, h);
1756 do_recolor(but, m, 0);
1757 break;
1758 case Erotate: unact = save_act(unact, h);
1759 prepare_mv(cur_sel.fp);
1760 if (get_1click(but, m, 0)) {
1761 unact->pt = cur_sel.p;
1762 unact->amt = do_rotate(but, m);
1764 break;
1765 case Eundo: unact = do_undo(unact);
1766 break;
1772 /******************************* Undoing edits *******************************/
1774 e_action* save_act(e_action* a0, e_index typ)
1775 { /* append new e_action for status quo */
1776 e_action* a = malloc(sizeof(e_action));
1777 a->link = a0;
1778 a->pt.x = a->pt.y = 0.0;
1779 a->amt = cur_sel.fp->thick;
1780 a->clr = cur_sel.fp->clr;
1781 a->fp = cur_sel.fp;
1782 a->typ = typ;
1783 return a;
1787 /* This would be trivial except it's nice to preserve the selection in order to make
1788 it easy to undo a series of moves. (There's no do_unrotate() because it's harder
1789 and less important to preserve the selection in that case.)
1791 void do_unmove(e_action* a)
1793 double tsav = cur_sel.t;
1794 unselect(0);
1795 move_fp(a->fp, -a->pt.x, -a->pt.y);
1796 if (a->fp == cur_sel.fp) {
1797 cur_sel.p.x -= a->pt.x;
1798 cur_sel.p.y -= a->pt.y;
1800 cur_sel.t = tsav;
1801 reselect(0);
1805 e_action* do_undo(e_action* a0) /* pop off an e_action and (un)do it */
1807 e_action* a = a0;
1808 if (a==0)
1809 return 0;
1810 switch(a->typ) {
1811 case Ethick: a->fp->thick = a->amt;
1812 eresized(0);
1813 break;
1814 case Erecolor: a->fp->clr = a->clr;
1815 eresized(0);
1816 break;
1817 case Edelete:
1818 a->fp->link = univ.p;
1819 univ.p = a->fp;
1820 grow_bb(&univ.bb, &a->fp->bb);
1821 eresized(0);
1822 break;
1823 case Emove:
1824 do_unmove(a);
1825 eresized(0);
1826 break;
1827 case Erotate:
1828 unselect(0);
1829 rotate_fp(a->fp, a->pt, -a->amt);
1830 eresized(0);
1831 break;
1833 a0 = a->link;
1834 free(a);
1835 return a0;
1840 /********************************* Main menu *********************************/
1842 enum m_index { Mzoom_in, Mzoom_out, Munzoom, Mslant, Munslant,
1843 Msquare_up, Mrecenter, Mrecolor, Mrestack, Mread,
1844 Mwrite, Mexit};
1845 char* m_items[] = {"zoom in", "zoom out", "unzoom", "slant", "unslant",
1846 "square up", "recenter", "recolor", "restack", "read",
1847 "write", "exit", 0};
1849 Menu m_menu = {m_items, 0, 0};
1852 void do_mmenu(int but, Mouse* m)
1854 int e, h = emenuhit(but, m, &m_menu);
1855 switch (h) {
1856 case Mzoom_in:
1857 disp_zoomin(egetrect(but,m));
1858 eresized(0);
1859 break;
1860 case Mzoom_out:
1861 disp_zoomout(egetrect(but,m));
1862 eresized(0);
1863 break;
1864 case Msquare_up:
1865 disp_squareup();
1866 eresized(0);
1867 break;
1868 case Munzoom:
1869 init_disp();
1870 eresized(0);
1871 break;
1872 case Mrecenter:
1873 if (get_1click(but, m, &bullseye)) {
1874 recenter_disp(m->xy);
1875 eresized(0);
1876 lift_button(but, m, Never);
1878 break;
1879 case Mslant:
1880 if (cur_sel.t>=0 && prev_sel.t>=0) {
1881 slant_disp(prev_sel.p, cur_sel.p);
1882 eresized(0);
1884 break;
1885 case Munslant:
1886 univ.slant_ht = univ.disp.max.y - univ.disp.min.y;
1887 eresized(0);
1888 break;
1889 case Mrecolor:
1890 do_recolor(but, m, 1);
1891 break;
1892 case Mrestack:
1893 fps_invert(&univ);
1894 eresized(0);
1895 break;
1896 case Mread:
1897 e = doinput(prompt_text("File:"));
1898 if (e==0)
1899 eresized(0);
1900 else if (e<0)
1901 show_mytext(" - can't read");
1902 else {
1903 char ebuf[80];
1904 snprintf(ebuf, 80, " - error line %d", e);
1905 show_mytext(ebuf);
1907 break;
1908 case Mwrite:
1909 if (!dooutput(prompt_text("File:")))
1910 show_mytext(" - can't write");
1911 break;
1912 case Mexit:
1913 exits("");
1919 /****************************** Handling events ******************************/
1921 void doevent(void)
1923 ulong etype;
1924 int mobile;
1925 ulong mvtime;
1926 Event ev;
1928 etype = eread(Emouse|Ekeyboard, &ev);
1929 if(etype & Emouse) {
1930 if (ev.mouse.buttons & But1) {
1931 do_select(ev.mouse.xy);
1932 mvtime = Never;
1933 mobile = !cantmv && cur_sel.t>=0;
1934 if (mobile) {
1935 mvtime = ev.mouse.msec + Mv_delay;
1936 prepare_mv(cur_sel.fp);
1937 if (!lift_button(1, &ev.mouse, mvtime))
1938 save_mv(do_move(1, &ev.mouse));
1940 } else if (ev.mouse.buttons & But2)
1941 do_emenu(2, &ev.mouse);
1942 else if (ev.mouse.buttons & But3)
1943 do_mmenu(3, &ev.mouse);
1945 /* no need to check (etype & Ekeyboard)--there are no keyboard commands */
1950 /******************************** Main program ********************************/
1952 extern char* argv0;
1954 void usage(void)
1956 int i;
1957 fprintf(stderr,"Usage %s [options] [infile]\n", argv0);
1958 fprintf(stderr,
1959 "option ::= -W winsize | -l logfile | -m\n"
1960 "\n"
1961 "Read a polygonal line graph in an ASCII format (one x y pair per line, delimited\n"
1962 "by spaces with a label after each polyline), and view it interactively. Use\n"
1963 "standard input if no infile is specified.\n"
1964 "Option -l specifies a file in which to log the coordinates of each point selected.\n"
1965 "(Clicking a point with button one selects it and displays its coordinates and\n"
1966 "the label of its polylone.) Option -m allows polylines to be moved and rotated.\n"
1967 "The polyline labels can use the following color names:"
1969 for (i=0; clrtab[i].c!=DNofill; i++)
1970 fprintf(stderr,"%s%8s", (i%8==0 ? "\n" : " "), clrtab[i].nam);
1971 fputc('\n', stderr);
1972 exits("usage");
1975 void main(int argc, char *argv[])
1977 int e;
1979 ARGBEGIN {
1980 case 'm': cantmv=0;
1981 break;
1982 case 'l': logfil = fopen(ARGF(),"w");
1983 break;
1984 case 'W':
1985 winsize = EARGF(usage());
1986 break;
1987 default: usage();
1988 } ARGEND
1990 if(initdraw(0, 0, "gview") < 0)
1991 exits("initdraw");
1992 einit(Emouse|Ekeyboard);
1994 e = doinput(*argv ? *argv : "-");
1995 if (e < 0) {
1996 fprintf(stderr,"Cannot read input file %s\n", *argv);
1997 exits("no valid input file");
1998 } else if (e > 0) {
1999 fprintf(stderr,"Bad syntax at line %d in input file\n", e);
2000 exits("bad syntax in input");
2002 init_disp();
2003 init_clrtab();
2004 set_default_clrs(&univ, 0);
2005 adjust_border(display->defaultfont);
2006 cur_sel.t = prev_sel.t = -1;
2007 eresized(0);
2008 for(;;)
2009 doevent();