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((uchar)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 #define DOrange 0xFFAA00FF
159 #define Dgray 0xBBBBBBFF
160 #define DDkgreen 0x009900FF
161 #define DDkred 0xCC0000FF
162 #define DViolet 0x990099FF
163 #define DDkyellow 0xAAAA00FF
164 #define DLtblue 0xAAAAFFFF
165 #define DPink 0xFFAAAAFF
167 /* draw.h sets DBlack, DBlue, DRed, DYellow, DGreen,
168 DCyan, DMagenta, DWhite */
170 typedef struct color_ref {
171 ulong c; /* RGBA pixel color */
172 char* nam; /* ASCII name (matched to input, used in output)*/
173 Image* im; /* replicated solid-color image */
174 } color_ref;
176 color_ref clrtab[] = {
177 DRed, "Red", 0,
178 DPink, "Pink", 0,
179 DDkred, "Dkred", 0,
180 DOrange, "Orange", 0,
181 DYellow, "Yellow", 0,
182 DDkyellow, "Dkyellow", 0,
183 DGreen, "Green", 0,
184 DDkgreen, "Dkgreen", 0,
185 DCyan, "Cyan", 0,
186 DBlue, "Blue", 0,
187 DLtblue, "Ltblue", 0,
188 DMagenta, "Magenta", 0,
189 DViolet, "Violet", 0,
190 Dgray, "Gray", 0,
191 DBlack, "Black", 0,
192 DWhite, "White", 0,
193 DNofill, 0, 0 /* DNofill means "end of data" */
194 };
197 void init_clrtab(void)
199 int i;
200 Rectangle r = Rect(0,0,1,1);
201 for (i=0; clrtab[i].c!=DNofill; i++)
202 clrtab[i].im = allocimage(display, r, CMAP8, 1, clrtab[i].c);
203 /* should check for 0 result? */
207 int clrim_id(Image* clr)
209 int i;
210 for (i=0; clrtab[i].im!=clr; i++)
211 if (clrtab[i].c==DNofill)
212 exits("bad image color");
213 return i;
216 int clr_id(int clr)
218 int i;
219 for (i=0; clrtab[i].c!=clr; i++)
220 if (clrtab[i].c==DNofill)
221 exits("bad color");
222 return i;
225 #define clr_im(clr) clrtab[clr_id(clr)].im
228 /* This decides what color to use for a polyline based on the label it has in the
229 input file. Whichever color name comes first is the winner, otherwise return black.
230 */
231 Image* nam2clr(const char* nam, int *idxdest)
233 char *c, *cbest=(char*)nam;
234 int i, ibest=-1;
235 if (*nam!=0)
236 for (i=0; clrtab[i].nam!=0; i++) {
237 c = strstr(nam,clrtab[i].nam);
238 if (c!=0 && (ibest<0 || c<cbest))
239 {ibest=i; cbest=c;}
241 if (idxdest!=0)
242 *idxdest = (ibest<0) ? clr_id(DBlack) : ibest;
243 return (ibest<0) ? clr_im(DBlack) : clrtab[ibest].im;
246 /* A polyline is initial drawn in thick mode iff its label in the file contains "Thick" */
247 int nam2thick(const char* nam)
249 return strstr(nam,"Thick")==0 ? 0 : 1;
253 /* Alter string nam so that nam2thick() and nam2clr() agree with th and clr, using
254 buf[] (a buffer of length bufn) to store the result if it differs from nam.
255 We go to great pains to perform this alteration in a manner that will seem natural
256 to the user, i.e., we try removing a suitably isolated color name before inserting
257 a new one.
258 */
259 char* nam_with_thclr(char* nam, int th, Image* clr, char* buf, int bufn)
261 int clr0i, th0=nam2thick(nam);
262 Image* clr0 = nam2clr(nam, &clr0i);
263 char *clr0s;
264 if (th0==th && clr0==clr)
265 return nam;
266 clr0s = clrtab[clr0i].nam;
267 if (strlen(nam)<bufn) strcpy(buf,nam);
268 else {strncpy(buf,nam,bufn); buf[bufn-1]='\0';}
269 if (clr0 != clr)
270 remove_substr(buf, clr0s);
271 if (th0 > th)
272 while (remove_substr(buf, "Thick"))
273 /* do nothing */;
274 if (nam2clr(buf,0) != clr)
275 str_insert(buf, clrtab[clrim_id(clr)].nam, bufn);
276 if (th0 < th)
277 str_insert(buf, "Thick", bufn);
278 return buf;
283 /****************************** Data structures ******************************/
285 Image* mv_bkgd; /* Background image (usually 0) */
287 typedef struct fpoint {
288 double x, y;
289 } fpoint;
291 typedef struct frectangle {
292 fpoint min, max;
293 } frectangle;
295 frectangle empty_frect = {1e30, 1e30, -1e30, -1e30};
298 /* When *r2 is transformed by y=y-x*slant, might it intersect *r1 ?
299 */
300 int fintersects(const frectangle* r1, const frectangle* r2, double slant)
302 double x2min=r2->min.x, x2max=r2->max.x;
303 if (r1->max.x <= x2min || x2max <= r1->min.x)
304 return 0;
305 if (slant >=0)
306 {x2min*=slant; x2max*=slant;}
307 else {double t=x2min*slant; x2min=x2max*slant; x2max=t;}
308 return r1->max.y > r2->min.y-x2max && r2->max.y-x2min > r1->min.y;
311 int fcontains(const frectangle* r, fpoint p)
313 return r->min.x <=p.x && p.x<= r->max.x && r->min.y <=p.y && p.y<= r->max.y;
317 void grow_bb(frectangle* dest, const frectangle* r)
319 if (r->min.x < dest->min.x) dest->min.x=r->min.x;
320 if (r->min.y < dest->min.y) dest->min.y=r->min.y;
321 if (r->max.x > dest->max.x) dest->max.x=r->max.x;
322 if (r->max.y > dest->max.y) dest->max.y=r->max.y;
326 void slant_frect(frectangle *r, double sl)
328 r->min.y += sl*r->min.x;
329 r->max.y += sl*r->max.x;
333 fpoint fcenter(const frectangle* r)
335 fpoint c;
336 c.x = .5*(r->max.x + r->min.x);
337 c.y = .5*(r->max.y + r->min.y);
338 return c;
342 typedef struct fpolygon {
343 fpoint* p; /* a malloc'ed array */
344 int n; /* p[] has n elements: p[0..n] */
345 frectangle bb; /* bounding box */
346 char* nam; /* name of this polygon (malloc'ed) */
347 int thick; /* use 1+2*thick pixel wide lines */
348 Image* clr; /* Color to use when drawing this */
349 struct fpolygon* link;
350 } fpolygon;
352 typedef struct fpolygons {
353 fpolygon* p; /* the head of a linked list */
354 frectangle bb; /* overall bounding box */
355 frectangle disp; /* part being mapped onto screen->r */
356 double slant_ht; /* controls how disp is slanted */
357 } fpolygons;
360 fpolygons univ = { /* everything there is to display */
361 0,
362 1e30, 1e30, -1e30, -1e30,
363 0, 0, 0, 0,
364 2*1e30
365 };
368 void set_default_clrs(fpolygons* fps, fpolygon* fpstop)
370 fpolygon* fp;
371 for (fp=fps->p; fp!=0 && fp!=fpstop; fp=fp->link) {
372 fp->clr = nam2clr(fp->nam,0);
373 fp->thick = nam2thick(fp->nam);
378 void fps_invert(fpolygons* fps)
380 fpolygon *p, *r=0;
381 for (p=fps->p; p!=0;) {
382 fpolygon* q = p;
383 p = p->link;
384 q->link = r;
385 r = q;
387 fps->p = r;
391 void fp_remove(fpolygons* fps, fpolygon* fp)
393 fpolygon *q, **p = &fps->p;
394 while (*p!=fp)
395 if (*p==0)
396 return;
397 else p = &(*p)->link;
398 *p = fp->link;
399 fps->bb = empty_frect;
400 for (q=fps->p; q!=0; q=q->link)
401 grow_bb(&fps->bb, &q->bb);
405 /* The transform maps abstract fpoint coordinates (the ones used in the input)
406 to the current screen coordinates. The do_untransform() macros reverses this.
407 If univ.slant_ht is not the height of univ.disp, the actual region in the
408 abstract coordinates is a parallelogram inscribed in univ.disp with two
409 vertical edges and two slanted slanted edges: slant_ht>0 means that the
410 vertical edges have height slant_ht and the parallelogram touches the lower
411 left and upper right corners of univ.disp; slant_ht<0 refers to a parallelogram
412 of height -slant_ht that touches the other two corners of univ.disp.
413 NOTE: the ytransform macro assumes that tr->sl times the x coordinate has
414 already been subtracted from yy.
415 */
416 typedef struct transform {
417 double sl;
418 fpoint o, sc; /* (x,y):->(o.x+sc.x*x, o.y+sc.y*y+sl*x) */
419 } transform;
421 #define do_transform(d,tr,s) ((d)->x = (tr)->o.x + (tr)->sc.x*(s)->x, \
422 (d)->y = (tr)->o.y + (tr)->sc.y*(s)->y \
423 + (tr)->sl*(s)->x)
424 #define do_untransform(d,tr,s) ((d)->x = (.5+(s)->x-(tr)->o.x)/(tr)->sc.x, \
425 (d)->y = (.5+(s)->y-(tr)->sl*(d)->x-(tr)->o.y) \
426 /(tr)->sc.y)
427 #define xtransform(tr,xx) ((tr)->o.x + (tr)->sc.x*(xx))
428 #define ytransform(tr,yy) ((tr)->o.y + (tr)->sc.y*(yy))
429 #define dxuntransform(tr,xx) ((xx)/(tr)->sc.x)
430 #define dyuntransform(tr,yy) ((yy)/(tr)->sc.y)
433 transform cur_trans(void)
435 transform t;
436 Rectangle d = screen->r;
437 const frectangle* s = &univ.disp;
438 double sh = univ.slant_ht;
439 d.min.x += lft_border;
440 d.min.y += top_border;
441 d.max.x -= rt_border;
442 d.max.y -= bot_border;
443 t.sc.x = (d.max.x - d.min.x)/(s->max.x - s->min.x);
444 t.sc.y = -(d.max.y - d.min.y)/fabs(sh);
445 if (sh > 0) {
446 t.sl = -t.sc.y*(s->max.y-s->min.y-sh)/(s->max.x - s->min.x);
447 t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->max.x;
448 } else {
449 t.sl = t.sc.y*(s->max.y-s->min.y+sh)/(s->max.x - s->min.x);
450 t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->min.x;
452 t.o.x = d.min.x - t.sc.x*s->min.x;
453 return t;
457 double u_slant_amt(fpolygons *u)
459 double sh=u->slant_ht, dy=u->disp.max.y - u->disp.min.y;
460 double dx = u->disp.max.x - u->disp.min.x;
461 return (sh>0) ? (dy-sh)/dx : -(dy+sh)/dx;
465 /* Set *y0 and *y1 to the lower and upper bounds of the set of y-sl*x values that
466 *u says to display, where sl is the amount of slant.
467 */
468 double set_unslanted_y(fpolygons *u, double *y0, double *y1)
470 double yy1, sl=u_slant_amt(u);
471 if (u->slant_ht > 0) {
472 *y0 = u->disp.min.y - sl*u->disp.min.x;
473 yy1 = *y0 + u->slant_ht;
474 } else {
475 yy1 = u->disp.max.y - sl*u->disp.min.x;
476 *y0 = yy1 + u->slant_ht;
478 if (y1 != 0)
479 *y1 = yy1;
480 return sl;
486 /*************************** The region to display ****************************/
488 void nontrivial_interval(double *lo, double *hi)
490 if (*lo >= *hi) {
491 double mid = .5*(*lo + *hi);
492 double tweak = 1e-6 + 1e-6*fabs(mid);
493 *lo = mid - tweak;
494 *hi = mid + tweak;
499 void init_disp(void)
501 double dw = (univ.bb.max.x - univ.bb.min.x)*underscan;
502 double dh = (univ.bb.max.y - univ.bb.min.y)*underscan;
503 univ.disp.min.x = univ.bb.min.x - dw;
504 univ.disp.min.y = univ.bb.min.y - dh;
505 univ.disp.max.x = univ.bb.max.x + dw;
506 univ.disp.max.y = univ.bb.max.y + dh;
507 nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x);
508 nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y);
509 univ.slant_ht = univ.disp.max.y - univ.disp.min.y; /* means no slant */
513 void recenter_disp(Point c)
515 transform tr = cur_trans();
516 fpoint cc, off;
517 do_untransform(&cc, &tr, &c);
518 off.x = cc.x - .5*(univ.disp.min.x + univ.disp.max.x);
519 off.y = cc.y - .5*(univ.disp.min.y + univ.disp.max.y);
520 univ.disp.min.x += off.x;
521 univ.disp.min.y += off.y;
522 univ.disp.max.x += off.x;
523 univ.disp.max.y += off.y;
527 /* Find the upper-left and lower-right corners of the bounding box of the
528 parallelogram formed by untransforming the rectangle rminx, rminy, ... (given
529 in screen coordinates), and return the height of the parallelogram (negated
530 if it slopes downward).
531 */
532 double untransform_corners(double rminx, double rminy, double rmaxx, double rmaxy,
533 fpoint *ul, fpoint *lr)
535 fpoint r_ur, r_ul, r_ll, r_lr; /* corners of the given recangle */
536 fpoint ur, ll; /* untransformed versions of r_ur, r_ll */
537 transform tr = cur_trans();
538 double ht;
539 r_ur.x=rmaxx; r_ur.y=rminy;
540 r_ul.x=rminx; r_ul.y=rminy;
541 r_ll.x=rminx; r_ll.y=rmaxy;
542 r_lr.x=rmaxx; r_lr.y=rmaxy;
543 do_untransform(ul, &tr, &r_ul);
544 do_untransform(lr, &tr, &r_lr);
545 do_untransform(&ur, &tr, &r_ur);
546 do_untransform(&ll, &tr, &r_ll);
547 ht = ur.y - lr->y;
548 if (ll.x < ul->x)
549 ul->x = ll.x;
550 if (ur.y > ul->y)
551 ul->y = ur.y;
552 else ht = -ht;
553 if (ur.x > lr->x)
554 lr->x = ur.x;
555 if (ll.y < lr->y)
556 lr->y = ll.y;
557 return ht;
561 void disp_dozoom(double rminx, double rminy, double rmaxx, double rmaxy)
563 fpoint ul, lr;
564 double sh = untransform_corners(rminx, rminy, rmaxx, rmaxy, &ul, &lr);
565 if (ul.x==lr.x || ul.y==lr.y)
566 return;
567 univ.slant_ht = sh;
568 univ.disp.min.x = ul.x;
569 univ.disp.max.y = ul.y;
570 univ.disp.max.x = lr.x;
571 univ.disp.min.y = lr.y;
572 nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x);
573 nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y);
577 void disp_zoomin(Rectangle r)
579 disp_dozoom(r.min.x, r.min.y, r.max.x, r.max.y);
583 void disp_zoomout(Rectangle r)
585 double qminx, qminy, qmaxx, qmaxy;
586 double scx, scy;
587 Rectangle s = screen->r;
588 if (r.min.x==r.max.x || r.min.y==r.max.y)
589 return;
590 s.min.x += lft_border;
591 s.min.y += top_border;
592 s.max.x -= rt_border;
593 s.max.y -= bot_border;
594 scx = (s.max.x - s.min.x)/(r.max.x - r.min.x);
595 scy = (s.max.y - s.min.y)/(r.max.y - r.min.y);
596 qminx = s.min.x + scx*(s.min.x - r.min.x);
597 qmaxx = s.max.x + scx*(s.max.x - r.max.x);
598 qminy = s.min.y + scy*(s.min.y - r.min.y);
599 qmaxy = s.max.y + scy*(s.max.y - r.max.y);
600 disp_dozoom(qminx, qminy, qmaxx, qmaxy);
604 void expand2(double* a, double* b, double f)
606 double mid = .5*(*a + *b);
607 *a = mid + f*(*a - mid);
608 *b = mid + f*(*b - mid);
611 void disp_squareup(void)
613 double dx = univ.disp.max.x - univ.disp.min.x;
614 double dy = univ.disp.max.y - univ.disp.min.y;
615 dx /= screen->r.max.x - lft_border - screen->r.min.x - rt_border;
616 dy /= screen->r.max.y - bot_border - screen->r.min.y - top_border;
617 if (dx > dy)
618 expand2(&univ.disp.min.y, &univ.disp.max.y, dx/dy);
619 else expand2(&univ.disp.min.x, &univ.disp.max.x, dy/dx);
620 univ.slant_ht = univ.disp.max.y - univ.disp.min.y;
624 /* Slant so that p and q appear at the same height on the screen and the
625 screen contains the smallest possible superset of what its previous contents.
626 */
627 void slant_disp(fpoint p, fpoint q)
629 double yll, ylr, yul, yur; /* corner y coords of displayed parallelogram */
630 double sh, dy;
631 if (p.x == q.x)
632 return;
633 sh = univ.slant_ht;
634 if (sh > 0) {
635 yll=yul=univ.disp.min.y; yul+=sh;
636 ylr=yur=univ.disp.max.y; ylr-=sh;
637 } else {
638 yll=yul=univ.disp.max.y; yll+=sh;
639 ylr=yur=univ.disp.min.y; yur-=sh;
641 dy = (univ.disp.max.x-univ.disp.min.x)*(q.y - p.y)/(q.x - p.x);
642 dy -= ylr - yll;
643 if (dy > 0)
644 {yll-=dy; yur+=dy;}
645 else {yul-=dy; ylr+=dy;}
646 if (ylr > yll) {
647 univ.disp.min.y = yll;
648 univ.disp.max.y = yur;
649 univ.slant_ht = yur - ylr;
650 } else {
651 univ.disp.max.y = yul;
652 univ.disp.min.y = ylr;
653 univ.slant_ht = ylr - yur;
660 /******************************** Ascii input ********************************/
662 void set_fbb(fpolygon* fp)
664 fpoint lo=fp->p[0], hi=fp->p[0];
665 const fpoint *q, *qtop;
666 for (qtop=(q=fp->p)+fp->n; ++q<=qtop;) {
667 if (q->x < lo.x) lo.x=q->x;
668 if (q->y < lo.y) lo.y=q->y;
669 if (q->x > hi.x) hi.x=q->x;
670 if (q->y > hi.y) hi.y=q->y;
672 fp->bb.min = lo;
673 fp->bb.max = hi;
676 char* mystrdup(char* s)
678 char *r, *t = strrchr(s,'"');
679 if (t==0) {
680 t = s + strlen(s);
681 while (t>s && (t[-1]=='\n' || t[-1]=='\r'))
682 t--;
684 r = malloc(1+(t-s));
685 memcpy(r, s, t-s);
686 r[t-s] = 0;
687 return r;
690 int is_valid_label(char* lab)
692 char* t;
693 if (lab[0]=='"')
694 return (t=strrchr(lab,'"'))!=0 && t!=lab && strspn(t+1," \t\r\n")==strlen(t+1);
695 return strcspn(lab," \t")==strlen(lab);
698 /* Read a polyline and update the number of lines read. A zero result indicates bad
699 syntax if *lineno increases; otherwise it indicates end of file.
700 */
701 fpolygon* rd_fpoly(FILE* fin, int *lineno)
703 char buf[1024], junk[2];
704 fpoint q;
705 fpolygon* fp;
706 int allocn;
707 if (!fgets(buf,sizeof buf,fin))
708 return 0;
709 (*lineno)++;
710 if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2)
711 return 0;
712 fp = malloc(sizeof(fpolygon));
713 allocn = 16;
714 fp->p = malloc(allocn*sizeof(fpoint));
715 fp->p[0] = q;
716 fp->n = 0;
717 fp->nam = "";
718 fp->thick = 0;
719 fp->clr = clr_im(DBlack);
720 while (fgets(buf,sizeof buf,fin)) {
721 (*lineno)++;
722 if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2) {
723 if (!is_valid_label(buf))
724 {free(fp->p); free(fp); return 0;}
725 fp->nam = (buf[0]=='"') ? buf+1 : buf;
726 break;
728 if (++(fp->n) == allocn)
729 fp->p = realloc(fp->p, (allocn<<=1)*sizeof(fpoint));
730 fp->p[fp->n] = q;
732 fp->nam = mystrdup(fp->nam);
733 set_fbb(fp);
734 fp->link = 0;
735 return fp;
739 /* Read input into *fps and return 0 or a line number where there's a syntax error */
740 int rd_fpolys(FILE* fin, fpolygons* fps)
742 fpolygon *fp, *fp0=fps->p;
743 int lineno=0, ok_upto=0;
744 while ((fp=rd_fpoly(fin,&lineno)) != 0) {
745 ok_upto = lineno;
746 fp->link = fps->p;
747 fps->p = fp;
748 grow_bb(&fps->bb, &fp->bb);
750 set_default_clrs(fps, fp0);
751 return (ok_upto==lineno) ? 0 : lineno;
755 /* Read input from file fnam and return an error line no., -1 for "can't open"
756 or 0 for success.
757 */
758 int doinput(char* fnam)
760 FILE* fin = strcmp(fnam,"-")==0 ? stdin : fopen(fnam, "r");
761 int errline_or0;
762 if (fin==0)
763 return -1;
764 errline_or0 = rd_fpolys(fin, &univ);
765 fclose(fin);
766 return errline_or0;
771 /******************************** Ascii output ********************************/
773 fpolygon* fp_reverse(fpolygon* fp)
775 fpolygon* r = 0;
776 while (fp!=0) {
777 fpolygon* q = fp->link;
778 fp->link = r;
779 r = fp;
780 fp = q;
782 return r;
785 void wr_fpoly(FILE* fout, const fpolygon* fp)
787 char buf[1024];
788 int i;
789 for (i=0; i<=fp->n; i++)
790 fprintf(fout,"%.12g\t%.12g\n", fp->p[i].x, fp->p[i].y);
791 fprintf(fout,"\"%s\"\n", nam_with_thclr(fp->nam, fp->thick, fp->clr, buf, 256));
794 void wr_fpolys(FILE* fout, fpolygons* fps)
796 fpolygon* fp;
797 fps->p = fp_reverse(fps->p);
798 for (fp=fps->p; fp!=0; fp=fp->link)
799 wr_fpoly(fout, fp);
800 fps->p = fp_reverse(fps->p);
804 int dooutput(char* fnam)
806 FILE* fout = fopen(fnam, "w");
807 if (fout==0)
808 return 0;
809 wr_fpolys(fout, &univ);
810 fclose(fout);
811 return 1;
817 /************************ Clipping to screen rectangle ************************/
819 /* Find the t values, 0<=t<=1 for which x0+t*(x1-x0) is between xlo and xhi,
820 or return 0 to indicate no such t values exist. If returning 1, set *t0 and
821 *t1 to delimit the t interval.
822 */
823 int do_xory(double x0, double x1, double xlo, double xhi, double* t0, double* t1)
825 *t1 = 1.0;
826 if (x0<xlo) {
827 if (x1<xlo) return 0;
828 *t0 = (xlo-x0)/(x1-x0);
829 if (x1>xhi) *t1 = (xhi-x0)/(x1-x0);
830 } else if (x0>xhi) {
831 if (x1>xhi) return 0;
832 *t0 = (xhi-x0)/(x1-x0);
833 if (x1<xlo) *t1 = (xlo-x0)/(x1-x0);
834 } else {
835 *t0 = 0.0;
836 if (x1>xhi) *t1 = (xhi-x0)/(x1-x0);
837 else if (x1<xlo) *t1 = (xlo-x0)/(x1-x0);
838 else *t1 = 1.0;
840 return 1;
844 /* After mapping y to y-slope*x, what initial fraction of the *p to *q edge is
845 outside of *r? Note that the edge could start outside *r, pass through *r,
846 and wind up outside again.
847 */
848 double frac_outside(const fpoint* p, const fpoint* q, const frectangle* r,
849 double slope)
851 double t0, t1, tt0, tt1;
852 double px=p->x, qx=q->x;
853 if (!do_xory(px, qx, r->min.x, r->max.x, &t0, &t1))
854 return 1;
855 if (!do_xory(p->y-slope*px, q->y-slope*qx, r->min.y, r->max.y, &tt0, &tt1))
856 return 1;
857 if (tt0 > t0)
858 t0 = tt0;
859 if (t1<=t0 || tt1<=t0)
860 return 1;
861 return t0;
865 /* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find
866 the maximum tt such that F(0..tt) is all inside of r, assuming p0 is inside.
867 Coordinates are transformed by y=y-x*slope before testing against r.
868 */
869 double in_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope)
871 const fpoint* p = p0;
872 double px, py;
873 do if (++p > pn)
874 return pn - p0;
875 while (r.min.x<=(px=p->x) && px<=r.max.x
876 && r.min.y<=(py=p->y-slope*px) && py<=r.max.y);
877 return (p - p0) - frac_outside(p, p-1, &r, slope);
881 /* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find
882 the maximum tt such that F(0..tt) is all outside of *r. Coordinates are
883 transformed by y=y-x*slope before testing against r.
884 */
885 double out_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope)
887 const fpoint* p = p0;
888 double fr;
889 do { if (p->x < r.min.x)
890 do if (++p>pn) return pn-p0;
891 while (p->x <= r.min.x);
892 else if (p->x > r.max.x)
893 do if (++p>pn) return pn-p0;
894 while (p->x >= r.max.x);
895 else if (p->y-slope*p->x < r.min.y)
896 do if (++p>pn) return pn-p0;
897 while (p->y-slope*p->x <= r.min.y);
898 else if (p->y-slope*p->x > r.max.y)
899 do if (++p>pn) return pn-p0;
900 while (p->y-slope*p->x >= r.max.y);
901 else return p - p0;
902 } while ((fr=frac_outside(p-1,p,&r,slope)) == 1);
903 return (p - p0) + fr-1;
908 /*********************** Drawing frame and axis labels ***********************/
910 #define Nthous 7
911 #define Len_thous 30 /* bound on strlen(thous_nam[i]) */
912 char* thous_nam[Nthous] = {
913 "one", "thousand", "million", "billion",
914 "trillion", "quadrillion", "quintillion",
915 };
918 typedef struct lab_interval {
919 double sep; /* separation between tick marks */
920 double unit; /* power of 1000 divisor */
921 int logunit; /* log base 1000 of of this divisor */
922 double off; /* offset to subtract before dividing */
923 } lab_interval;
926 char* abbrev_num(double x, const lab_interval* iv)
928 static char buf[16];
929 double dx = x - iv->off;
930 dx = iv->sep * floor(dx/iv->sep + .5);
931 sprintf(buf,"%g", dx/iv->unit);
932 return buf;
936 double lead_digits(double n, double r) /* n truncated to power of 10 above r */
938 double rr = pow(10, ceil(log10(r)));
939 double nn = (n<rr) ? 0.0 : rr*floor(n/rr);
940 if (n+r-nn >= digs10pow) {
941 rr /= 10;
942 nn = (n<rr) ? 0.0 : rr*floor(n/rr);
944 return nn;
948 lab_interval next_larger(double s0, double xlo, double xhi)
950 double nlo, nhi;
951 lab_interval r;
952 r.logunit = (int) floor(log10(s0) + LOG2);
953 r.unit = pow(10, r.logunit);
954 nlo = xlo/r.unit;
955 nhi = xhi/r.unit;
956 if (nhi >= digs10pow)
957 r.off = r.unit*lead_digits(nlo, nhi-nlo);
958 else if (nlo <= -digs10pow)
959 r.off = -r.unit*lead_digits(-nhi, nhi-nlo);
960 else r.off = 0;
961 r.sep = (s0<=r.unit) ? r.unit : (s0<2*r.unit ? 2*r.unit : 5*r.unit);
962 switch (r.logunit%3) {
963 case 1: r.unit*=.1; r.logunit--;
964 break;
965 case -1: case 2:
966 r.unit*=10; r.logunit++;
967 break;
968 case -2: r.unit*=100; r.logunit+=2;
970 r.logunit /= 3;
971 return r;
975 double min_hsep(const transform* tr)
977 double s = (2+labdigs)*sdigit.x;
978 double ss = (univ.disp.min.x<0) ? s+sdigit.x : s;
979 return dxuntransform(tr, ss);
983 lab_interval mark_x_axis(const transform* tr)
985 fpoint p = univ.disp.min;
986 Point q, qtop, qbot, tmp;
987 double x0=univ.disp.min.x, x1=univ.disp.max.x;
988 double seps0, nseps, seps;
989 lab_interval iv = next_larger(min_hsep(tr), x0, x1);
990 set_unslanted_y(&univ, &p.y, 0);
991 q.y = ytransform(tr, p.y) + .5;
992 qtop.y = q.y - tick_len;
993 qbot.y = q.y + framewd + framesep;
994 seps0 = ceil(x0/iv.sep);
995 for (seps=0, nseps=floor(x1/iv.sep)-seps0; seps<=nseps; seps+=1) {
996 char* num = abbrev_num((p.x=iv.sep*(seps0+seps)), &iv);
997 Font* f = display->defaultfont;
998 q.x = qtop.x = qbot.x = xtransform(tr, p.x);
999 line(screen, qtop, q, Enddisc, Enddisc, 0, axis_color, q);
1000 tmp = stringsize(f, num);
1001 qbot.x -= tmp.x/2;
1002 string(screen, qbot, display->black, qbot, f, num);
1004 return iv;
1008 lab_interval mark_y_axis(const transform* tr)
1010 Font* f = display->defaultfont;
1011 fpoint p = univ.disp.min;
1012 Point q, qrt, qlft;
1013 double y0, y1, seps0, nseps, seps;
1014 lab_interval iv;
1015 set_unslanted_y(&univ, &y0, &y1);
1016 iv = next_larger(dyuntransform(tr,-f->height), y0, y1);
1017 q.x = xtransform(tr, p.x) - .5;
1018 qrt.x = q.x + tick_len;
1019 qlft.x = q.x - (framewd + framesep);
1020 seps0 = ceil(y0/iv.sep);
1021 for (seps=0, nseps=floor(y1/iv.sep)-seps0; seps<=nseps; seps+=1) {
1022 char* num = abbrev_num((p.y=iv.sep*(seps0+seps)), &iv);
1023 Point qq = stringsize(f, num);
1024 q.y = qrt.y = qlft.y = ytransform(tr, p.y);
1025 line(screen, qrt, q, Enddisc, Enddisc, 0, axis_color, q);
1026 qq.x = qlft.x - qq.x;
1027 qq.y = qlft.y - qq.y/2;
1028 string(screen, qq, display->black, qq, f, num);
1030 return iv;
1034 void lab_iv_info(const lab_interval *iv, double slant, char* buf, int *n)
1036 if (iv->off > 0)
1037 (*n) += sprintf(buf+*n,"-%.12g",iv->off);
1038 else if (iv->off < 0)
1039 (*n) += sprintf(buf+*n,"+%.12g",-iv->off);
1040 if (slant>0)
1041 (*n) += sprintf(buf+*n,"-%.6gx", slant);
1042 else if (slant<0)
1043 (*n) += sprintf(buf+*n,"+%.6gx", -slant);
1044 if (abs(iv->logunit) >= Nthous)
1045 (*n) += sprintf(buf+*n," in 1e%d units", 3*iv->logunit);
1046 else if (iv->logunit > 0)
1047 (*n) += sprintf(buf+*n," in %ss", thous_nam[iv->logunit]);
1048 else if (iv->logunit < 0)
1049 (*n) += sprintf(buf+*n," in %sths", thous_nam[-iv->logunit]);
1053 void draw_xy_ranges(const lab_interval *xiv, const lab_interval *yiv)
1055 Point p;
1056 char buf[2*(19+Len_thous+8)+50];
1057 int bufn = 0;
1058 buf[bufn++] = 'x';
1059 lab_iv_info(xiv, 0, buf, &bufn);
1060 bufn += sprintf(buf+bufn, "; y");
1061 lab_iv_info(yiv, u_slant_amt(&univ), buf, &bufn);
1062 buf[bufn] = '\0';
1063 p = stringsize(display->defaultfont, buf);
1064 top_left = screen->r.min.x + lft_border;
1065 p.x = top_right = screen->r.max.x - rt_border - p.x;
1066 p.y = screen->r.min.y + outersep;
1067 string(screen, p, display->black, p, display->defaultfont, buf);
1071 transform draw_frame(void)
1073 lab_interval x_iv, y_iv;
1074 transform tr;
1075 Rectangle r = screen->r;
1076 lft_border = (univ.disp.min.y<0) ? lft_border0+sdigit.x : lft_border0;
1077 tr = cur_trans();
1078 r.min.x += lft_border;
1079 r.min.y += top_border;
1080 r.max.x -= rt_border;
1081 r.max.y -= bot_border;
1082 border(screen, r, -framewd, axis_color, r.min);
1083 x_iv = mark_x_axis(&tr);
1084 y_iv = mark_y_axis(&tr);
1085 draw_xy_ranges(&x_iv, &y_iv);
1086 return tr;
1091 /*************************** Finding the selection ***************************/
1093 typedef struct pt_on_fpoly {
1094 fpoint p; /* the point */
1095 fpolygon* fp; /* the fpolygon it lies on */
1096 double t; /* how many knots from the beginning */
1097 } pt_on_fpoly;
1100 static double myx, myy;
1101 #define mydist(p,o,sl,xwt,ywt) (myx=(p).x-(o).x, myy=(p).y-sl*(p).x-(o).y, \
1102 xwt*myx*myx + ywt*myy*myy)
1104 /* At what fraction of the way from p0[0] to p0[1] is mydist(p,ctr,slant,xwt,ywt)
1105 minimized?
1107 double closest_time(const fpoint* p0, const fpoint* ctr, double slant,
1108 double xwt, double ywt)
1110 double p00y=p0[0].y-slant*p0[0].x, p01y=p0[1].y-slant*p0[1].x;
1111 double dx=p0[1].x-p0[0].x, dy=p01y-p00y;
1112 double x0=p0[0].x-ctr->x, y0=p00y-ctr->y;
1113 double bot = xwt*dx*dx + ywt*dy*dy;
1114 if (bot==0)
1115 return 0;
1116 return -(xwt*x0*dx + ywt*y0*dy)/bot;
1120 /* Scan the polygonal path of length len knots starting at p0, and find the
1121 point that the transformation y=y-x*slant makes closest to the center of *r,
1122 where *r itself defines the distance metric. Knots get higher priority than
1123 points between knots. If psel->t is negative, always update *psel; otherwise
1124 update *psel only if the scan can improve it. Return a boolean that says
1125 whether *psel was updated.
1126 Note that *r is a very tiny rectangle (tiny when converted screen pixels)
1127 such that anything in *r is considered close enough to match the mouse click.
1128 The purpose of this routine is to be careful in case there is a lot of hidden
1129 detail in the tiny rectangle *r.
1131 int improve_pt(fpoint* p0, double len, const frectangle* r, double slant,
1132 pt_on_fpoly* psel)
1134 fpoint ctr = fcenter(r);
1135 double x_wt=2/(r->max.x-r->min.x), y_wt=2/(r->max.y-r->min.y);
1136 double xwt=x_wt*x_wt, ywt=y_wt*y_wt;
1137 double d, dbest = (psel->t <0) ? 1e30 : mydist(psel->p,ctr,slant,xwt,ywt);
1138 double tt, dbest0 = dbest;
1139 fpoint pp;
1140 int ilen = (int) len;
1141 if (len==0 || ilen>0) {
1142 int i;
1143 for (i=(len==0 ? 0 : 1); i<=ilen; i++) {
1144 d = mydist(p0[i], ctr, slant, xwt, ywt);
1145 if (d < dbest)
1146 {psel->p=p0[i]; psel->t=i; dbest=d;}
1148 return (dbest < dbest0);
1150 tt = closest_time(p0, &ctr, slant, xwt, ywt);
1151 if (tt > len)
1152 tt = len;
1153 pp.x = p0[0].x + tt*(p0[1].x - p0[0].x);
1154 pp.y = p0[0].y + tt*(p0[1].y - p0[0].y);
1155 if (mydist(pp, ctr, slant, xwt, ywt) < dbest) {
1156 psel->p = pp;
1157 psel->t = tt;
1158 return 1;
1160 return 0;
1164 /* Test *fp against *r after transforming by y=y-x*slope, and set *psel accordingly.
1166 void select_in_fpoly(fpolygon* fp, const frectangle* r, double slant,
1167 pt_on_fpoly* psel)
1169 fpoint *p0=fp->p, *pn=fp->p+fp->n;
1170 double l1, l2;
1171 if (p0==pn)
1172 {improve_pt(p0, 0, r, slant, psel); psel->fp=fp; return;}
1173 while ((l1=out_length(p0,pn,*r,slant)) < pn-p0) {
1174 fpoint p0sav;
1175 int i1 = (int) l1;
1176 p0+=i1; l1-=i1;
1177 p0sav = *p0;
1178 p0[0].x += l1*(p0[1].x - p0[0].x);
1179 p0[0].y += l1*(p0[1].y - p0[0].y);
1180 l2 = in_length(p0, pn, *r, slant);
1181 if (improve_pt(p0, l2, r, slant, psel)) {
1182 if (l1==0 && psel->t!=((int) psel->t)) {
1183 psel->t = 0;
1184 psel->p = *p0;
1185 } else if (psel->t < 1)
1186 psel->t += l1*(1 - psel->t);
1187 psel->t += p0 - fp->p;
1188 psel->fp = fp;
1190 *p0 = p0sav;
1191 p0 += (l2>0) ? ((int) ceil(l2)) : 1;
1196 /* Test all the fpolygons against *r after transforming by y=y-x*slope, and return
1197 the resulting selection, if any.
1199 pt_on_fpoly* select_in_univ(const frectangle* r, double slant)
1201 static pt_on_fpoly answ;
1202 fpolygon* fp;
1203 answ.t = -1;
1204 for (fp=univ.p; fp!=0; fp=fp->link)
1205 if (fintersects(r, &fp->bb, slant))
1206 select_in_fpoly(fp, r, slant, &answ);
1207 if (answ.t < 0)
1208 return 0;
1209 return &answ;
1214 /**************************** Using the selection ****************************/
1216 pt_on_fpoly cur_sel; /* current selection if cur_sel.t>=0 */
1217 pt_on_fpoly prev_sel; /* previous selection if prev_sel.t>=0 (for slant) */
1218 Image* sel_bkg = 0; /* what's behind the red dot */
1221 void clear_txt(void)
1223 Rectangle r;
1224 r.min = screen->r.min;
1225 r.min.x += lft_border;
1226 r.min.y += outersep;
1227 r.max.x = top_left;
1228 r.max.y = r.min.y + smaxch.y;
1229 draw(screen, r, display->white, display->opaque, r.min);
1230 top_left = r.min.x;
1234 Rectangle sel_dot_box(const transform* tr)
1236 Point ctr;
1237 Rectangle r;
1238 if (tr==0)
1239 ctr.x = ctr.y = Dotrad;
1240 else do_transform(&ctr, tr, &cur_sel.p);
1241 r.min.x=ctr.x-Dotrad; r.max.x=ctr.x+Dotrad+1;
1242 r.min.y=ctr.y-Dotrad; r.max.y=ctr.y+Dotrad+1;
1243 return r;
1247 void unselect(const transform* tr)
1249 transform tra;
1250 if (sel_bkg==0)
1251 sel_bkg = allocimage(display, sel_dot_box(0), CMAP8, 0, DWhite);
1252 clear_txt();
1253 if (cur_sel.t < 0)
1254 return;
1255 prev_sel = cur_sel;
1256 if (tr==0)
1257 {tra=cur_trans(); tr=&tra;}
1258 draw(screen, sel_dot_box(tr), sel_bkg, display->opaque, ZP);
1259 cur_sel.t = -1;
1263 /* Text at top right is written first and this low-level routine clobbers it if
1264 the new top-left text would overwrite it. However, users of this routine should
1265 try to keep the new text short enough to avoid this.
1267 void show_mytext(char* msg)
1269 Point tmp, pt = screen->r.min;
1270 int siz;
1271 tmp = stringsize(display->defaultfont, msg);
1272 siz = tmp.x;
1273 pt.x=top_left; pt.y+=outersep;
1274 if (top_left+siz > top_right) {
1275 Rectangle r;
1276 r.min.y = pt.y;
1277 r.min.x = top_right;
1278 r.max.y = r.min.y + smaxch.y;
1279 r.max.x = top_left+siz;
1280 draw(screen, r, display->white, display->opaque, r.min);
1281 top_right = top_left+siz;
1283 string(screen, pt, display->black, ZP, display->defaultfont, msg);
1284 top_left += siz;
1288 double rnd(double x, double tol) /* round to enough digits for accuracy tol */
1290 double t = pow(10, floor(log10(tol)));
1291 return t * floor(x/t + .5);
1294 double t_tol(double xtol, double ytol)
1296 int t = (int) floor(cur_sel.t);
1297 fpoint* p = cur_sel.fp->p;
1298 double dx, dy;
1299 if (t==cur_sel.t)
1300 return 1;
1301 dx = fabs(p[t+1].x - p[t].x);
1302 dy = fabs(p[t+1].y - p[t].y);
1303 xtol /= (xtol>dx) ? xtol : dx;
1304 ytol /= (ytol>dy) ? ytol : dy;
1305 return (xtol<ytol) ? xtol : ytol;
1308 void say_where(const transform* tr)
1310 double xtol=dxuntransform(tr,1), ytol=dyuntransform(tr,-1);
1311 char buf[100];
1312 int n, nmax = (top_right - top_left)/smaxch.x;
1313 if (nmax >= 100)
1314 nmax = 100-1;
1315 n = sprintf(buf,"(%.14g,%.14g) at t=%.14g",
1316 rnd(cur_sel.p.x,xtol), rnd(cur_sel.p.y,ytol),
1317 rnd(cur_sel.t, t_tol(xtol,ytol)));
1318 if (cur_sel.fp->nam[0] != 0)
1319 sprintf(buf+n," %.*s", nmax-n-1, cur_sel.fp->nam);
1320 show_mytext(buf);
1324 void reselect(const transform* tr) /* uselect(); set cur_sel; call this */
1326 Point pt2, pt3;
1327 fpoint p2;
1328 transform tra;
1329 if (cur_sel.t < 0)
1330 return;
1331 if (tr==0)
1332 {tra=cur_trans(); tr=&tra;}
1333 do_transform(&p2, tr, &cur_sel.p);
1334 if (fabs(p2.x)+fabs(p2.y)>1e8 || (pt2.x=p2.x, pt2.y=p2.y, is_off_screen(pt2)))
1335 {cur_sel.t= -1; return;}
1336 pt3.x=pt2.x-Dotrad; pt3.y=pt2.y-Dotrad;
1337 draw(sel_bkg, sel_dot_box(0), screen, display->opaque, pt3);
1338 fillellipse(screen, pt2, Dotrad, Dotrad, clr_im(DRed), pt2);
1339 say_where(tr);
1343 void do_select(Point pt)
1345 transform tr = cur_trans();
1346 fpoint pt1, pt2, ctr;
1347 frectangle r;
1348 double slant;
1349 pt_on_fpoly* psel;
1350 unselect(&tr);
1351 do_untransform(&ctr, &tr, &pt);
1352 pt1.x=pt.x-fuzz; pt1.y=pt.y+fuzz;
1353 pt2.x=pt.x+fuzz; pt2.y=pt.y-fuzz;
1354 do_untransform(&r.min, &tr, &pt1);
1355 do_untransform(&r.max, &tr, &pt2);
1356 slant = u_slant_amt(&univ);
1357 slant_frect(&r, -slant);
1358 psel = select_in_univ(&r, slant);
1359 if (psel==0)
1360 return;
1361 if (logfil!=0) {
1362 fprintf(logfil,"%.14g\t%.14g\n", psel->p.x, psel->p.y);
1363 fflush(logfil);
1365 cur_sel = *psel;
1366 reselect(&tr);
1370 /***************************** Prompting for text *****************************/
1372 void unshow_mytext(char* msg)
1374 Rectangle r;
1375 Point siz = stringsize(display->defaultfont, msg);
1376 top_left -= siz.x;
1377 r.min.y = screen->r.min.y + outersep;
1378 r.min.x = top_left;
1379 r.max.y = r.min.y + siz.y;
1380 r.max.x = r.min.x + siz.x;
1381 draw(screen, r, display->white, display->opaque, r.min);
1385 /* Show the given prompt and read a line of user input. The text appears at the
1386 top left. If it runs into the top right text, we stop echoing but let the user
1387 continue typing blind if he wants to.
1389 char* prompt_text(char* prompt)
1391 static char buf[200];
1392 int n0, n=0, nshown=0;
1393 Rune c;
1394 unselect(0);
1395 show_mytext(prompt);
1396 while (n<200-1-UTFmax && (c=ekbd())!='\n') {
1397 if (c=='\b') {
1398 buf[n] = 0;
1399 if (n > 0)
1400 do n--;
1401 while (n>0 && (buf[n-1]&0xc0)==0x80);
1402 if (n < nshown)
1403 {unshow_mytext(buf+n); nshown=n;}
1404 } else {
1405 n0 = n;
1406 n += runetochar(buf+n, &c);
1407 buf[n] = 0;
1408 if (nshown==n0 && top_right-top_left >= smaxch.x)
1409 {show_mytext(buf+n0); nshown=n;}
1412 buf[n] = 0;
1413 while (ecanmouse())
1414 emouse();
1415 return buf;
1419 /**************************** Redrawing the screen ****************************/
1421 /* Let p0 and its successors define a piecewise-linear function of a paramter t,
1422 and draw the 0<=t<=n1 portion using transform *tr.
1424 void draw_fpts(const fpoint* p0, double n1, const transform* tr, int thick,
1425 Image* clr)
1427 int n = (int) n1;
1428 const fpoint* p = p0 + n;
1429 fpoint pp;
1430 Point qq, q;
1431 if (n1 > n) {
1432 pp.x = p[0].x + (n1-n)*(p[1].x - p[0].x);
1433 pp.y = p[0].y + (n1-n)*(p[1].y - p[0].y);
1434 } else pp = *p--;
1435 do_transform(&qq, tr, &pp);
1436 if (n1==0)
1437 fillellipse(screen, qq, 1+thick, 1+thick, clr, qq);
1438 for (; p>=p0; p--) {
1439 do_transform(&q, tr, p);
1440 line(screen, qq, q, Enddisc, Enddisc, thick, clr, qq);
1441 qq = q;
1445 void draw_1fpoly(const fpolygon* fp, const transform* tr, Image* clr,
1446 const frectangle *udisp, double slant)
1448 fpoint *p0=fp->p, *pn=fp->p+fp->n;
1449 double l1, l2;
1450 if (p0==pn && fcontains(udisp,*p0))
1451 {draw_fpts(p0, 0, tr, fp->thick, clr); return;}
1452 while ((l1=out_length(p0,pn,*udisp,slant)) < pn-p0) {
1453 fpoint p0sav;
1454 int i1 = (int) l1;
1455 p0+=i1; l1-=i1;
1456 p0sav = *p0;
1457 p0[0].x += l1*(p0[1].x - p0[0].x);
1458 p0[0].y += l1*(p0[1].y - p0[0].y);
1459 l2 = in_length(p0, pn, *udisp, slant);
1460 draw_fpts(p0, l2, tr, fp->thick, clr);
1461 *p0 = p0sav;
1462 p0 += (l2>0) ? ((int) ceil(l2)) : 1;
1467 double get_clip_data(const fpolygons *u, frectangle *r)
1469 double slant = set_unslanted_y((fpolygons*)u, &r->min.y, &r->max.y);
1470 r->min.x = u->disp.min.x;
1471 r->max.x = u->disp.max.x;
1472 return slant;
1476 void draw_fpoly(const fpolygon* fp, const transform* tr, Image* clr)
1478 frectangle r;
1479 double slant = get_clip_data(&univ, &r);
1480 draw_1fpoly(fp, tr, clr, &r, slant);
1484 void eresized(int new)
1486 transform tr;
1487 fpolygon* fp;
1488 frectangle clipr;
1489 double slant;
1490 if(new && getwindow(display, Refmesg) < 0) {
1491 fprintf(stderr,"can't reattach to window\n");
1492 exits("reshap");
1494 draw(screen, screen->r, display->white, display->opaque, screen->r.min);
1495 tr = draw_frame();
1496 slant = get_clip_data(&univ, &clipr);
1497 for (fp=univ.p; fp!=0; fp=fp->link)
1498 if (fintersects(&clipr, &fp->bb, slant))
1499 draw_1fpoly(fp, &tr, fp->clr, &clipr, slant);
1500 reselect(0);
1501 if (mv_bkgd!=0 && mv_bkgd->repl==0) {
1502 freeimage(mv_bkgd);
1503 mv_bkgd = display->white;
1505 flushimage(display, 1);
1511 /********************************* Recoloring *********************************/
1513 int draw_palette(int n) /* n is number of colors; returns patch dy */
1515 int y0 = screen->r.min.y + top_border;
1516 int dy = (screen->r.max.y - bot_border - y0)/n;
1517 Rectangle r;
1518 int i;
1519 r.min.y = y0;
1520 r.min.x = screen->r.max.x - rt_border + framewd;
1521 r.max.y = y0 + dy;
1522 r.max.x = screen->r.max.x;
1523 for (i=0; i<n; i++) {
1524 draw(screen, r, clrtab[i].im, display->opaque, r.min);
1525 r.min.y = r.max.y;
1526 r.max.y += dy;
1528 return dy;
1532 Image* palette_color(Point pt, int dy, int n)
1533 { /* mouse at pt, patch size dy, n colors */
1534 int yy;
1535 if (screen->r.max.x - pt.x > rt_border - framewd)
1536 return 0;
1537 yy = pt.y - (screen->r.min.y + top_border);
1538 if (yy<0 || yy>=n*dy)
1539 return 0;
1540 return clrtab[yy/dy].im;
1544 void all_set_clr(fpolygons* fps, Image* clr)
1546 fpolygon* p;
1547 for (p=fps->p; p!=0; p=p->link)
1548 p->clr = clr;
1552 void do_recolor(int but, Mouse* m, int alluniv)
1554 int nclr = clr_id(DWhite);
1555 int dy = draw_palette(nclr);
1556 Image* clr;
1557 if (!get_1click(but, m, 0)) {
1558 eresized(0);
1559 return;
1561 clr = palette_color(m->xy, dy, nclr);
1562 if (clr != 0) {
1563 if (alluniv)
1564 all_set_clr(&univ, clr);
1565 else cur_sel.fp->clr = clr;
1567 eresized(0);
1568 lift_button(but, m, Never);
1572 /****************************** Move and rotate ******************************/
1574 void prepare_mv(const fpolygon* fp)
1576 Rectangle r = screen->r;
1577 Image* scr0;
1578 int dt = 1 + fp->thick;
1579 r.min.x+=lft_border-dt; r.min.y+=top_border-dt;
1580 r.max.x-=rt_border-dt; r.max.y-=bot_border-dt;
1581 if (mv_bkgd!=0 && mv_bkgd->repl==0)
1582 freeimage(mv_bkgd);
1583 mv_bkgd = allocimage(display, r, CMAP8, 0, DNofill);
1584 if (mv_bkgd==0)
1585 mv_bkgd = display->white;
1586 else { transform tr = cur_trans();
1587 draw(mv_bkgd, r, screen, display->opaque, r.min);
1588 draw(mv_bkgd, sel_dot_box(&tr), sel_bkg, display->opaque, ZP);
1589 scr0 = screen;
1590 screen = mv_bkgd;
1591 draw_fpoly(fp, &tr, display->white);
1592 screen = scr0;
1597 void move_fp(fpolygon* fp, double dx, double dy)
1599 fpoint *p, *pn=fp->p+fp->n;
1600 for (p=fp->p; p<=pn; p++) {
1601 (p->x) += dx;
1602 (p->y) += dy;
1604 (fp->bb.min.x)+=dx; (fp->bb.min.y)+=dy;
1605 (fp->bb.max.x)+=dx; (fp->bb.max.y)+=dy;
1609 void rotate_fp(fpolygon* fp, fpoint o, double theta)
1611 double s=sin(theta), c=cos(theta);
1612 fpoint *p, *pn=fp->p+fp->n;
1613 for (p=fp->p; p<=pn; p++) {
1614 double x=p->x-o.x, y=p->y-o.y;
1615 (p->x) = o.x + c*x - s*y;
1616 (p->y) = o.y + s*x + c*y;
1618 set_fbb(fp);
1622 /* Move the selected fpolygon so the selected point tracks the mouse, and return
1623 the total amount of movement. Button but has already been held down for at
1624 least Mv_delay milliseconds and the mouse might have moved some distance.
1626 fpoint do_move(int but, Mouse* m)
1628 transform tr = cur_trans();
1629 int bbit = Button_bit(but);
1630 fpolygon* fp = cur_sel.fp;
1631 fpoint loc, loc0=cur_sel.p;
1632 double tsav = cur_sel.t;
1633 unselect(&tr);
1634 do { latest_mouse(but, m);
1635 (fp->thick)++; /* line() DISAGREES WITH ITSELF */
1636 draw_fpoly(fp, &tr, mv_bkgd);
1637 (fp->thick)--;
1638 do_untransform(&loc, &tr, &m->xy);
1639 move_fp(fp, loc.x-cur_sel.p.x, loc.y-cur_sel.p.y);
1640 cur_sel.p = loc;
1641 draw_fpoly(fp, &tr, fp->clr);
1642 } while (m->buttons & bbit);
1643 cur_sel.t = tsav;
1644 reselect(&tr);
1645 loc.x -= loc0.x;
1646 loc.y -= loc0.y;
1647 return loc;
1651 double dir_angle(const Point* pt, const transform* tr)
1653 fpoint p;
1654 double dy, dx;
1655 do_untransform(&p, tr, pt);
1656 dy=p.y-cur_sel.p.y; dx=p.x-cur_sel.p.x;
1657 return (dx==0 && dy==0) ? 0.0 : atan2(dy, dx);
1661 /* Rotate the selected fpolygon around the selection point so as to track the
1662 direction angle from the selected point to m->xy. Stop when button but goes
1663 up and return the total amount of rotation in radians.
1665 double do_rotate(int but, Mouse* m)
1667 transform tr = cur_trans();
1668 int bbit = Button_bit(but);
1669 fpolygon* fp = cur_sel.fp;
1670 double theta0 = dir_angle(&m->xy, &tr);
1671 double th, theta = theta0;
1672 do { latest_mouse(but, m);
1673 (fp->thick)++; /* line() DISAGREES WITH ITSELF */
1674 draw_fpoly(fp, &tr, mv_bkgd);
1675 (fp->thick)--;
1676 th = dir_angle(&m->xy, &tr);
1677 rotate_fp(fp, cur_sel.p, th-theta);
1678 theta = th;
1679 draw_fpoly(fp, &tr, fp->clr);
1680 } while (m->buttons & bbit);
1681 unselect(&tr);
1682 cur_sel = prev_sel;
1683 reselect(&tr);
1684 return theta - theta0;
1689 /********************************* Edit menu *********************************/
1691 typedef enum e_index {
1692 Erecolor, Ethick, Edelete, Eundo, Erotate, Eoptions,
1693 Emove
1694 } e_index;
1696 char* e_items[Eoptions+1];
1698 Menu e_menu = {e_items, 0, 0};
1701 typedef struct e_action {
1702 e_index typ; /* What type of action */
1703 fpolygon* fp; /* fpolygon the action applies to */
1704 Image* clr; /* color to use if typ==Erecolor */
1705 double amt; /* rotation angle or line thickness */
1706 fpoint pt; /* movement vector or rotation center */
1707 struct e_action* link; /* next in a stack */
1708 } e_action;
1710 e_action* unact = 0; /* heads a linked list of actions */
1711 e_action* do_undo(e_action*); /* pop off an e_action and (un)do it */
1712 e_action* save_act(e_action*,e_index); /* append new e_action for status quo */
1715 void save_mv(fpoint movement)
1717 unact = save_act(unact, Emove);
1718 unact->pt = movement;
1722 void init_e_menu(void)
1724 char* u = "can't undo";
1725 e_items[Erecolor] = "recolor";
1726 e_items[Edelete] = "delete";
1727 e_items[Erotate] = "rotate";
1728 e_items[Eoptions-cantmv] = 0;
1729 e_items[Ethick] = (cur_sel.fp->thick >0) ? "thin" : "thick";
1730 if (unact!=0)
1731 switch (unact->typ) {
1732 case Erecolor: u="uncolor"; break;
1733 case Ethick: u=(unact->fp->thick==0) ? "unthin" : "unthicken";
1734 break;
1735 case Edelete: u="undelete"; break;
1736 case Emove: u="unmove"; break;
1737 case Erotate: u="unrotate"; break;
1739 e_items[Eundo] = u;
1743 void do_emenu(int but, Mouse* m)
1745 int h;
1746 if (cur_sel.t < 0)
1747 return;
1748 init_e_menu();
1749 h = emenuhit(but, m, &e_menu);
1750 switch(h) {
1751 case Ethick: unact = save_act(unact, h);
1752 cur_sel.fp->thick ^= 1;
1753 eresized(0);
1754 break;
1755 case Edelete: unact = save_act(unact, h);
1756 fp_remove(&univ, cur_sel.fp);
1757 unselect(0);
1758 eresized(0);
1759 break;
1760 case Erecolor: unact = save_act(unact, h);
1761 do_recolor(but, m, 0);
1762 break;
1763 case Erotate: unact = save_act(unact, h);
1764 prepare_mv(cur_sel.fp);
1765 if (get_1click(but, m, 0)) {
1766 unact->pt = cur_sel.p;
1767 unact->amt = do_rotate(but, m);
1769 break;
1770 case Eundo: unact = do_undo(unact);
1771 break;
1777 /******************************* Undoing edits *******************************/
1779 e_action* save_act(e_action* a0, e_index typ)
1780 { /* append new e_action for status quo */
1781 e_action* a = malloc(sizeof(e_action));
1782 a->link = a0;
1783 a->pt.x = a->pt.y = 0.0;
1784 a->amt = cur_sel.fp->thick;
1785 a->clr = cur_sel.fp->clr;
1786 a->fp = cur_sel.fp;
1787 a->typ = typ;
1788 return a;
1792 /* This would be trivial except it's nice to preserve the selection in order to make
1793 it easy to undo a series of moves. (There's no do_unrotate() because it's harder
1794 and less important to preserve the selection in that case.)
1796 void do_unmove(e_action* a)
1798 double tsav = cur_sel.t;
1799 unselect(0);
1800 move_fp(a->fp, -a->pt.x, -a->pt.y);
1801 if (a->fp == cur_sel.fp) {
1802 cur_sel.p.x -= a->pt.x;
1803 cur_sel.p.y -= a->pt.y;
1805 cur_sel.t = tsav;
1806 reselect(0);
1810 e_action* do_undo(e_action* a0) /* pop off an e_action and (un)do it */
1812 e_action* a = a0;
1813 if (a==0)
1814 return 0;
1815 switch(a->typ) {
1816 case Ethick: a->fp->thick = a->amt;
1817 eresized(0);
1818 break;
1819 case Erecolor: a->fp->clr = a->clr;
1820 eresized(0);
1821 break;
1822 case Edelete:
1823 a->fp->link = univ.p;
1824 univ.p = a->fp;
1825 grow_bb(&univ.bb, &a->fp->bb);
1826 eresized(0);
1827 break;
1828 case Emove:
1829 do_unmove(a);
1830 eresized(0);
1831 break;
1832 case Erotate:
1833 unselect(0);
1834 rotate_fp(a->fp, a->pt, -a->amt);
1835 eresized(0);
1836 break;
1838 a0 = a->link;
1839 free(a);
1840 return a0;
1845 /********************************* Main menu *********************************/
1847 enum m_index { Mzoom_in, Mzoom_out, Munzoom, Mslant, Munslant,
1848 Msquare_up, Mrecenter, Mrecolor, Mrestack, Mread,
1849 Mwrite, Mexit};
1850 char* m_items[] = {"zoom in", "zoom out", "unzoom", "slant", "unslant",
1851 "square up", "recenter", "recolor", "restack", "read",
1852 "write", "exit", 0};
1854 Menu m_menu = {m_items, 0, 0};
1857 void do_mmenu(int but, Mouse* m)
1859 int e, h = emenuhit(but, m, &m_menu);
1860 switch (h) {
1861 case Mzoom_in:
1862 disp_zoomin(egetrect(but,m));
1863 eresized(0);
1864 break;
1865 case Mzoom_out:
1866 disp_zoomout(egetrect(but,m));
1867 eresized(0);
1868 break;
1869 case Msquare_up:
1870 disp_squareup();
1871 eresized(0);
1872 break;
1873 case Munzoom:
1874 init_disp();
1875 eresized(0);
1876 break;
1877 case Mrecenter:
1878 if (get_1click(but, m, &bullseye)) {
1879 recenter_disp(m->xy);
1880 eresized(0);
1881 lift_button(but, m, Never);
1883 break;
1884 case Mslant:
1885 if (cur_sel.t>=0 && prev_sel.t>=0) {
1886 slant_disp(prev_sel.p, cur_sel.p);
1887 eresized(0);
1889 break;
1890 case Munslant:
1891 univ.slant_ht = univ.disp.max.y - univ.disp.min.y;
1892 eresized(0);
1893 break;
1894 case Mrecolor:
1895 do_recolor(but, m, 1);
1896 break;
1897 case Mrestack:
1898 fps_invert(&univ);
1899 eresized(0);
1900 break;
1901 case Mread:
1902 e = doinput(prompt_text("File:"));
1903 if (e==0)
1904 eresized(0);
1905 else if (e<0)
1906 show_mytext(" - can't read");
1907 else {
1908 char ebuf[80];
1909 snprintf(ebuf, 80, " - error line %d", e);
1910 show_mytext(ebuf);
1912 break;
1913 case Mwrite:
1914 if (!dooutput(prompt_text("File:")))
1915 show_mytext(" - can't write");
1916 break;
1917 case Mexit:
1918 exits("");
1924 /****************************** Handling events ******************************/
1926 void doevent(void)
1928 ulong etype;
1929 int mobile;
1930 ulong mvtime;
1931 Event ev;
1933 etype = eread(Emouse|Ekeyboard, &ev);
1934 if(etype & Emouse) {
1935 if (ev.mouse.buttons & But1) {
1936 do_select(ev.mouse.xy);
1937 mvtime = Never;
1938 mobile = !cantmv && cur_sel.t>=0;
1939 if (mobile) {
1940 mvtime = ev.mouse.msec + Mv_delay;
1941 prepare_mv(cur_sel.fp);
1942 if (!lift_button(1, &ev.mouse, mvtime))
1943 save_mv(do_move(1, &ev.mouse));
1945 } else if (ev.mouse.buttons & But2)
1946 do_emenu(2, &ev.mouse);
1947 else if (ev.mouse.buttons & But3)
1948 do_mmenu(3, &ev.mouse);
1950 /* no need to check (etype & Ekeyboard)--there are no keyboard commands */
1955 /******************************** Main program ********************************/
1957 extern char* argv0;
1959 void usage(void)
1961 int i;
1962 fprintf(stderr,"Usage %s [options] [infile]\n", argv0);
1963 fprintf(stderr,
1964 "option ::= -W winsize | -l logfile | -m\n"
1965 "\n"
1966 "Read a polygonal line graph in an ASCII format (one x y pair per line, delimited\n"
1967 "by spaces with a label after each polyline), and view it interactively. Use\n"
1968 "standard input if no infile is specified.\n"
1969 "Option -l specifies a file in which to log the coordinates of each point selected.\n"
1970 "(Clicking a point with button one selects it and displays its coordinates and\n"
1971 "the label of its polylone.) Option -m allows polylines to be moved and rotated.\n"
1972 "The polyline labels can use the following color names:"
1974 for (i=0; clrtab[i].c!=DNofill; i++)
1975 fprintf(stderr,"%s%8s", (i%8==0 ? "\n" : " "), clrtab[i].nam);
1976 fputc('\n', stderr);
1977 exits("usage");
1980 void main(int argc, char *argv[])
1982 int e;
1984 ARGBEGIN {
1985 case 'm': cantmv=0;
1986 break;
1987 case 'l': logfil = fopen(ARGF(),"w");
1988 break;
1989 case 'W':
1990 winsize = EARGF(usage());
1991 break;
1992 default: usage();
1993 } ARGEND
1995 if(initdraw(0, 0, "gview") < 0)
1996 exits("initdraw");
1997 einit(Emouse|Ekeyboard);
1999 e = doinput(*argv ? *argv : "-");
2000 if (e < 0) {
2001 fprintf(stderr,"Cannot read input file %s\n", *argv);
2002 exits("no valid input file");
2003 } else if (e > 0) {
2004 fprintf(stderr,"Bad syntax at line %d in input file\n", e);
2005 exits("bad syntax in input");
2007 init_disp();
2008 init_clrtab();
2009 set_default_clrs(&univ, 0);
2010 adjust_border(display->defaultfont);
2011 cur_sel.t = prev_sel.t = -1;
2012 eresized(0);
2013 for(;;)
2014 doevent();