9 #define Never 0xffffffff /* Maximum ulong */
10 #define LOG2 0.301029995664
11 #define Button_bit(b) (1 << ((b)-1))
14 But1 = Button_bit(1),/* mouse buttons for events */
18 int cantmv = 1; /* disallow rotate and move? 0..1 */
19 int plotdots; /* plot dots instead of lines */
20 int top_border, bot_border, lft_border, rt_border;
21 int lft_border0; /* lft_border for y-axis labels >0 */
22 int top_left, top_right; /* edges of top line free space */
23 int Mv_delay = 400; /* msec for button click vs. button hold down */
24 int Dotrad = 2; /* dot radius in pixels */
25 int framewd=1; /* line thickness for frame (pixels) */
26 int framesep=1; /* distance between frame and surrounding text */
27 int outersep=1; /* distance: surrounding text to screen edge */
28 Point sdigit; /* size of a digit in the font */
29 Point smaxch; /* assume any character in font fits in this */
30 double underscan = .05; /* fraction of frame initially unused per side */
31 double fuzz = 6; /* selection tolerance in pixels */
32 int tick_len = 15; /* length of axis label tick mark in pixels */
33 FILE* logfil = 0; /* dump selected points here if nonzero */
35 #define labdigs 3 /* allow this many sig digits in axis labels */
36 #define digs10pow 1000 /* pow(10,labdigs) */
37 #define axis_color clr_im(DLtblue)
42 /********************************* Utilities *********************************/
44 /* Prepend string s to null-terminated string in n-byte buffer buf[], truncating if
45 necessary and using a space to separate s from the rest of buf[].
47 char* str_insert(char* buf, char* s, int n)
49 int blen, slen = strlen(s) + 1;
51 {strncpy(buf,s,n); buf[n-1]='\0'; return buf;}
54 buf[blen=n-slen-1] = '\0';
55 memmove(buf+slen, buf, slen+blen+1);
56 memcpy(buf, s, slen-1);
61 /* Alter string smain (without lengthening it) so as to remove the first occurrence of
62 ssub, assuming ssub is ASCII. Return nonzero (true) if string smain had to be changed.
63 In spite of the ASCII-centric appearance, I think this can handle UTF in smain.
65 int remove_substr(char* smain, char* ssub)
67 char *ss, *s = strstr(smain, ssub);
72 s[0] ^= 32; /* probably tolower(s[0]) or toupper(s[0]) */
74 for (ss=s+n; *ss!=0; s++, ss++)
81 void adjust_border(Font* f)
83 int sep = framesep + outersep;
84 sdigit = stringsize(f, "8");
85 smaxch = stringsize(f, "MMMg");
86 smaxch.x = (smaxch.x + 3)/4;
87 lft_border0 = (1+labdigs)*sdigit.x + framewd + sep;
88 rt_border = (lft_border0 - sep)/2 + outersep;
89 bot_border = sdigit.y + framewd + sep;
90 top_border = smaxch.y + framewd + sep;
91 lft_border = lft_border0; /* this gets reset later */
95 int is_off_screen(Point p)
97 const Rectangle* r = &(screen->r);
98 return p.x-r->min.x<lft_border || r->max.x-p.x<rt_border
99 || p.y-r->min.y<=top_border || r->max.y-p.y<=bot_border;
107 0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
108 0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
109 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
110 0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8,
113 0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
114 0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
115 0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
116 0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00,
120 /* Wait for a mouse click and return 0 for failue if not button but (curs can be 0) */
121 int get_1click(int but, Mouse* m, Cursor* curs)
125 while (m->buttons==0)
129 return (m->buttons==Button_bit(but));
133 /* Wait for a mouse click or keyboard event from the string of expected characters. Return
134 the character code or -1 for a button-but mouse event or 0 for wrong button.
136 int get_click_or_kbd(int but, Mouse* m, const char* expected)
139 ulong expbits[4], ty;
140 expbits[0] = expbits[1] = expbits[2] = expbits[3];
141 for (; *expected!=0; expected++)
142 expbits[((*expected)>>5)&3] |= 1 << (*expected&31);
143 do ty = eread(Emouse|Ekeyboard, &ev);
144 while ((ty&Emouse) ? ev.mouse.buttons==0
145 : (ev.kbdc&~127) || !(expbits[(ev.kbdc>>5)&3] & (1<<(ev.kbdc&31))) );
149 return (ev.mouse.buttons==Button_bit(but)) ? -1 : 0;
153 /* Wait until but goes up or until a mouse event's msec passes tlimit.
154 Return a boolean result that tells whether the button went up.
156 int lift_button(int but, Mouse* m, int tlimit)
159 if (m->msec >= tlimit)
161 } while (m->buttons & Button_bit(but));
166 /* Set *m to the last pending mouse event, or the first one where but is up.
167 If no mouse events are pending, wait for the next one.
169 void latest_mouse(int but, Mouse* m)
171 int bbit = Button_bit(but);
173 } while ((m->buttons & bbit) && ecanmouse());
178 /*********************************** Colors ***********************************/
180 enum { DOrange=0xffaa00FF, Dgray=0xbbbbbbFF, DDkgreen=0x009900FF,
181 DDkred=0xcc0000FF, DViolet=0x990099FF, DDkyellow=0xaaaa00FF,
182 DLtblue=0xaaaaffFF, DPink=0xffaaaaFF,
183 /* ndraw.h sets DBlack, DBlue, DRed, DYellow, DGreen,
184 DCyan, DMagenta, DWhite */
188 typedef struct thick_color {
189 int thick; /* use 1+2*thick pixel wide lines */
190 Image* clr; /* Color to use when drawing this */
194 typedef struct color_ref {
195 ulong c; /* RGBA pixel color */
196 char* nam; /* ASCII name (matched to input, used in output)*/
197 int nam1; /* single-letter version of color name */
198 Image* im; /* replicated solid-color image */
201 color_ref clrtab[] = {
203 DPink, "Pink", 'P', 0,
204 DDkred, "Dkred", 'r', 0,
205 DOrange, "Orange", 'O', 0,
206 DYellow, "Yellow", 'Y', 0,
207 DDkyellow, "Dkyellow", 'y', 0,
208 DGreen, "Green", 'G', 0,
209 DDkgreen, "Dkgreen", 'g', 0,
210 DCyan, "Cyan", 'C', 0,
211 DBlue, "Blue", 'B', 0,
212 DLtblue, "Ltblue", 'b', 0,
213 DMagenta, "Magenta", 'M', 0,
214 DViolet, "Violet", 'V', 0,
215 Dgray, "Gray", 'A', 0,
216 DBlack, "Black", 'K', 0,
217 DWhite, "White", 'W', 0,
218 DNofill, 0, 0, 0 /* DNofill means "end of data" */
221 short nam1_idx[128]; /* the clrtab[] index for each nam1, else -1 */
224 void init_clrtab(void)
227 Rectangle r = Rect(0,0,1,1);
228 memset(&nam1_idx[0], -1, sizeof(nam1_idx));
229 for (i=0; clrtab[i].c!=DNofill; i++) {
230 clrtab[i].im = allocimage(display, r, CMAP8, 1, clrtab[i].c);
231 /* should check for 0 result? */
232 nam1_idx[clrtab[i].nam1] = i;
237 int clrim_id(Image* clr)
240 for (i=0; clrtab[i].im!=clr; i++)
241 if (clrtab[i].c==DNofill)
242 exits("bad image color");
249 for (i=0; clrtab[i].c!=clr; i++)
250 if (clrtab[i].c==DNofill)
256 #define clr_im(clr) clrtab[clr_id(clr)].im
257 #define is_Multi -2 /* dummy clrtab[] less than -1 */
260 thick_color* tc_default(thick_color *buf)
263 buf[1].clr = clr_im(DBlack);
269 /* Return an allocated array that describes the color letters (clrtab[].nam1 values,
270 optionally prefixed by 'T') in the string that starts at c0 and ends just before
271 fin. The first entry is a dummy whose thick field tells how many data entries follow.
272 If buf!=0, it should point to an array of length 2 that is to hold the output
273 (truncated to a dummy and one data entry). The error indication is 1 data entry
274 of default color and thickness; e.g., "Multi(xxbadxx)" in a label prevents gview
275 from recognizing anything that follows.
277 thick_color* parse_color_chars(const char* c0, const char* fin, thick_color *buf)
279 thick_color* tc; /* Pending return value */
280 int i, j, n=fin-c0; /* n is an upper bound on how many data members */
282 for (c=c0; c<fin-1; c++)
286 tc = (thick_color*) malloc((n+1)*sizeof(thick_color));
289 for (c=c0; c<fin && i<n; c++) {
293 return tc_default(tc);
295 j = (*c&~127) ? -1 : nam1_idx[*c];
297 return tc_default(tc);
298 tc[i].clr = clrtab[j].im;
305 /* Decide what color and thickness to use for a polyline based on the label it has in the
306 input file. The winner is whichever color name comes first, or otherwise black; and
307 thickness is determined by the presence of "Thick" in the string. Place the result
308 in *r1 and return 0 unless a Multi(...) spec is found, in which case the result is
309 an allocated array of alternative color and thickness values. A nonzero idxdest
310 requests the clrtab[] index in *idxdest and no allocated array.
312 thick_color* nam2thclr(const char* nam, thick_color *r1, int *idxdest)
314 char *c, *cbest=0, *rp=0;
319 c = strstr(nam, "Multi(");
320 if (c!=0 && (rp=strchr(c+6,')'))!=0)
321 {ibest=is_Multi; cbest=c;}
322 for (i=0; clrtab[i].nam!=0; i++) {
323 c = strstr(nam,clrtab[i].nam);
324 if (c!=0 && (ibest==-1 || c<cbest))
328 if (ibest==is_Multi) {
329 tc = parse_color_chars(cbest+6, rp, (idxdest==0 ? 0 : &buf[0]));
330 ibest = clrim_id(tc[1].clr);
333 *idxdest = (ibest<0) ? clr_id(DBlack) : ibest;
334 r1->clr = (ibest<0) ? clr_im(DBlack) : clrtab[ibest].im;
335 r1->thick = (tc!=0) ? tc[1].thick : (strstr(nam,"Thick")==0 ? 0 : 1);
340 /* Alter string nam so that nam2thick() and nam2clr() agree with *tc, using
341 buf[] (a buffer of length bufn) to store the result if it differs from nam.
342 We go to great pains to perform this alteration in a manner that will seem natural
343 to the user, i.e., we try removing a suitably isolated color name before inserting
346 char* nam_with_thclr(char* nam, const thick_color *tc, char* buf, int bufn)
350 nam2thclr(nam, &c0, &clr0i);
352 if (c0.thick==tc->thick && c0.clr==tc->clr)
354 clr0s = clrtab[clr0i].nam;
355 if (strlen(nam)<bufn) strcpy(buf,nam);
356 else {strncpy(buf,nam,bufn); buf[bufn-1]='\0';}
357 if (c0.clr != tc->clr)
358 remove_substr(buf, clr0s);
359 if (c0.thick > tc->thick)
360 while (remove_substr(buf, "Thick"))
362 nam2thclr(nam, &c0, &clr0i);
363 if (c0.clr != tc->clr)
364 str_insert(buf, clrtab[clrim_id(tc->clr)].nam, bufn);
365 if (c0.thick < tc->thick)
366 str_insert(buf, "Thick", bufn);
372 /****************************** Data structures ******************************/
374 Image* mv_bkgd; /* Background image (usually 0) */
376 typedef struct fpoint {
380 typedef struct frectangle {
384 frectangle empty_frect = {1e30, 1e30, -1e30, -1e30};
387 /* When *r2 is transformed by y=y-x*slant, might it intersect *r1 ?
389 int fintersects(const frectangle* r1, const frectangle* r2, double slant)
391 double x2min=r2->min.x, x2max=r2->max.x;
392 if (r1->max.x <= x2min || x2max <= r1->min.x)
395 {x2min*=slant; x2max*=slant;}
396 else {double t=x2min*slant; x2min=x2max*slant; x2max=t;}
397 return r1->max.y > r2->min.y-x2max && r2->max.y-x2min > r1->min.y;
400 int fcontains(const frectangle* r, fpoint p)
402 return r->min.x <=p.x && p.x<= r->max.x && r->min.y <=p.y && p.y<= r->max.y;
406 void grow_bb(frectangle* dest, const frectangle* r)
408 if (r->min.x < dest->min.x) dest->min.x=r->min.x;
409 if (r->min.y < dest->min.y) dest->min.y=r->min.y;
410 if (r->max.x > dest->max.x) dest->max.x=r->max.x;
411 if (r->max.y > dest->max.y) dest->max.y=r->max.y;
415 void slant_frect(frectangle *r, double sl)
417 r->min.y += sl*r->min.x;
418 r->max.y += sl*r->max.x;
422 fpoint fcenter(const frectangle* r)
425 c.x = .5*(r->max.x + r->min.x);
426 c.y = .5*(r->max.y + r->min.y);
431 typedef struct fpolygon {
432 fpoint* p; /* a malloc'ed array */
433 int n; /* p[] has n elements: p[0..n] */
434 frectangle bb; /* bounding box */
435 char* nam; /* name of this polygon (malloc'ed) */
436 thick_color c; /* the current color and line thickness */
437 thick_color* ct; /* 0 or malloc'ed color schemes, ct[1..ct->thick] */
438 struct fpolygon* link;
441 typedef struct fpolygons {
442 fpolygon* p; /* the head of a linked list */
443 frectangle bb; /* overall bounding box */
444 frectangle disp; /* part being mapped onto screen->r */
445 double slant_ht; /* controls how disp is slanted */
449 fpolygons univ = { /* everything there is to display */
451 1e30, 1e30, -1e30, -1e30,
457 void free_fp_etc(fpolygon* fp)
466 void set_default_clrs(fpolygons* fps, fpolygon* fpstop)
469 for (fp=fps->p; fp!=0 && fp!=fpstop; fp=fp->link)
470 fp->ct = nam2thclr(fp->nam, &fp->c, 0);
474 void fps_invert(fpolygons* fps)
477 for (p=fps->p; p!=0;) {
487 void fp_remove(fpolygons* fps, fpolygon* fp)
489 fpolygon *q, **p = &fps->p;
493 else p = &(*p)->link;
495 fps->bb = empty_frect;
496 for (q=fps->p; q!=0; q=q->link)
497 grow_bb(&fps->bb, &q->bb);
501 /* The transform maps abstract fpoint coordinates (the ones used in the input)
502 to the current screen coordinates. The do_untransform() macros reverses this.
503 If univ.slant_ht is not the height of univ.disp, the actual region in the
504 abstract coordinates is a parallelogram inscribed in univ.disp with two
505 vertical edges and two slanted slanted edges: slant_ht>0 means that the
506 vertical edges have height slant_ht and the parallelogram touches the lower
507 left and upper right corners of univ.disp; slant_ht<0 refers to a parallelogram
508 of height -slant_ht that touches the other two corners of univ.disp.
509 NOTE: the ytransform macro assumes that tr->sl times the x coordinate has
510 already been subtracted from yy.
512 typedef struct transform {
514 fpoint o, sc; /* (x,y):->(o.x+sc.x*x, o.y+sc.y*y+sl*x) */
517 #define do_transform(d,tr,s) ((d)->x = (tr)->o.x + (tr)->sc.x*(s)->x, \
518 (d)->y = (tr)->o.y + (tr)->sc.y*(s)->y \
520 #define do_untransform(d,tr,s) ((d)->x = (.5+(s)->x-(tr)->o.x)/(tr)->sc.x, \
521 (d)->y = (.5+(s)->y-(tr)->sl*(d)->x-(tr)->o.y) \
523 #define xtransform(tr,xx) ((tr)->o.x + (tr)->sc.x*(xx))
524 #define ytransform(tr,yy) ((tr)->o.y + (tr)->sc.y*(yy))
525 #define dxuntransform(tr,xx) ((xx)/(tr)->sc.x)
526 #define dyuntransform(tr,yy) ((yy)/(tr)->sc.y)
529 transform cur_trans(void)
532 Rectangle d = screen->r;
533 const frectangle* s = &univ.disp;
534 double sh = univ.slant_ht;
535 d.min.x += lft_border;
536 d.min.y += top_border;
537 d.max.x -= rt_border;
538 d.max.y -= bot_border;
539 t.sc.x = (d.max.x - d.min.x)/(s->max.x - s->min.x);
540 t.sc.y = -(d.max.y - d.min.y)/fabs(sh);
542 t.sl = -t.sc.y*(s->max.y-s->min.y-sh)/(s->max.x - s->min.x);
543 t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->max.x;
545 t.sl = t.sc.y*(s->max.y-s->min.y+sh)/(s->max.x - s->min.x);
546 t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->min.x;
548 t.o.x = d.min.x - t.sc.x*s->min.x;
553 double u_slant_amt(fpolygons *u)
555 double sh=u->slant_ht, dy=u->disp.max.y - u->disp.min.y;
556 double dx = u->disp.max.x - u->disp.min.x;
557 return (sh>0) ? (dy-sh)/dx : -(dy+sh)/dx;
561 /* Set *y0 and *y1 to the lower and upper bounds of the set of y-sl*x values that
562 *u says to display, where sl is the amount of slant.
564 double set_unslanted_y(fpolygons *u, double *y0, double *y1)
566 double yy1, sl=u_slant_amt(u);
567 if (u->slant_ht > 0) {
568 *y0 = u->disp.min.y - sl*u->disp.min.x;
569 yy1 = *y0 + u->slant_ht;
571 yy1 = u->disp.max.y - sl*u->disp.min.x;
572 *y0 = yy1 + u->slant_ht;
582 /*************************** The region to display ****************************/
584 void nontrivial_interval(double *lo, double *hi)
587 double mid = .5*(*lo + *hi);
588 double tweak = 1e-6 + 1e-6*fabs(mid);
597 double dw = (univ.bb.max.x - univ.bb.min.x)*underscan;
598 double dh = (univ.bb.max.y - univ.bb.min.y)*underscan;
599 univ.disp.min.x = univ.bb.min.x - dw;
600 univ.disp.min.y = univ.bb.min.y - dh;
601 univ.disp.max.x = univ.bb.max.x + dw;
602 univ.disp.max.y = univ.bb.max.y + dh;
603 nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x);
604 nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y);
605 univ.slant_ht = univ.disp.max.y - univ.disp.min.y; /* means no slant */
609 void recenter_disp(Point c)
611 transform tr = cur_trans();
613 do_untransform(&cc, &tr, &c);
614 off.x = cc.x - .5*(univ.disp.min.x + univ.disp.max.x);
615 off.y = cc.y - .5*(univ.disp.min.y + univ.disp.max.y);
616 univ.disp.min.x += off.x;
617 univ.disp.min.y += off.y;
618 univ.disp.max.x += off.x;
619 univ.disp.max.y += off.y;
623 /* Find the upper-left and lower-right corners of the bounding box of the
624 parallelogram formed by untransforming the rectangle rminx, rminy, ... (given
625 in screen coordinates), and return the height of the parallelogram (negated
626 if it slopes downward).
628 double untransform_corners(double rminx, double rminy, double rmaxx, double rmaxy,
629 fpoint *ul, fpoint *lr)
631 fpoint r_ur, r_ul, r_ll, r_lr; /* corners of the given recangle */
632 fpoint ur, ll; /* untransformed versions of r_ur, r_ll */
633 transform tr = cur_trans();
635 r_ur.x=rmaxx; r_ur.y=rminy;
636 r_ul.x=rminx; r_ul.y=rminy;
637 r_ll.x=rminx; r_ll.y=rmaxy;
638 r_lr.x=rmaxx; r_lr.y=rmaxy;
639 do_untransform(ul, &tr, &r_ul);
640 do_untransform(lr, &tr, &r_lr);
641 do_untransform(&ur, &tr, &r_ur);
642 do_untransform(&ll, &tr, &r_ll);
657 void disp_dozoom(double rminx, double rminy, double rmaxx, double rmaxy)
660 double sh = untransform_corners(rminx, rminy, rmaxx, rmaxy, &ul, &lr);
661 if (ul.x==lr.x || ul.y==lr.y)
664 univ.disp.min.x = ul.x;
665 univ.disp.max.y = ul.y;
666 univ.disp.max.x = lr.x;
667 univ.disp.min.y = lr.y;
668 nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x);
669 nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y);
673 void disp_zoomin(Rectangle r)
675 disp_dozoom(r.min.x, r.min.y, r.max.x, r.max.y);
679 void disp_zoomout(Rectangle r)
681 double qminx, qminy, qmaxx, qmaxy;
683 Rectangle s = screen->r;
684 if (r.min.x==r.max.x || r.min.y==r.max.y)
686 s.min.x += lft_border;
687 s.min.y += top_border;
688 s.max.x -= rt_border;
689 s.max.y -= bot_border;
690 scx = (s.max.x - s.min.x)/(r.max.x - r.min.x);
691 scy = (s.max.y - s.min.y)/(r.max.y - r.min.y);
692 qminx = s.min.x + scx*(s.min.x - r.min.x);
693 qmaxx = s.max.x + scx*(s.max.x - r.max.x);
694 qminy = s.min.y + scy*(s.min.y - r.min.y);
695 qmaxy = s.max.y + scy*(s.max.y - r.max.y);
696 disp_dozoom(qminx, qminy, qmaxx, qmaxy);
700 void expand2(double* a, double* b, double f)
702 double mid = .5*(*a + *b);
703 *a = mid + f*(*a - mid);
704 *b = mid + f*(*b - mid);
707 void disp_squareup(void)
709 double dx = univ.disp.max.x - univ.disp.min.x;
710 double dy = univ.disp.max.y - univ.disp.min.y;
711 dx /= screen->r.max.x - lft_border - screen->r.min.x - rt_border;
712 dy /= screen->r.max.y - bot_border - screen->r.min.y - top_border;
714 expand2(&univ.disp.min.y, &univ.disp.max.y, dx/dy);
715 else expand2(&univ.disp.min.x, &univ.disp.max.x, dy/dx);
716 univ.slant_ht = univ.disp.max.y - univ.disp.min.y;
720 /* Slant so that p and q appear at the same height on the screen and the
721 screen contains the smallest possible superset of what its previous contents.
723 void slant_disp(fpoint p, fpoint q)
725 double yll, ylr, yul, yur; /* corner y coords of displayed parallelogram */
731 yll=yul=univ.disp.min.y; yul+=sh;
732 ylr=yur=univ.disp.max.y; ylr-=sh;
734 yll=yul=univ.disp.max.y; yll+=sh;
735 ylr=yur=univ.disp.min.y; yur-=sh;
737 dy = (univ.disp.max.x-univ.disp.min.x)*(q.y - p.y)/(q.x - p.x);
741 else {yul-=dy; ylr+=dy;}
743 univ.disp.min.y = yll;
744 univ.disp.max.y = yur;
745 univ.slant_ht = yur - ylr;
747 univ.disp.max.y = yul;
748 univ.disp.min.y = ylr;
749 univ.slant_ht = ylr - yur;
756 /******************************** Ascii input ********************************/
758 void set_fbb(fpolygon* fp)
760 fpoint lo=fp->p[0], hi=fp->p[0];
761 const fpoint *q, *qtop;
762 for (qtop=(q=fp->p)+fp->n; ++q<=qtop;) {
763 if (q->x < lo.x) lo.x=q->x;
764 if (q->y < lo.y) lo.y=q->y;
765 if (q->x > hi.x) hi.x=q->x;
766 if (q->y > hi.y) hi.y=q->y;
772 char* mystrdup(char* s)
774 char *r, *t = strrchr(s,'"');
777 while (t>s && (t[-1]=='\n' || t[-1]=='\r'))
786 int is_valid_label(char* lab)
790 return (t=strrchr(lab,'"'))!=0 && t!=lab && strspn(t+1," \t\r\n")==strlen(t+1);
791 return strcspn(lab," \t")==strlen(lab);
794 /* Read a polyline and update the number of lines read. A zero result indicates bad
795 syntax if *lineno increases; otherwise it indicates end of file.
797 fpolygon* rd_fpoly(FILE* fin, int *lineno)
799 char buf[4096], junk[2];
803 if (!fgets(buf,4096,fin))
806 if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2)
808 fp = malloc(sizeof(fpolygon));
810 fp->p = malloc(allocn*sizeof(fpoint));
815 fp->c.clr = clr_im(DBlack);
817 while (fgets(buf,4096,fin)) {
819 if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2) {
820 if (!is_valid_label(buf))
821 {free_fp_etc(fp); return 0;}
822 fp->nam = (buf[0]=='"') ? buf+1 : buf;
825 if (++(fp->n) == allocn)
826 fp->p = realloc(fp->p, (allocn<<=1)*sizeof(fpoint));
829 fp->nam = mystrdup(fp->nam);
836 /* Read input into *fps and return 0 or a line number where there's a syntax error */
837 int rd_fpolys(FILE* fin, fpolygons* fps)
839 fpolygon *fp, *fp0=fps->p;
840 int lineno=0, ok_upto=0;
841 while ((fp=rd_fpoly(fin,&lineno)) != 0) {
845 grow_bb(&fps->bb, &fp->bb);
847 set_default_clrs(fps, fp0);
848 return (ok_upto==lineno) ? 0 : lineno;
852 /* Read input from file fnam and return an error line no., -1 for "can't open"
855 int doinput(char* fnam)
857 FILE* fin = strcmp(fnam,"-")==0 ? stdin : fopen(fnam, "r");
861 errline_or0 = rd_fpolys(fin, &univ);
868 /******************************** Ascii output ********************************/
870 fpolygon* fp_reverse(fpolygon* fp)
874 fpolygon* q = fp->link;
882 void wr_fpoly(FILE* fout, const fpolygon* fp)
886 for (i=0; i<=fp->n; i++)
887 fprintf(fout,"%.12g\t%.12g\n", fp->p[i].x, fp->p[i].y);
888 fprintf(fout,"\"%s\"\n", nam_with_thclr(fp->nam, &fp->c, buf, 256));
891 void wr_fpolys(FILE* fout, fpolygons* fps)
894 fps->p = fp_reverse(fps->p);
895 for (fp=fps->p; fp!=0; fp=fp->link)
897 fps->p = fp_reverse(fps->p);
901 int dooutput(char* fnam)
903 FILE* fout = fopen(fnam, "w");
906 wr_fpolys(fout, &univ);
914 /************************ Clipping to screen rectangle ************************/
916 /* Find the t values, 0<=t<=1 for which x0+t*(x1-x0) is between xlo and xhi,
917 or return 0 to indicate no such t values exist. If returning 1, set *t0 and
918 *t1 to delimit the t interval.
920 int do_xory(double x0, double x1, double xlo, double xhi, double* t0, double* t1)
924 if (x1<xlo) return 0;
925 *t0 = (xlo-x0)/(x1-x0);
926 if (x1>xhi) *t1 = (xhi-x0)/(x1-x0);
928 if (x1>xhi) return 0;
929 *t0 = (xhi-x0)/(x1-x0);
930 if (x1<xlo) *t1 = (xlo-x0)/(x1-x0);
933 if (x1>xhi) *t1 = (xhi-x0)/(x1-x0);
934 else if (x1<xlo) *t1 = (xlo-x0)/(x1-x0);
941 /* After mapping y to y-slope*x, what initial fraction of the *p to *q edge is
942 outside of *r? Note that the edge could start outside *r, pass through *r,
943 and wind up outside again.
945 double frac_outside(const fpoint* p, const fpoint* q, const frectangle* r,
948 double t0, t1, tt0, tt1;
949 double px=p->x, qx=q->x;
950 if (!do_xory(px, qx, r->min.x, r->max.x, &t0, &t1))
952 if (!do_xory(p->y-slope*px, q->y-slope*qx, r->min.y, r->max.y, &tt0, &tt1))
956 if (t1<=t0 || tt1<=t0)
962 /* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find
963 the maximum tt such that F(0..tt) is all inside of r, assuming p0 is inside.
964 Coordinates are transformed by y=y-x*slope before testing against r.
966 double in_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope)
968 const fpoint* p = p0;
972 while (r.min.x<=(px=p->x) && px<=r.max.x
973 && r.min.y<=(py=p->y-slope*px) && py<=r.max.y);
974 return (p - p0) - frac_outside(p, p-1, &r, slope);
978 /* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find
979 the maximum tt such that F(0..tt) is all outside of *r. Coordinates are
980 transformed by y=y-x*slope before testing against r.
982 double out_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope)
984 const fpoint* p = p0;
986 do { if (p->x < r.min.x)
987 do if (++p>pn) return pn-p0;
988 while (p->x <= r.min.x);
989 else if (p->x > r.max.x)
990 do if (++p>pn) return pn-p0;
991 while (p->x >= r.max.x);
992 else if (p->y-slope*p->x < r.min.y)
993 do if (++p>pn) return pn-p0;
994 while (p->y-slope*p->x <= r.min.y);
995 else if (p->y-slope*p->x > r.max.y)
996 do if (++p>pn) return pn-p0;
997 while (p->y-slope*p->x >= r.max.y);
999 } while ((fr=frac_outside(p-1,p,&r,slope)) == 1);
1000 return (p - p0) + fr-1;
1005 /*********************** Drawing frame and axis labels ***********************/
1008 #define Len_thous 30 /* bound on strlen(thous_nam[i]) */
1009 char* thous_nam[Nthous] = {
1010 "one", "thousand", "million", "billion",
1011 "trillion", "quadrillion", "quintillion",
1015 typedef struct lab_interval {
1016 double sep; /* separation between tick marks */
1017 double unit; /* power of 1000 divisor */
1018 int logunit; /* log base 1000 of of this divisor */
1019 double off; /* offset to subtract before dividing */
1023 char* abbrev_num(double x, const lab_interval* iv)
1025 static char buf[16];
1026 double dx = x - iv->off;
1027 dx = iv->sep * floor(dx/iv->sep + .5);
1028 sprintf(buf,"%g", dx/iv->unit);
1033 double lead_digits(double n, double r) /* n truncated to power of 10 above r */
1035 double rr = pow(10, ceil(log10(r)));
1036 double nn = (n<rr) ? 0.0 : rr*floor(n/rr);
1037 if (n+r-nn >= digs10pow) {
1039 nn = (n<rr) ? 0.0 : rr*floor(n/rr);
1045 lab_interval next_larger(double s0, double xlo, double xhi)
1049 r.logunit = (int) floor(log10(s0) + LOG2);
1050 r.unit = pow(10, r.logunit);
1053 if (nhi >= digs10pow)
1054 r.off = r.unit*lead_digits(nlo, nhi-nlo);
1055 else if (nlo <= -digs10pow)
1056 r.off = -r.unit*lead_digits(-nhi, nhi-nlo);
1058 r.sep = (s0<=r.unit) ? r.unit : (s0<2*r.unit ? 2*r.unit : 5*r.unit);
1059 switch (r.logunit%3) {
1060 case 1: r.unit*=.1; r.logunit--;
1063 r.unit*=10; r.logunit++;
1065 case -2: r.unit*=100; r.logunit+=2;
1072 double min_hsep(const transform* tr)
1074 double s = (2+labdigs)*sdigit.x;
1075 double ss = (univ.disp.min.x<0) ? s+sdigit.x : s;
1076 return dxuntransform(tr, ss);
1080 lab_interval mark_x_axis(const transform* tr)
1082 fpoint p = univ.disp.min;
1083 Point q, qtop, qbot, tmp;
1084 double x0=univ.disp.min.x, x1=univ.disp.max.x;
1085 double seps0, nseps, seps;
1086 lab_interval iv = next_larger(min_hsep(tr), x0, x1);
1087 set_unslanted_y(&univ, &p.y, 0);
1088 q.y = ytransform(tr, p.y) + .5;
1089 qtop.y = q.y - tick_len;
1090 qbot.y = q.y + framewd + framesep;
1091 seps0 = ceil(x0/iv.sep);
1092 for (seps=0, nseps=floor(x1/iv.sep)-seps0; seps<=nseps; seps+=1) {
1093 char* num = abbrev_num((p.x=iv.sep*(seps0+seps)), &iv);
1094 Font* f = display->defaultfont;
1095 q.x = qtop.x = qbot.x = xtransform(tr, p.x);
1096 line(screen, qtop, q, Enddisc, Enddisc, 0, axis_color, q);
1097 tmp = stringsize(f, num);
1099 string(screen, qbot, display->black, qbot, f, num);
1105 lab_interval mark_y_axis(const transform* tr)
1107 Font* f = display->defaultfont;
1108 fpoint p = univ.disp.min;
1110 double y0, y1, seps0, nseps, seps;
1112 set_unslanted_y(&univ, &y0, &y1);
1113 iv = next_larger(dyuntransform(tr,-f->height), y0, y1);
1114 q.x = xtransform(tr, p.x) - .5;
1115 qrt.x = q.x + tick_len;
1116 qlft.x = q.x - (framewd + framesep);
1117 seps0 = ceil(y0/iv.sep);
1118 for (seps=0, nseps=floor(y1/iv.sep)-seps0; seps<=nseps; seps+=1) {
1119 char* num = abbrev_num((p.y=iv.sep*(seps0+seps)), &iv);
1120 Point qq = stringsize(f, num);
1121 q.y = qrt.y = qlft.y = ytransform(tr, p.y);
1122 line(screen, qrt, q, Enddisc, Enddisc, 0, axis_color, q);
1123 qq.x = qlft.x - qq.x;
1124 qq.y = qlft.y - qq.y/2;
1125 string(screen, qq, display->black, qq, f, num);
1131 void lab_iv_info(const lab_interval *iv, double slant, char* buf, int *n)
1134 (*n) += sprintf(buf+*n,"-%.12g",iv->off);
1135 else if (iv->off < 0)
1136 (*n) += sprintf(buf+*n,"+%.12g",-iv->off);
1138 (*n) += sprintf(buf+*n,"-%.6gx", slant);
1140 (*n) += sprintf(buf+*n,"+%.6gx", -slant);
1141 if (abs(iv->logunit) >= Nthous)
1142 (*n) += sprintf(buf+*n," in 1e%d units", 3*iv->logunit);
1143 else if (iv->logunit > 0)
1144 (*n) += sprintf(buf+*n," in %ss", thous_nam[iv->logunit]);
1145 else if (iv->logunit < 0)
1146 (*n) += sprintf(buf+*n," in %sths", thous_nam[-iv->logunit]);
1150 void draw_xy_ranges(const lab_interval *xiv, const lab_interval *yiv)
1153 char buf[2*(19+Len_thous+8)+50];
1156 lab_iv_info(xiv, 0, buf, &bufn);
1157 bufn += sprintf(buf+bufn, "; y");
1158 lab_iv_info(yiv, u_slant_amt(&univ), buf, &bufn);
1160 p = stringsize(display->defaultfont, buf);
1161 top_left = screen->r.min.x + lft_border;
1162 p.x = top_right = screen->r.max.x - rt_border - p.x;
1163 p.y = screen->r.min.y + outersep;
1164 string(screen, p, display->black, p, display->defaultfont, buf);
1168 transform draw_frame(void)
1170 lab_interval x_iv, y_iv;
1172 Rectangle r = screen->r;
1173 lft_border = (univ.disp.min.y<0) ? lft_border0+sdigit.x : lft_border0;
1175 r.min.x += lft_border;
1176 r.min.y += top_border;
1177 r.max.x -= rt_border;
1178 r.max.y -= bot_border;
1179 border(screen, r, -framewd, axis_color, r.min);
1180 x_iv = mark_x_axis(&tr);
1181 y_iv = mark_y_axis(&tr);
1182 draw_xy_ranges(&x_iv, &y_iv);
1188 /*************************** Finding the selection ***************************/
1190 typedef struct pt_on_fpoly {
1191 fpoint p; /* the point */
1192 fpolygon* fp; /* the fpolygon it lies on */
1193 double t; /* how many knots from the beginning */
1197 static double myx, myy;
1198 #define mydist(p,o,sl,xwt,ywt) (myx=(p).x-(o).x, myy=(p).y-sl*(p).x-(o).y, \
1199 xwt*myx*myx + ywt*myy*myy)
1201 /* At what fraction of the way from p0[0] to p0[1] is mydist(p,ctr,slant,xwt,ywt)
1204 double closest_time(const fpoint* p0, const fpoint* ctr, double slant,
1205 double xwt, double ywt)
1207 double p00y=p0[0].y-slant*p0[0].x, p01y=p0[1].y-slant*p0[1].x;
1208 double dx=p0[1].x-p0[0].x, dy=p01y-p00y;
1209 double x0=p0[0].x-ctr->x, y0=p00y-ctr->y;
1210 double bot = xwt*dx*dx + ywt*dy*dy;
1213 return -(xwt*x0*dx + ywt*y0*dy)/bot;
1217 /* Scan the polygonal path of length len knots starting at p0, and find the
1218 point that the transformation y=y-x*slant makes closest to the center of *r,
1219 where *r itself defines the distance metric. Knots get higher priority than
1220 points between knots. If psel->t is negative, always update *psel; otherwise
1221 update *psel only if the scan can improve it. Return a boolean that says
1222 whether *psel was updated.
1223 Note that *r is a very tiny rectangle (tiny when converted screen pixels)
1224 such that anything in *r is considered close enough to match the mouse click.
1225 The purpose of this routine is to be careful in case there is a lot of hidden
1226 detail in the tiny rectangle *r.
1228 int improve_pt(fpoint* p0, double len, const frectangle* r, double slant,
1231 fpoint ctr = fcenter(r);
1232 double x_wt=2/(r->max.x-r->min.x), y_wt=2/(r->max.y-r->min.y);
1233 double xwt=x_wt*x_wt, ywt=y_wt*y_wt;
1234 double d, dbest = (psel->t <0) ? 1e30 : mydist(psel->p,ctr,slant,xwt,ywt);
1235 double tt, dbest0 = dbest;
1237 int ilen = (int) len;
1238 if (len==0 || ilen>0) {
1240 for (i=(len==0 ? 0 : 1); i<=ilen; i++) {
1241 d = mydist(p0[i], ctr, slant, xwt, ywt);
1243 {psel->p=p0[i]; psel->t=i; dbest=d;}
1245 return (dbest < dbest0);
1247 tt = closest_time(p0, &ctr, slant, xwt, ywt);
1250 pp.x = p0[0].x + tt*(p0[1].x - p0[0].x);
1251 pp.y = p0[0].y + tt*(p0[1].y - p0[0].y);
1252 if (mydist(pp, ctr, slant, xwt, ywt) < dbest) {
1261 /* Test *fp against *r after transforming by y=y-x*slope, and set *psel accordingly.
1263 void select_in_fpoly(fpolygon* fp, const frectangle* r, double slant,
1266 fpoint *p0=fp->p, *pn=fp->p+fp->n;
1269 {improve_pt(p0, 0, r, slant, psel); psel->fp=fp; return;}
1270 while ((l1=out_length(p0,pn,*r,slant)) < pn-p0) {
1275 p0[0].x += l1*(p0[1].x - p0[0].x);
1276 p0[0].y += l1*(p0[1].y - p0[0].y);
1277 l2 = in_length(p0, pn, *r, slant);
1278 if (improve_pt(p0, l2, r, slant, psel)) {
1279 if (l1==0 && psel->t!=((int) psel->t)) {
1282 } else if (psel->t < 1)
1283 psel->t += l1*(1 - psel->t);
1284 psel->t += p0 - fp->p;
1288 p0 += (l2>0) ? ((int) ceil(l2)) : 1;
1293 /* Test all the fpolygons against *r after transforming by y=y-x*slope, and return
1294 the resulting selection, if any.
1296 pt_on_fpoly* select_in_univ(const frectangle* r, double slant)
1298 static pt_on_fpoly answ;
1301 for (fp=univ.p; fp!=0; fp=fp->link)
1302 if (fintersects(r, &fp->bb, slant))
1303 select_in_fpoly(fp, r, slant, &answ);
1311 /**************************** Using the selection ****************************/
1313 pt_on_fpoly cur_sel; /* current selection if cur_sel.t>=0 */
1314 pt_on_fpoly prev_sel; /* previous selection if prev_sel.t>=0 (for slant) */
1315 Image* sel_bkg = 0; /* what's behind the red dot */
1318 void clear_txt(void)
1321 r.min = screen->r.min;
1322 r.min.x += lft_border;
1323 r.min.y += outersep;
1325 r.max.y = r.min.y + smaxch.y;
1326 draw(screen, r, display->white, display->opaque, r.min);
1331 Rectangle sel_dot_box(const transform* tr)
1336 ctr.x = ctr.y = Dotrad;
1337 else do_transform(&ctr, tr, &cur_sel.p);
1338 r.min.x=ctr.x-Dotrad; r.max.x=ctr.x+Dotrad+1;
1339 r.min.y=ctr.y-Dotrad; r.max.y=ctr.y+Dotrad+1;
1344 void unselect(const transform* tr)
1348 sel_bkg = allocimage(display, sel_dot_box(0), CMAP8, 0, DWhite);
1354 {tra=cur_trans(); tr=&tra;}
1355 draw(screen, sel_dot_box(tr), sel_bkg, display->opaque, ZP);
1360 /* Text at top right is written first and this low-level routine clobbers it if
1361 the new top-left text would overwrite it. However, users of this routine should
1362 try to keep the new text short enough to avoid this.
1364 void show_mytext(char* msg)
1366 Point tmp, pt = screen->r.min;
1368 tmp = stringsize(display->defaultfont, msg);
1370 pt.x=top_left; pt.y+=outersep;
1371 if (top_left+siz > top_right) {
1374 r.min.x = top_right;
1375 r.max.y = r.min.y + smaxch.y;
1376 r.max.x = top_left+siz;
1377 draw(screen, r, display->white, display->opaque, r.min);
1378 top_right = top_left+siz;
1380 string(screen, pt, display->black, ZP, display->defaultfont, msg);
1385 double rnd(double x, double tol) /* round to enough digits for accuracy tol */
1387 double t = pow(10, floor(log10(tol)));
1388 return t * floor(x/t + .5);
1391 double t_tol(double xtol, double ytol)
1393 int t = (int) floor(cur_sel.t);
1394 fpoint* p = cur_sel.fp->p;
1398 dx = fabs(p[t+1].x - p[t].x);
1399 dy = fabs(p[t+1].y - p[t].y);
1400 xtol /= (xtol>dx) ? xtol : dx;
1401 ytol /= (ytol>dy) ? ytol : dy;
1402 return (xtol<ytol) ? xtol : ytol;
1405 void say_where(const transform* tr)
1407 double xtol=dxuntransform(tr,1), ytol=dyuntransform(tr,-1);
1409 int n, nmax = (top_right - top_left)/smaxch.x;
1412 n = sprintf(buf,"(%.14g,%.14g) at t=%.14g",
1413 rnd(cur_sel.p.x,xtol), rnd(cur_sel.p.y,ytol),
1414 rnd(cur_sel.t, t_tol(xtol,ytol)));
1415 if (cur_sel.fp->nam[0] != 0)
1416 sprintf(buf+n," %.*s", nmax-n-1, cur_sel.fp->nam);
1421 void reselect(const transform* tr) /* uselect(); set cur_sel; call this */
1429 {tra=cur_trans(); tr=&tra;}
1430 do_transform(&p2, tr, &cur_sel.p);
1431 if (fabs(p2.x)+fabs(p2.y)>1e8 || (pt2.x=p2.x, pt2.y=p2.y, is_off_screen(pt2)))
1432 {cur_sel.t= -1; return;}
1433 pt3.x=pt2.x-Dotrad; pt3.y=pt2.y-Dotrad;
1434 draw(sel_bkg, sel_dot_box(0), screen, display->opaque, pt3);
1435 fillellipse(screen, pt2, Dotrad, Dotrad, clr_im(DRed), pt2);
1440 void do_select(Point pt)
1442 transform tr = cur_trans();
1443 fpoint pt1, pt2, ctr;
1448 do_untransform(&ctr, &tr, &pt);
1449 pt1.x=pt.x-fuzz; pt1.y=pt.y+fuzz;
1450 pt2.x=pt.x+fuzz; pt2.y=pt.y-fuzz;
1451 do_untransform(&r.min, &tr, &pt1);
1452 do_untransform(&r.max, &tr, &pt2);
1453 slant = u_slant_amt(&univ);
1454 slant_frect(&r, -slant);
1455 psel = select_in_univ(&r, slant);
1459 fprintf(logfil,"%.14g\t%.14g\n", psel->p.x, psel->p.y);
1467 /***************************** Prompting for text *****************************/
1469 void unshow_mytext(char* msg)
1472 Point siz = stringsize(display->defaultfont, msg);
1474 r.min.y = screen->r.min.y + outersep;
1476 r.max.y = r.min.y + siz.y;
1477 r.max.x = r.min.x + siz.x;
1478 draw(screen, r, display->white, display->opaque, r.min);
1482 /* Show the given prompt and read a line of user input. The text appears at the
1483 top left. If it runs into the top right text, we stop echoing but let the user
1484 continue typing blind if he wants to.
1486 char* prompt_text(char* prompt)
1488 static char buf[200];
1489 int n0, n=0, nshown=0;
1492 show_mytext(prompt);
1493 while (n<200-1-UTFmax && (c=ekbd())!='\n') {
1498 while (n>0 && (buf[n-1]&0xc0)==0x80);
1500 {unshow_mytext(buf+n); nshown=n;}
1503 n += runetochar(buf+n, &c);
1505 if (nshown==n0 && top_right-top_left >= smaxch.x)
1506 {show_mytext(buf+n0); nshown=n;}
1516 /**************************** Redrawing the screen ****************************/
1518 /* Let p0 and its successors define a piecewise-linear function of a paramter t,
1519 and draw the 0<=t<=n1 portion using transform *tr.
1521 void draw_fpts(const fpoint* p0, double n1, const transform* tr, int thick,
1525 const fpoint* p = p0 + n;
1529 pp.x = p[0].x + (n1-n)*(p[1].x - p[0].x);
1530 pp.y = p[0].y + (n1-n)*(p[1].y - p[0].y);
1532 do_transform(&qq, tr, &pp);
1534 fillellipse(screen, qq, 1+thick, 1+thick, clr, qq);
1535 for (; p>=p0; p--) {
1536 do_transform(&q, tr, p);
1538 fillellipse(screen, q, Dotrad, Dotrad, clr, q);
1540 line(screen, qq, q, Enddisc, Enddisc, thick, clr, qq);
1545 void draw_1fpoly(const fpolygon* fp, const transform* tr, Image* clr,
1546 const frectangle *udisp, double slant)
1548 fpoint *p0=fp->p, *pn=fp->p+fp->n;
1550 if (p0==pn && fcontains(udisp,*p0))
1551 {draw_fpts(p0, 0, tr, fp->c.thick, clr); return;}
1552 while ((l1=out_length(p0,pn,*udisp,slant)) < pn-p0) {
1557 p0[0].x += l1*(p0[1].x - p0[0].x);
1558 p0[0].y += l1*(p0[1].y - p0[0].y);
1559 l2 = in_length(p0, pn, *udisp, slant);
1560 draw_fpts(p0, l2, tr, fp->c.thick, clr);
1562 p0 += (l2>0) ? ((int) ceil(l2)) : 1;
1567 double get_clip_data(const fpolygons *u, frectangle *r)
1569 double slant = set_unslanted_y(u, &r->min.y, &r->max.y);
1570 r->min.x = u->disp.min.x;
1571 r->max.x = u->disp.max.x;
1576 void draw_fpoly(const fpolygon* fp, const transform* tr, Image* clr)
1579 double slant = get_clip_data(&univ, &r);
1580 draw_1fpoly(fp, tr, clr, &r, slant);
1584 void eresized(int new)
1590 if(new && getwindow(display, Refmesg) < 0) {
1591 fprintf(stderr,"can't reattach to window\n");
1594 draw(screen, screen->r, display->white, display->opaque, screen->r.min);
1596 slant = get_clip_data(&univ, &clipr);
1597 for (fp=univ.p; fp!=0; fp=fp->link)
1598 if (fintersects(&clipr, &fp->bb, slant))
1599 draw_1fpoly(fp, &tr, fp->c.clr, &clipr, slant);
1601 if (mv_bkgd!=0 && mv_bkgd->repl==0) {
1603 mv_bkgd = display->white;
1605 flushimage(display, 1);
1611 /********************************* Recoloring *********************************/
1613 int draw_palette(int n) /* n is number of colors; returns patch dy */
1615 int y0 = screen->r.min.y + top_border;
1616 int dy = (screen->r.max.y - bot_border - y0)/n;
1620 r.min.x = screen->r.max.x - rt_border + framewd;
1622 r.max.x = screen->r.max.x;
1623 for (i=0; i<n; i++) {
1624 draw(screen, r, clrtab[i].im, display->opaque, r.min);
1632 Image* palette_color(Point pt, int dy, int n)
1633 { /* mouse at pt, patch size dy, n colors */
1635 if (screen->r.max.x - pt.x > rt_border - framewd)
1637 yy = pt.y - (screen->r.min.y + top_border);
1638 if (yy<0 || yy>=n*dy)
1640 return clrtab[yy/dy].im;
1644 void all_set_clr(fpolygons* fps, Image* clr)
1647 for (p=fps->p; p!=0; p=p->link)
1652 void all_set_scheme(fpolygons* fps, int scheme)
1655 for (p=fps->p; p!=0; p=p->link)
1656 if (p->ct!=0 && scheme <= p->ct[0].thick)
1657 p->c = p->ct[scheme];
1661 void do_recolor(int but, Mouse* m, int alluniv)
1663 int sel, clkk, nclr = clr_id(DWhite);
1664 int dy = draw_palette(nclr);
1666 clkk = get_click_or_kbd(but, m, "123456789abcdefghijklmnopqrstuvwxyz");
1668 clr = palette_color(m->xy, dy, nclr);
1671 all_set_clr(&univ, clr);
1672 else cur_sel.fp->c.clr = clr;
1675 lift_button(but, m, Never);
1676 } else if (clkk > 0) {
1677 sel = ('0'<clkk&&clkk<='9') ? 0 : 10+(clkk-'a')*10;
1678 while (!('0'<=clkk&&clkk<='9'))
1682 all_set_scheme(&univ, sel);
1683 else if (sel <= cur_sel.fp->ct[0].thick)
1684 cur_sel.fp->c = cur_sel.fp->ct[sel];
1690 /****************************** Move and rotate ******************************/
1692 void prepare_mv(const fpolygon* fp)
1694 Rectangle r = screen->r;
1696 int dt = 1 + fp->c.thick;
1697 r.min.x+=lft_border-dt; r.min.y+=top_border-dt;
1698 r.max.x-=rt_border-dt; r.max.y-=bot_border-dt;
1699 if (mv_bkgd!=0 && mv_bkgd->repl==0)
1701 mv_bkgd = allocimage(display, r, CMAP8, 0, DNofill);
1703 mv_bkgd = display->white;
1704 else { transform tr = cur_trans();
1705 draw(mv_bkgd, r, screen, display->opaque, r.min);
1706 draw(mv_bkgd, sel_dot_box(&tr), sel_bkg, display->opaque, ZP);
1709 draw_fpoly(fp, &tr, display->white);
1715 void move_fp(fpolygon* fp, double dx, double dy)
1717 fpoint *p, *pn=fp->p+fp->n;
1718 for (p=fp->p; p<=pn; p++) {
1722 (fp->bb.min.x)+=dx; (fp->bb.min.y)+=dy;
1723 (fp->bb.max.x)+=dx; (fp->bb.max.y)+=dy;
1727 void rotate_fp(fpolygon* fp, fpoint o, double theta)
1729 double s=sin(theta), c=cos(theta);
1730 fpoint *p, *pn=fp->p+fp->n;
1731 for (p=fp->p; p<=pn; p++) {
1732 double x=p->x-o.x, y=p->y-o.y;
1733 (p->x) = o.x + c*x - s*y;
1734 (p->y) = o.y + s*x + c*y;
1740 /* Move the selected fpolygon so the selected point tracks the mouse, and return
1741 the total amount of movement. Button but has already been held down for at
1742 least Mv_delay milliseconds and the mouse might have moved some distance.
1744 fpoint do_move(int but, Mouse* m)
1746 transform tr = cur_trans();
1747 int bbit = Button_bit(but);
1748 fpolygon* fp = cur_sel.fp;
1749 fpoint loc, loc0=cur_sel.p;
1750 double tsav = cur_sel.t;
1752 do { latest_mouse(but, m);
1753 (fp->c.thick)++; /* line() DISAGREES WITH ITSELF */
1754 draw_fpoly(fp, &tr, mv_bkgd);
1756 do_untransform(&loc, &tr, &m->xy);
1757 move_fp(fp, loc.x-cur_sel.p.x, loc.y-cur_sel.p.y);
1759 draw_fpoly(fp, &tr, fp->c.clr);
1760 } while (m->buttons & bbit);
1769 double dir_angle(const Point* pt, const transform* tr)
1773 do_untransform(&p, tr, pt);
1774 dy=p.y-cur_sel.p.y; dx=p.x-cur_sel.p.x;
1775 return (dx==0 && dy==0) ? 0.0 : atan2(dy, dx);
1779 /* Rotate the selected fpolygon around the selection point so as to track the
1780 direction angle from the selected point to m->xy. Stop when button but goes
1781 up and return the total amount of rotation in radians.
1783 double do_rotate(int but, Mouse* m)
1785 transform tr = cur_trans();
1786 int bbit = Button_bit(but);
1787 fpolygon* fp = cur_sel.fp;
1788 double theta0 = dir_angle(&m->xy, &tr);
1789 double th, theta = theta0;
1790 do { latest_mouse(but, m);
1791 (fp->c.thick)++; /* line() DISAGREES WITH ITSELF */
1792 draw_fpoly(fp, &tr, mv_bkgd);
1794 th = dir_angle(&m->xy, &tr);
1795 rotate_fp(fp, cur_sel.p, th-theta);
1797 draw_fpoly(fp, &tr, fp->c.clr);
1798 } while (m->buttons & bbit);
1802 return theta - theta0;
1807 /********************************* Edit menu *********************************/
1809 typedef enum e_index {
1810 Erecolor, Ethick, Edelete, Eundo, Erotate, Eoptions,
1814 char* e_items[Eoptions+1];
1816 Menu e_menu = {e_items, 0, 0};
1819 typedef struct e_action {
1820 e_index typ; /* What type of action */
1821 fpolygon* fp; /* fpolygon the action applies to */
1822 Image* clr; /* color to use if typ==Erecolor */
1823 double amt; /* rotation angle or line thickness */
1824 fpoint pt; /* movement vector or rotation center */
1825 struct e_action* link; /* next in a stack */
1828 e_action* unact = 0; /* heads a linked list of actions */
1829 e_action* do_undo(e_action*); /* pop off an e_action and (un)do it */
1830 e_action* save_act(e_action*,e_index); /* append new e_action for status quo */
1833 void save_mv(fpoint movement)
1835 unact = save_act(unact, Emove);
1836 unact->pt = movement;
1840 void init_e_menu(void)
1842 char* u = "can't undo";
1843 e_items[Erecolor] = "recolor";
1844 e_items[Edelete] = "delete";
1845 e_items[Erotate] = "rotate";
1846 e_items[Eoptions-cantmv] = 0;
1847 e_items[Ethick] = (cur_sel.fp->c.thick >0) ? "thin" : "thick";
1849 switch (unact->typ) {
1850 case Erecolor: u="uncolor"; break;
1851 case Ethick: u=(unact->fp->c.thick==0) ? "unthin" : "unthicken";
1853 case Edelete: u="undelete"; break;
1854 case Emove: u="unmove"; break;
1855 case Erotate: u="unrotate"; break;
1861 void do_emenu(int but, Mouse* m)
1867 h = emenuhit(but, m, &e_menu);
1869 case Ethick: unact = save_act(unact, h);
1870 cur_sel.fp->c.thick ^= 1;
1873 case Edelete: unact = save_act(unact, h);
1874 fp_remove(&univ, cur_sel.fp);
1878 case Erecolor: unact = save_act(unact, h);
1879 do_recolor(but, m, 0);
1881 case Erotate: unact = save_act(unact, h);
1882 prepare_mv(cur_sel.fp);
1883 if (get_1click(but, m, 0)) {
1884 unact->pt = cur_sel.p;
1885 unact->amt = do_rotate(but, m);
1888 case Eundo: unact = do_undo(unact);
1895 /******************************* Undoing edits *******************************/
1897 e_action* save_act(e_action* a0, e_index typ)
1898 { /* append new e_action for status quo */
1899 e_action* a = malloc(sizeof(e_action));
1901 a->pt.x = a->pt.y = 0.0;
1902 a->amt = cur_sel.fp->c.thick;
1903 a->clr = cur_sel.fp->c.clr;
1910 /* This would be trivial except it's nice to preserve the selection in order to make
1911 it easy to undo a series of moves. (There's no do_unrotate() because it's harder
1912 and less important to preserve the selection in that case.)
1914 void do_unmove(e_action* a)
1916 double tsav = cur_sel.t;
1918 move_fp(a->fp, -a->pt.x, -a->pt.y);
1919 if (a->fp == cur_sel.fp) {
1920 cur_sel.p.x -= a->pt.x;
1921 cur_sel.p.y -= a->pt.y;
1928 e_action* do_undo(e_action* a0) /* pop off an e_action and (un)do it */
1934 case Ethick: a->fp->c.thick = a->amt;
1937 case Erecolor: a->fp->c.clr = a->clr;
1941 a->fp->link = univ.p;
1943 grow_bb(&univ.bb, &a->fp->bb);
1952 rotate_fp(a->fp, a->pt, -a->amt);
1963 /********************************* Main menu *********************************/
1965 enum m_index { Mzoom_in, Mzoom_out, Munzoom, Mslant, Munslant,
1966 Msquare_up, Mrecenter, Mrecolor, Mrestack, Mread,
1968 char* m_items[] = {"zoom in", "zoom out", "unzoom", "slant", "unslant",
1969 "square up", "recenter", "recolor", "restack", "read",
1970 "write", "exit", 0};
1972 Menu m_menu = {m_items, 0, 0};
1975 void do_mmenu(int but, Mouse* m)
1977 int e, h = emenuhit(but, m, &m_menu);
1980 disp_zoomin(egetrect(but,m));
1984 disp_zoomout(egetrect(but,m));
1996 if (get_1click(but, m, &bullseye)) {
1997 recenter_disp(m->xy);
1999 lift_button(but, m, Never);
2003 if (cur_sel.t>=0 && prev_sel.t>=0) {
2004 slant_disp(prev_sel.p, cur_sel.p);
2009 univ.slant_ht = univ.disp.max.y - univ.disp.min.y;
2013 do_recolor(but, m, 1);
2020 e = doinput(prompt_text("File:"));
2024 show_mytext(" - can't read");
2027 snprintf(ebuf, 80, " - error line %d", e);
2032 if (!dooutput(prompt_text("File:")))
2033 show_mytext(" - can't write");
2042 /****************************** Handling events ******************************/
2051 etype = eread(Emouse|Ekeyboard, &ev);
2052 if(etype & Emouse) {
2053 if (ev.mouse.buttons & But1) {
2054 do_select(ev.mouse.xy);
2056 mobile = !cantmv && cur_sel.t>=0;
2058 mvtime = ev.mouse.msec + Mv_delay;
2059 prepare_mv(cur_sel.fp);
2061 if (!lift_button(1, &ev.mouse, mvtime) && mobile)
2062 save_mv(do_move(1, &ev.mouse));
2063 } else if (ev.mouse.buttons & But2)
2064 do_emenu(2, &ev.mouse);
2065 else if (ev.mouse.buttons & But3)
2066 do_mmenu(3, &ev.mouse);
2067 } else if (etype & Ekeyboard) {
2068 if (ev.kbdc=='\n' && cur_sel.t>=0 && logfil!=0) {
2069 fprintf(logfil,"%s\n", cur_sel.fp->nam);
2077 /******************************** Main program ********************************/
2084 fprintf(stderr,"Usage %s [options] [infile]\n", argv0);
2086 "option ::= -l logfile | -m | -p\n"
2088 "Read a polygonal line graph in an ASCII format (one x y pair per line, delimited\n"
2089 "by spaces with a label after each polyline), and view it interactively. Use\n"
2090 "standard input if no infile is specified.\n"
2091 "Option -l specifies a file in which to log the coordinates of each point selected.\n"
2092 "(Clicking a point with button one selects it and displays its coordinates and\n"
2093 "the label of its polylone.) Option -m allows polylines to be moved and rotated.\n"
2094 "The -p option plots only the vertices of the polygons.\n"
2095 "The polyline labels can use the following color names:"
2097 for (i=0; clrtab[i].c!=DNofill; i++)
2098 fprintf(stderr,"%s%8s", (i%8==0 ? "\n" : " "), clrtab[i].nam);
2099 fputc('\n', stderr);
2103 void main(int argc, char *argv[])
2113 logfil = fopen(ARGF(),"w");
2122 if(initdraw(0, 0, "gview") < 0)
2124 einit(Emouse|Ekeyboard);
2127 e = doinput(*argv ? *argv : "-");
2129 rerrstr(err, sizeof err);
2130 fprintf(stderr, "%s: cannot read %s: %s\n",
2132 exits("no valid input file");
2134 fprintf(stderr, "%s: %s:%d: bad data syntax\n",
2135 argv0, (*argv ? *argv : "-"), e);
2136 exits("bad syntax in input");
2138 } while (*argv && *++argv);
2141 set_default_clrs(&univ, 0);
2142 adjust_border(display->defaultfont);
2143 cur_sel.t = prev_sel.t = -1;