123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016 |
- #include <u.h>
- #include <libc.h>
- #include <ctype.h>
- #include <draw.h>
- #include <event.h>
- #include <cursor.h>
- #include <stdio.h>
- #define Never 0xffffffff /* Maximum ulong */
- #define LOG2 0.301029995664
- #define Button_bit(b) (1 << ((b)-1))
- enum {
- But1 = Button_bit(1),/* mouse buttons for events */
- But2 = Button_bit(2),
- But3 = Button_bit(3),
- };
- int cantmv = 1; /* disallow rotate and move? 0..1 */
- int plotdots; /* plot dots instead of lines */
- int top_border, bot_border, lft_border, rt_border;
- int lft_border0; /* lft_border for y-axis labels >0 */
- int top_left, top_right; /* edges of top line free space */
- int Mv_delay = 400; /* msec for button click vs. button hold down */
- int Dotrad = 2; /* dot radius in pixels */
- int framewd=1; /* line thickness for frame (pixels) */
- int framesep=1; /* distance between frame and surrounding text */
- int outersep=1; /* distance: surrounding text to screen edge */
- Point sdigit; /* size of a digit in the font */
- Point smaxch; /* assume any character in font fits in this */
- double underscan = .05; /* fraction of frame initially unused per side */
- double fuzz = 6; /* selection tolerance in pixels */
- int tick_len = 15; /* length of axis label tick mark in pixels */
- FILE* logfil = 0; /* dump selected points here if nonzero */
- #define labdigs 3 /* allow this many sig digits in axis labels */
- #define digs10pow 1000 /* pow(10,labdigs) */
- #define axis_color clr_im(DLtblue)
- /********************************* Utilities *********************************/
- /* Prepend string s to null-terminated string in n-byte buffer buf[], truncating if
- necessary and using a space to separate s from the rest of buf[].
- */
- char* str_insert(char* buf, char* s, int n)
- {
- int blen, slen = strlen(s) + 1;
- if (slen >= n)
- {strncpy(buf,s,n); buf[n-1]='\0'; return buf;}
- blen = strlen(buf);
- if (blen >= n-slen)
- buf[blen=n-slen-1] = '\0';
- memmove(buf+slen, buf, slen+blen+1);
- memcpy(buf, s, slen-1);
- buf[slen-1] = ' ';
- return buf;
- }
- /* Alter string smain (without lengthening it) so as to remove the first occurrence of
- ssub, assuming ssub is ASCII. Return nonzero (true) if string smain had to be changed.
- In spite of the ASCII-centric appearance, I think this can handle UTF in smain.
- */
- int remove_substr(char* smain, char* ssub)
- {
- char *ss, *s = strstr(smain, ssub);
- int n = strlen(ssub);
- if (s==0)
- return 0;
- if (islower(s[n]))
- s[0] ^= 32; /* probably tolower(s[0]) or toupper(s[0]) */
- else {
- for (ss=s+n; *ss!=0; s++, ss++)
- *s = *ss;
- *s = '\0';
- }
- return 1;
- }
- void adjust_border(Font* f)
- {
- int sep = framesep + outersep;
- sdigit = stringsize(f, "8");
- smaxch = stringsize(f, "MMMg");
- smaxch.x = (smaxch.x + 3)/4;
- lft_border0 = (1+labdigs)*sdigit.x + framewd + sep;
- rt_border = (lft_border0 - sep)/2 + outersep;
- bot_border = sdigit.y + framewd + sep;
- top_border = smaxch.y + framewd + sep;
- lft_border = lft_border0; /* this gets reset later */
- }
- int is_off_screen(Point p)
- {
- const Rectangle* r = &(screen->r);
- return p.x-r->min.x<lft_border || r->max.x-p.x<rt_border
- || p.y-r->min.y<=top_border || r->max.y-p.y<=bot_border;
- }
- Cursor bullseye =
- {
- {-7, -7},
- {
- 0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
- 0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
- 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
- 0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8,
- },
- {
- 0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
- 0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
- 0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
- 0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00,
- }
- };
- int get_1click(int but, Mouse* m, Cursor* curs)
- {
- if (curs)
- esetcursor(curs);
- while (m->buttons==0)
- *m = emouse();
- if (curs)
- esetcursor(0);
- return (m->buttons==Button_bit(but));
- }
- /* Wait until but goes up or until a mouse event's msec passes tlimit.
- Return a boolean result that tells whether the button went up.
- */
- int lift_button(int but, Mouse* m, int tlimit)
- {
- do { *m = emouse();
- if (m->msec >= tlimit)
- return 0;
- } while (m->buttons & Button_bit(but));
- return 1;
- }
- /* Set *m to the last pending mouse event, or the first one where but is up.
- If no mouse events are pending, wait for the next one.
- */
- void latest_mouse(int but, Mouse* m)
- {
- int bbit = Button_bit(but);
- do { *m = emouse();
- } while ((m->buttons & bbit) && ecanmouse());
- }
- /*********************************** Colors ***********************************/
- enum { DOrange=0xffaa00FF, Dgray=0xbbbbbbFF, DDkgreen=0x009900FF,
- DDkred=0xcc0000FF, DViolet=0x990099FF, DDkyellow=0xaaaa00FF,
- DLtblue=0xaaaaffFF, DPink=0xffaaaaFF,
- /* ndraw.h sets DBlack, DBlue, DRed, DYellow, DGreen,
- DCyan, DMagenta, DWhite */
- };
- typedef struct color_ref {
- ulong c; /* RGBA pixel color */
- char* nam; /* ASCII name (matched to input, used in output)*/
- Image* im; /* replicated solid-color image */
- } color_ref;
- color_ref clrtab[] = {
- DRed, "Red", 0,
- DPink, "Pink", 0,
- DDkred, "Dkred", 0,
- DOrange, "Orange", 0,
- DYellow, "Yellow", 0,
- DDkyellow, "Dkyellow", 0,
- DGreen, "Green", 0,
- DDkgreen, "Dkgreen", 0,
- DCyan, "Cyan", 0,
- DBlue, "Blue", 0,
- DLtblue, "Ltblue", 0,
- DMagenta, "Magenta", 0,
- DViolet, "Violet", 0,
- Dgray, "Gray", 0,
- DBlack, "Black", 0,
- DWhite, "White", 0,
- DNofill, 0, 0 /* DNofill means "end of data" */
- };
- void init_clrtab(void)
- {
- int i;
- Rectangle r = Rect(0,0,1,1);
- for (i=0; clrtab[i].c!=DNofill; i++)
- clrtab[i].im = allocimage(display, r, CMAP8, 1, clrtab[i].c);
- /* should check for 0 result? */
- }
- int clrim_id(Image* clr)
- {
- int i;
- for (i=0; clrtab[i].im!=clr; i++)
- if (clrtab[i].c==DNofill)
- exits("bad image color");
- return i;
- }
- int clr_id(int clr)
- {
- int i;
- for (i=0; clrtab[i].c!=clr; i++)
- if (clrtab[i].c==DNofill)
- exits("bad color");
- return i;
- }
- #define clr_im(clr) clrtab[clr_id(clr)].im
- /* This decides what color to use for a polyline based on the label it has in the
- input file. Whichever color name comes first is the winner, otherwise return black.
- */
- Image* nam2clr(const char* nam, int *idxdest)
- {
- char *c, *cbest=nam;
- int i, ibest=-1;
- if (*nam!=0)
- for (i=0; clrtab[i].nam!=0; i++) {
- c = strstr(nam,clrtab[i].nam);
- if (c!=0 && (ibest<0 || c<cbest))
- {ibest=i; cbest=c;}
- }
- if (idxdest!=0)
- *idxdest = (ibest<0) ? clr_id(DBlack) : ibest;
- return (ibest<0) ? clr_im(DBlack) : clrtab[ibest].im;
- }
- /* A polyline is initial drawn in thick mode iff its label in the file contains "Thick" */
- int nam2thick(const char* nam)
- {
- return strstr(nam,"Thick")==0 ? 0 : 1;
- }
- /* Alter string nam so that nam2thick() and nam2clr() agree with th and clr, using
- buf[] (a buffer of length bufn) to store the result if it differs from nam.
- We go to great pains to perform this alteration in a manner that will seem natural
- to the user, i.e., we try removing a suitably isolated color name before inserting
- a new one.
- */
- char* nam_with_thclr(char* nam, int th, Image* clr, char* buf, int bufn)
- {
- int clr0i, th0=nam2thick(nam);
- Image* clr0 = nam2clr(nam, &clr0i);
- char *clr0s;
- if (th0==th && clr0==clr)
- return nam;
- clr0s = clrtab[clr0i].nam;
- if (strlen(nam)<bufn) strcpy(buf,nam);
- else {strncpy(buf,nam,bufn); buf[bufn-1]='\0';}
- if (clr0 != clr)
- remove_substr(buf, clr0s);
- if (th0 > th)
- while (remove_substr(buf, "Thick"))
- /* do nothing */;
- if (nam2clr(buf,0) != clr)
- str_insert(buf, clrtab[clrim_id(clr)].nam, bufn);
- if (th0 < th)
- str_insert(buf, "Thick", bufn);
- return buf;
- }
- /****************************** Data structures ******************************/
- Image* mv_bkgd; /* Background image (usually 0) */
- typedef struct fpoint {
- double x, y;
- } fpoint;
- typedef struct frectangle {
- fpoint min, max;
- } frectangle;
- frectangle empty_frect = {1e30, 1e30, -1e30, -1e30};
- /* When *r2 is transformed by y=y-x*slant, might it intersect *r1 ?
- */
- int fintersects(const frectangle* r1, const frectangle* r2, double slant)
- {
- double x2min=r2->min.x, x2max=r2->max.x;
- if (r1->max.x <= x2min || x2max <= r1->min.x)
- return 0;
- if (slant >=0)
- {x2min*=slant; x2max*=slant;}
- else {double t=x2min*slant; x2min=x2max*slant; x2max=t;}
- return r1->max.y > r2->min.y-x2max && r2->max.y-x2min > r1->min.y;
- }
- int fcontains(const frectangle* r, fpoint p)
- {
- return r->min.x <=p.x && p.x<= r->max.x && r->min.y <=p.y && p.y<= r->max.y;
- }
- void grow_bb(frectangle* dest, const frectangle* r)
- {
- if (r->min.x < dest->min.x) dest->min.x=r->min.x;
- if (r->min.y < dest->min.y) dest->min.y=r->min.y;
- if (r->max.x > dest->max.x) dest->max.x=r->max.x;
- if (r->max.y > dest->max.y) dest->max.y=r->max.y;
- }
- void slant_frect(frectangle *r, double sl)
- {
- r->min.y += sl*r->min.x;
- r->max.y += sl*r->max.x;
- }
- fpoint fcenter(const frectangle* r)
- {
- fpoint c;
- c.x = .5*(r->max.x + r->min.x);
- c.y = .5*(r->max.y + r->min.y);
- return c;
- }
- typedef struct fpolygon {
- fpoint* p; /* a malloc'ed array */
- int n; /* p[] has n elements: p[0..n] */
- frectangle bb; /* bounding box */
- char* nam; /* name of this polygon (malloc'ed) */
- int thick; /* use 1+2*thick pixel wide lines */
- Image* clr; /* Color to use when drawing this */
- struct fpolygon* link;
- } fpolygon;
- typedef struct fpolygons {
- fpolygon* p; /* the head of a linked list */
- frectangle bb; /* overall bounding box */
- frectangle disp; /* part being mapped onto screen->r */
- double slant_ht; /* controls how disp is slanted */
- } fpolygons;
- fpolygons univ = { /* everything there is to display */
- 0,
- 1e30, 1e30, -1e30, -1e30,
- 0, 0, 0, 0,
- 2*1e30
- };
- void set_default_clrs(fpolygons* fps, fpolygon* fpstop)
- {
- fpolygon* fp;
- for (fp=fps->p; fp!=0 && fp!=fpstop; fp=fp->link) {
- fp->clr = nam2clr(fp->nam,0);
- fp->thick = nam2thick(fp->nam);
- }
- }
- void fps_invert(fpolygons* fps)
- {
- fpolygon *p, *r=0;
- for (p=fps->p; p!=0;) {
- fpolygon* q = p;
- p = p->link;
- q->link = r;
- r = q;
- }
- fps->p = r;
- }
- void fp_remove(fpolygons* fps, fpolygon* fp)
- {
- fpolygon *q, **p = &fps->p;
- while (*p!=fp)
- if (*p==0)
- return;
- else p = &(*p)->link;
- *p = fp->link;
- fps->bb = empty_frect;
- for (q=fps->p; q!=0; q=q->link)
- grow_bb(&fps->bb, &q->bb);
- }
- /* The transform maps abstract fpoint coordinates (the ones used in the input)
- to the current screen coordinates. The do_untransform() macros reverses this.
- If univ.slant_ht is not the height of univ.disp, the actual region in the
- abstract coordinates is a parallelogram inscribed in univ.disp with two
- vertical edges and two slanted slanted edges: slant_ht>0 means that the
- vertical edges have height slant_ht and the parallelogram touches the lower
- left and upper right corners of univ.disp; slant_ht<0 refers to a parallelogram
- of height -slant_ht that touches the other two corners of univ.disp.
- NOTE: the ytransform macro assumes that tr->sl times the x coordinate has
- already been subtracted from yy.
- */
- typedef struct transform {
- double sl;
- fpoint o, sc; /* (x,y):->(o.x+sc.x*x, o.y+sc.y*y+sl*x) */
- } transform;
- #define do_transform(d,tr,s) ((d)->x = (tr)->o.x + (tr)->sc.x*(s)->x, \
- (d)->y = (tr)->o.y + (tr)->sc.y*(s)->y \
- + (tr)->sl*(s)->x)
- #define do_untransform(d,tr,s) ((d)->x = (.5+(s)->x-(tr)->o.x)/(tr)->sc.x, \
- (d)->y = (.5+(s)->y-(tr)->sl*(d)->x-(tr)->o.y) \
- /(tr)->sc.y)
- #define xtransform(tr,xx) ((tr)->o.x + (tr)->sc.x*(xx))
- #define ytransform(tr,yy) ((tr)->o.y + (tr)->sc.y*(yy))
- #define dxuntransform(tr,xx) ((xx)/(tr)->sc.x)
- #define dyuntransform(tr,yy) ((yy)/(tr)->sc.y)
- transform cur_trans(void)
- {
- transform t;
- Rectangle d = screen->r;
- const frectangle* s = &univ.disp;
- double sh = univ.slant_ht;
- d.min.x += lft_border;
- d.min.y += top_border;
- d.max.x -= rt_border;
- d.max.y -= bot_border;
- t.sc.x = (d.max.x - d.min.x)/(s->max.x - s->min.x);
- t.sc.y = -(d.max.y - d.min.y)/fabs(sh);
- if (sh > 0) {
- t.sl = -t.sc.y*(s->max.y-s->min.y-sh)/(s->max.x - s->min.x);
- t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->max.x;
- } else {
- t.sl = t.sc.y*(s->max.y-s->min.y+sh)/(s->max.x - s->min.x);
- t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->min.x;
- }
- t.o.x = d.min.x - t.sc.x*s->min.x;
- return t;
- }
- double u_slant_amt(fpolygons *u)
- {
- double sh=u->slant_ht, dy=u->disp.max.y - u->disp.min.y;
- double dx = u->disp.max.x - u->disp.min.x;
- return (sh>0) ? (dy-sh)/dx : -(dy+sh)/dx;
- }
- /* Set *y0 and *y1 to the lower and upper bounds of the set of y-sl*x values that
- *u says to display, where sl is the amount of slant.
- */
- double set_unslanted_y(fpolygons *u, double *y0, double *y1)
- {
- double yy1, sl=u_slant_amt(u);
- if (u->slant_ht > 0) {
- *y0 = u->disp.min.y - sl*u->disp.min.x;
- yy1 = *y0 + u->slant_ht;
- } else {
- yy1 = u->disp.max.y - sl*u->disp.min.x;
- *y0 = yy1 + u->slant_ht;
- }
- if (y1 != 0)
- *y1 = yy1;
- return sl;
- }
- /*************************** The region to display ****************************/
- void nontrivial_interval(double *lo, double *hi)
- {
- if (*lo >= *hi) {
- double mid = .5*(*lo + *hi);
- double tweak = 1e-6 + 1e-6*fabs(mid);
- *lo = mid - tweak;
- *hi = mid + tweak;
- }
- }
- void init_disp(void)
- {
- double dw = (univ.bb.max.x - univ.bb.min.x)*underscan;
- double dh = (univ.bb.max.y - univ.bb.min.y)*underscan;
- univ.disp.min.x = univ.bb.min.x - dw;
- univ.disp.min.y = univ.bb.min.y - dh;
- univ.disp.max.x = univ.bb.max.x + dw;
- univ.disp.max.y = univ.bb.max.y + dh;
- nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x);
- nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y);
- univ.slant_ht = univ.disp.max.y - univ.disp.min.y; /* means no slant */
- }
- void recenter_disp(Point c)
- {
- transform tr = cur_trans();
- fpoint cc, off;
- do_untransform(&cc, &tr, &c);
- off.x = cc.x - .5*(univ.disp.min.x + univ.disp.max.x);
- off.y = cc.y - .5*(univ.disp.min.y + univ.disp.max.y);
- univ.disp.min.x += off.x;
- univ.disp.min.y += off.y;
- univ.disp.max.x += off.x;
- univ.disp.max.y += off.y;
- }
- /* Find the upper-left and lower-right corners of the bounding box of the
- parallelogram formed by untransforming the rectangle rminx, rminy, ... (given
- in screen coordinates), and return the height of the parallelogram (negated
- if it slopes downward).
- */
- double untransform_corners(double rminx, double rminy, double rmaxx, double rmaxy,
- fpoint *ul, fpoint *lr)
- {
- fpoint r_ur, r_ul, r_ll, r_lr; /* corners of the given recangle */
- fpoint ur, ll; /* untransformed versions of r_ur, r_ll */
- transform tr = cur_trans();
- double ht;
- r_ur.x=rmaxx; r_ur.y=rminy;
- r_ul.x=rminx; r_ul.y=rminy;
- r_ll.x=rminx; r_ll.y=rmaxy;
- r_lr.x=rmaxx; r_lr.y=rmaxy;
- do_untransform(ul, &tr, &r_ul);
- do_untransform(lr, &tr, &r_lr);
- do_untransform(&ur, &tr, &r_ur);
- do_untransform(&ll, &tr, &r_ll);
- ht = ur.y - lr->y;
- if (ll.x < ul->x)
- ul->x = ll.x;
- if (ur.y > ul->y)
- ul->y = ur.y;
- else ht = -ht;
- if (ur.x > lr->x)
- lr->x = ur.x;
- if (ll.y < lr->y)
- lr->y = ll.y;
- return ht;
- }
- void disp_dozoom(double rminx, double rminy, double rmaxx, double rmaxy)
- {
- fpoint ul, lr;
- double sh = untransform_corners(rminx, rminy, rmaxx, rmaxy, &ul, &lr);
- if (ul.x==lr.x || ul.y==lr.y)
- return;
- univ.slant_ht = sh;
- univ.disp.min.x = ul.x;
- univ.disp.max.y = ul.y;
- univ.disp.max.x = lr.x;
- univ.disp.min.y = lr.y;
- nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x);
- nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y);
- }
- void disp_zoomin(Rectangle r)
- {
- disp_dozoom(r.min.x, r.min.y, r.max.x, r.max.y);
- }
- void disp_zoomout(Rectangle r)
- {
- double qminx, qminy, qmaxx, qmaxy;
- double scx, scy;
- Rectangle s = screen->r;
- if (r.min.x==r.max.x || r.min.y==r.max.y)
- return;
- s.min.x += lft_border;
- s.min.y += top_border;
- s.max.x -= rt_border;
- s.max.y -= bot_border;
- scx = (s.max.x - s.min.x)/(r.max.x - r.min.x);
- scy = (s.max.y - s.min.y)/(r.max.y - r.min.y);
- qminx = s.min.x + scx*(s.min.x - r.min.x);
- qmaxx = s.max.x + scx*(s.max.x - r.max.x);
- qminy = s.min.y + scy*(s.min.y - r.min.y);
- qmaxy = s.max.y + scy*(s.max.y - r.max.y);
- disp_dozoom(qminx, qminy, qmaxx, qmaxy);
- }
- void expand2(double* a, double* b, double f)
- {
- double mid = .5*(*a + *b);
- *a = mid + f*(*a - mid);
- *b = mid + f*(*b - mid);
- }
- void disp_squareup(void)
- {
- double dx = univ.disp.max.x - univ.disp.min.x;
- double dy = univ.disp.max.y - univ.disp.min.y;
- dx /= screen->r.max.x - lft_border - screen->r.min.x - rt_border;
- dy /= screen->r.max.y - bot_border - screen->r.min.y - top_border;
- if (dx > dy)
- expand2(&univ.disp.min.y, &univ.disp.max.y, dx/dy);
- else expand2(&univ.disp.min.x, &univ.disp.max.x, dy/dx);
- univ.slant_ht = univ.disp.max.y - univ.disp.min.y;
- }
- /* Slant so that p and q appear at the same height on the screen and the
- screen contains the smallest possible superset of what its previous contents.
- */
- void slant_disp(fpoint p, fpoint q)
- {
- double yll, ylr, yul, yur; /* corner y coords of displayed parallelogram */
- double sh, dy;
- if (p.x == q.x)
- return;
- sh = univ.slant_ht;
- if (sh > 0) {
- yll=yul=univ.disp.min.y; yul+=sh;
- ylr=yur=univ.disp.max.y; ylr-=sh;
- } else {
- yll=yul=univ.disp.max.y; yll+=sh;
- ylr=yur=univ.disp.min.y; yur-=sh;
- }
- dy = (univ.disp.max.x-univ.disp.min.x)*(q.y - p.y)/(q.x - p.x);
- dy -= ylr - yll;
- if (dy > 0)
- {yll-=dy; yur+=dy;}
- else {yul-=dy; ylr+=dy;}
- if (ylr > yll) {
- univ.disp.min.y = yll;
- univ.disp.max.y = yur;
- univ.slant_ht = yur - ylr;
- } else {
- univ.disp.max.y = yul;
- univ.disp.min.y = ylr;
- univ.slant_ht = ylr - yur;
- }
- }
- /******************************** Ascii input ********************************/
- void set_fbb(fpolygon* fp)
- {
- fpoint lo=fp->p[0], hi=fp->p[0];
- const fpoint *q, *qtop;
- for (qtop=(q=fp->p)+fp->n; ++q<=qtop;) {
- if (q->x < lo.x) lo.x=q->x;
- if (q->y < lo.y) lo.y=q->y;
- if (q->x > hi.x) hi.x=q->x;
- if (q->y > hi.y) hi.y=q->y;
- }
- fp->bb.min = lo;
- fp->bb.max = hi;
- }
- char* mystrdup(char* s)
- {
- char *r, *t = strrchr(s,'"');
- if (t==0) {
- t = s + strlen(s);
- while (t>s && (t[-1]=='\n' || t[-1]=='\r'))
- t--;
- }
- r = malloc(1+(t-s));
- memcpy(r, s, t-s);
- r[t-s] = 0;
- return r;
- }
- int is_valid_label(char* lab)
- {
- char* t;
- if (lab[0]=='"')
- return (t=strrchr(lab,'"'))!=0 && t!=lab && strspn(t+1," \t\r\n")==strlen(t+1);
- return strcspn(lab," \t")==strlen(lab);
- }
- /* Read a polyline and update the number of lines read. A zero result indicates bad
- syntax if *lineno increases; otherwise it indicates end of file.
- */
- fpolygon* rd_fpoly(FILE* fin, int *lineno)
- {
- char buf[256], junk[2];
- fpoint q;
- fpolygon* fp;
- int allocn;
- if (!fgets(buf,256,fin))
- return 0;
- (*lineno)++;
- if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2)
- return 0;
- fp = malloc(sizeof(fpolygon));
- allocn = 16;
- fp->p = malloc(allocn*sizeof(fpoint));
- fp->p[0] = q;
- fp->n = 0;
- fp->nam = "";
- fp->thick = 0;
- fp->clr = clr_im(DBlack);
- while (fgets(buf,256,fin)) {
- (*lineno)++;
- if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2) {
- if (!is_valid_label(buf))
- {free(fp->p); free(fp); return 0;}
- fp->nam = (buf[0]=='"') ? buf+1 : buf;
- break;
- }
- if (++(fp->n) == allocn)
- fp->p = realloc(fp->p, (allocn<<=1)*sizeof(fpoint));
- fp->p[fp->n] = q;
- }
- fp->nam = mystrdup(fp->nam);
- set_fbb(fp);
- fp->link = 0;
- return fp;
- }
- /* Read input into *fps and return 0 or a line number where there's a syntax error */
- int rd_fpolys(FILE* fin, fpolygons* fps)
- {
- fpolygon *fp, *fp0=fps->p;
- int lineno=0, ok_upto=0;
- while ((fp=rd_fpoly(fin,&lineno)) != 0) {
- ok_upto = lineno;
- fp->link = fps->p;
- fps->p = fp;
- grow_bb(&fps->bb, &fp->bb);
- }
- set_default_clrs(fps, fp0);
- return (ok_upto==lineno) ? 0 : lineno;
- }
- /* Read input from file fnam and return an error line no., -1 for "can't open"
- or 0 for success.
- */
- int doinput(char* fnam)
- {
- FILE* fin = strcmp(fnam,"-")==0 ? stdin : fopen(fnam, "r");
- int errline_or0;
- if (fin==0)
- return -1;
- errline_or0 = rd_fpolys(fin, &univ);
- fclose(fin);
- return errline_or0;
- }
- /******************************** Ascii output ********************************/
- fpolygon* fp_reverse(fpolygon* fp)
- {
- fpolygon* r = 0;
- while (fp!=0) {
- fpolygon* q = fp->link;
- fp->link = r;
- r = fp;
- fp = q;
- }
- return r;
- }
- void wr_fpoly(FILE* fout, const fpolygon* fp)
- {
- char buf[256];
- int i;
- for (i=0; i<=fp->n; i++)
- fprintf(fout,"%.12g\t%.12g\n", fp->p[i].x, fp->p[i].y);
- fprintf(fout,"\"%s\"\n", nam_with_thclr(fp->nam, fp->thick, fp->clr, buf, 256));
- }
- void wr_fpolys(FILE* fout, fpolygons* fps)
- {
- fpolygon* fp;
- fps->p = fp_reverse(fps->p);
- for (fp=fps->p; fp!=0; fp=fp->link)
- wr_fpoly(fout, fp);
- fps->p = fp_reverse(fps->p);
- }
- int dooutput(char* fnam)
- {
- FILE* fout = fopen(fnam, "w");
- if (fout==0)
- return 0;
- wr_fpolys(fout, &univ);
- fclose(fout);
- return 1;
- }
- /************************ Clipping to screen rectangle ************************/
- /* Find the t values, 0<=t<=1 for which x0+t*(x1-x0) is between xlo and xhi,
- or return 0 to indicate no such t values exist. If returning 1, set *t0 and
- *t1 to delimit the t interval.
- */
- int do_xory(double x0, double x1, double xlo, double xhi, double* t0, double* t1)
- {
- *t1 = 1.0;
- if (x0<xlo) {
- if (x1<xlo) return 0;
- *t0 = (xlo-x0)/(x1-x0);
- if (x1>xhi) *t1 = (xhi-x0)/(x1-x0);
- } else if (x0>xhi) {
- if (x1>xhi) return 0;
- *t0 = (xhi-x0)/(x1-x0);
- if (x1<xlo) *t1 = (xlo-x0)/(x1-x0);
- } else {
- *t0 = 0.0;
- if (x1>xhi) *t1 = (xhi-x0)/(x1-x0);
- else if (x1<xlo) *t1 = (xlo-x0)/(x1-x0);
- else *t1 = 1.0;
- }
- return 1;
- }
- /* After mapping y to y-slope*x, what initial fraction of the *p to *q edge is
- outside of *r? Note that the edge could start outside *r, pass through *r,
- and wind up outside again.
- */
- double frac_outside(const fpoint* p, const fpoint* q, const frectangle* r,
- double slope)
- {
- double t0, t1, tt0, tt1;
- double px=p->x, qx=q->x;
- if (!do_xory(px, qx, r->min.x, r->max.x, &t0, &t1))
- return 1;
- if (!do_xory(p->y-slope*px, q->y-slope*qx, r->min.y, r->max.y, &tt0, &tt1))
- return 1;
- if (tt0 > t0)
- t0 = tt0;
- if (t1<=t0 || tt1<=t0)
- return 1;
- return t0;
- }
- /* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find
- the maximum tt such that F(0..tt) is all inside of r, assuming p0 is inside.
- Coordinates are transformed by y=y-x*slope before testing against r.
- */
- double in_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope)
- {
- const fpoint* p = p0;
- double px, py;
- do if (++p > pn)
- return pn - p0;
- while (r.min.x<=(px=p->x) && px<=r.max.x
- && r.min.y<=(py=p->y-slope*px) && py<=r.max.y);
- return (p - p0) - frac_outside(p, p-1, &r, slope);
- }
- /* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find
- the maximum tt such that F(0..tt) is all outside of *r. Coordinates are
- transformed by y=y-x*slope before testing against r.
- */
- double out_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope)
- {
- const fpoint* p = p0;
- double fr;
- do { if (p->x < r.min.x)
- do if (++p>pn) return pn-p0;
- while (p->x <= r.min.x);
- else if (p->x > r.max.x)
- do if (++p>pn) return pn-p0;
- while (p->x >= r.max.x);
- else if (p->y-slope*p->x < r.min.y)
- do if (++p>pn) return pn-p0;
- while (p->y-slope*p->x <= r.min.y);
- else if (p->y-slope*p->x > r.max.y)
- do if (++p>pn) return pn-p0;
- while (p->y-slope*p->x >= r.max.y);
- else return p - p0;
- } while ((fr=frac_outside(p-1,p,&r,slope)) == 1);
- return (p - p0) + fr-1;
- }
- /*********************** Drawing frame and axis labels ***********************/
- #define Nthous 7
- #define Len_thous 30 /* bound on strlen(thous_nam[i]) */
- char* thous_nam[Nthous] = {
- "one", "thousand", "million", "billion",
- "trillion", "quadrillion", "quintillion",
- };
- typedef struct lab_interval {
- double sep; /* separation between tick marks */
- double unit; /* power of 1000 divisor */
- int logunit; /* log base 1000 of of this divisor */
- double off; /* offset to subtract before dividing */
- } lab_interval;
- char* abbrev_num(double x, const lab_interval* iv)
- {
- static char buf[16];
- double dx = x - iv->off;
- dx = iv->sep * floor(dx/iv->sep + .5);
- sprintf(buf,"%g", dx/iv->unit);
- return buf;
- }
- double lead_digits(double n, double r) /* n truncated to power of 10 above r */
- {
- double rr = pow(10, ceil(log10(r)));
- double nn = (n<rr) ? 0.0 : rr*floor(n/rr);
- if (n+r-nn >= digs10pow) {
- rr /= 10;
- nn = (n<rr) ? 0.0 : rr*floor(n/rr);
- }
- return nn;
- }
- lab_interval next_larger(double s0, double xlo, double xhi)
- {
- double nlo, nhi;
- lab_interval r;
- r.logunit = (int) floor(log10(s0) + LOG2);
- r.unit = pow(10, r.logunit);
- nlo = xlo/r.unit;
- nhi = xhi/r.unit;
- if (nhi >= digs10pow)
- r.off = r.unit*lead_digits(nlo, nhi-nlo);
- else if (nlo <= -digs10pow)
- r.off = -r.unit*lead_digits(-nhi, nhi-nlo);
- else r.off = 0;
- r.sep = (s0<=r.unit) ? r.unit : (s0<2*r.unit ? 2*r.unit : 5*r.unit);
- switch (r.logunit%3) {
- case 1: r.unit*=.1; r.logunit--;
- break;
- case -1: case 2:
- r.unit*=10; r.logunit++;
- break;
- case -2: r.unit*=100; r.logunit+=2;
- }
- r.logunit /= 3;
- return r;
- }
- double min_hsep(const transform* tr)
- {
- double s = (2+labdigs)*sdigit.x;
- double ss = (univ.disp.min.x<0) ? s+sdigit.x : s;
- return dxuntransform(tr, ss);
- }
- lab_interval mark_x_axis(const transform* tr)
- {
- fpoint p = univ.disp.min;
- Point q, qtop, qbot, tmp;
- double x0=univ.disp.min.x, x1=univ.disp.max.x;
- double seps0, nseps, seps;
- lab_interval iv = next_larger(min_hsep(tr), x0, x1);
- set_unslanted_y(&univ, &p.y, 0);
- q.y = ytransform(tr, p.y) + .5;
- qtop.y = q.y - tick_len;
- qbot.y = q.y + framewd + framesep;
- seps0 = ceil(x0/iv.sep);
- for (seps=0, nseps=floor(x1/iv.sep)-seps0; seps<=nseps; seps+=1) {
- char* num = abbrev_num((p.x=iv.sep*(seps0+seps)), &iv);
- Font* f = display->defaultfont;
- q.x = qtop.x = qbot.x = xtransform(tr, p.x);
- line(screen, qtop, q, Enddisc, Enddisc, 0, axis_color, q);
- tmp = stringsize(f, num);
- qbot.x -= tmp.x/2;
- string(screen, qbot, display->black, qbot, f, num);
- }
- return iv;
- }
- lab_interval mark_y_axis(const transform* tr)
- {
- Font* f = display->defaultfont;
- fpoint p = univ.disp.min;
- Point q, qrt, qlft;
- double y0, y1, seps0, nseps, seps;
- lab_interval iv;
- set_unslanted_y(&univ, &y0, &y1);
- iv = next_larger(dyuntransform(tr,-f->height), y0, y1);
- q.x = xtransform(tr, p.x) - .5;
- qrt.x = q.x + tick_len;
- qlft.x = q.x - (framewd + framesep);
- seps0 = ceil(y0/iv.sep);
- for (seps=0, nseps=floor(y1/iv.sep)-seps0; seps<=nseps; seps+=1) {
- char* num = abbrev_num((p.y=iv.sep*(seps0+seps)), &iv);
- Point qq = stringsize(f, num);
- q.y = qrt.y = qlft.y = ytransform(tr, p.y);
- line(screen, qrt, q, Enddisc, Enddisc, 0, axis_color, q);
- qq.x = qlft.x - qq.x;
- qq.y = qlft.y - qq.y/2;
- string(screen, qq, display->black, qq, f, num);
- }
- return iv;
- }
- void lab_iv_info(const lab_interval *iv, double slant, char* buf, int *n)
- {
- if (iv->off > 0)
- (*n) += sprintf(buf+*n,"-%.12g",iv->off);
- else if (iv->off < 0)
- (*n) += sprintf(buf+*n,"+%.12g",-iv->off);
- if (slant>0)
- (*n) += sprintf(buf+*n,"-%.6gx", slant);
- else if (slant<0)
- (*n) += sprintf(buf+*n,"+%.6gx", -slant);
- if (abs(iv->logunit) >= Nthous)
- (*n) += sprintf(buf+*n," in 1e%d units", 3*iv->logunit);
- else if (iv->logunit > 0)
- (*n) += sprintf(buf+*n," in %ss", thous_nam[iv->logunit]);
- else if (iv->logunit < 0)
- (*n) += sprintf(buf+*n," in %sths", thous_nam[-iv->logunit]);
- }
- void draw_xy_ranges(const lab_interval *xiv, const lab_interval *yiv)
- {
- Point p;
- char buf[2*(19+Len_thous+8)+50];
- int bufn = 0;
- buf[bufn++] = 'x';
- lab_iv_info(xiv, 0, buf, &bufn);
- bufn += sprintf(buf+bufn, "; y");
- lab_iv_info(yiv, u_slant_amt(&univ), buf, &bufn);
- buf[bufn] = '\0';
- p = stringsize(display->defaultfont, buf);
- top_left = screen->r.min.x + lft_border;
- p.x = top_right = screen->r.max.x - rt_border - p.x;
- p.y = screen->r.min.y + outersep;
- string(screen, p, display->black, p, display->defaultfont, buf);
- }
- transform draw_frame(void)
- {
- lab_interval x_iv, y_iv;
- transform tr;
- Rectangle r = screen->r;
- lft_border = (univ.disp.min.y<0) ? lft_border0+sdigit.x : lft_border0;
- tr = cur_trans();
- r.min.x += lft_border;
- r.min.y += top_border;
- r.max.x -= rt_border;
- r.max.y -= bot_border;
- border(screen, r, -framewd, axis_color, r.min);
- x_iv = mark_x_axis(&tr);
- y_iv = mark_y_axis(&tr);
- draw_xy_ranges(&x_iv, &y_iv);
- return tr;
- }
- /*************************** Finding the selection ***************************/
- typedef struct pt_on_fpoly {
- fpoint p; /* the point */
- fpolygon* fp; /* the fpolygon it lies on */
- double t; /* how many knots from the beginning */
- } pt_on_fpoly;
- static double myx, myy;
- #define mydist(p,o,sl,xwt,ywt) (myx=(p).x-(o).x, myy=(p).y-sl*(p).x-(o).y, \
- xwt*myx*myx + ywt*myy*myy)
- /* At what fraction of the way from p0[0] to p0[1] is mydist(p,ctr,slant,xwt,ywt)
- minimized?
- */
- double closest_time(const fpoint* p0, const fpoint* ctr, double slant,
- double xwt, double ywt)
- {
- double p00y=p0[0].y-slant*p0[0].x, p01y=p0[1].y-slant*p0[1].x;
- double dx=p0[1].x-p0[0].x, dy=p01y-p00y;
- double x0=p0[0].x-ctr->x, y0=p00y-ctr->y;
- double bot = xwt*dx*dx + ywt*dy*dy;
- if (bot==0)
- return 0;
- return -(xwt*x0*dx + ywt*y0*dy)/bot;
- }
- /* Scan the polygonal path of length len knots starting at p0, and find the
- point that the transformation y=y-x*slant makes closest to the center of *r,
- where *r itself defines the distance metric. Knots get higher priority than
- points between knots. If psel->t is negative, always update *psel; otherwise
- update *psel only if the scan can improve it. Return a boolean that says
- whether *psel was updated.
- Note that *r is a very tiny rectangle (tiny when converted screen pixels)
- such that anything in *r is considered close enough to match the mouse click.
- The purpose of this routine is to be careful in case there is a lot of hidden
- detail in the tiny rectangle *r.
- */
- int improve_pt(fpoint* p0, double len, const frectangle* r, double slant,
- pt_on_fpoly* psel)
- {
- fpoint ctr = fcenter(r);
- double x_wt=2/(r->max.x-r->min.x), y_wt=2/(r->max.y-r->min.y);
- double xwt=x_wt*x_wt, ywt=y_wt*y_wt;
- double d, dbest = (psel->t <0) ? 1e30 : mydist(psel->p,ctr,slant,xwt,ywt);
- double tt, dbest0 = dbest;
- fpoint pp;
- int ilen = (int) len;
- if (len==0 || ilen>0) {
- int i;
- for (i=(len==0 ? 0 : 1); i<=ilen; i++) {
- d = mydist(p0[i], ctr, slant, xwt, ywt);
- if (d < dbest)
- {psel->p=p0[i]; psel->t=i; dbest=d;}
- }
- return (dbest < dbest0);
- }
- tt = closest_time(p0, &ctr, slant, xwt, ywt);
- if (tt > len)
- tt = len;
- pp.x = p0[0].x + tt*(p0[1].x - p0[0].x);
- pp.y = p0[0].y + tt*(p0[1].y - p0[0].y);
- if (mydist(pp, ctr, slant, xwt, ywt) < dbest) {
- psel->p = pp;
- psel->t = tt;
- return 1;
- }
- return 0;
- }
- /* Test *fp against *r after transforming by y=y-x*slope, and set *psel accordingly.
- */
- void select_in_fpoly(fpolygon* fp, const frectangle* r, double slant,
- pt_on_fpoly* psel)
- {
- fpoint *p0=fp->p, *pn=fp->p+fp->n;
- double l1, l2;
- if (p0==pn)
- {improve_pt(p0, 0, r, slant, psel); psel->fp=fp; return;}
- while ((l1=out_length(p0,pn,*r,slant)) < pn-p0) {
- fpoint p0sav;
- int i1 = (int) l1;
- p0+=i1; l1-=i1;
- p0sav = *p0;
- p0[0].x += l1*(p0[1].x - p0[0].x);
- p0[0].y += l1*(p0[1].y - p0[0].y);
- l2 = in_length(p0, pn, *r, slant);
- if (improve_pt(p0, l2, r, slant, psel)) {
- if (l1==0 && psel->t!=((int) psel->t)) {
- psel->t = 0;
- psel->p = *p0;
- } else if (psel->t < 1)
- psel->t += l1*(1 - psel->t);
- psel->t += p0 - fp->p;
- psel->fp = fp;
- }
- *p0 = p0sav;
- p0 += (l2>0) ? ((int) ceil(l2)) : 1;
- }
- }
- /* Test all the fpolygons against *r after transforming by y=y-x*slope, and return
- the resulting selection, if any.
- */
- pt_on_fpoly* select_in_univ(const frectangle* r, double slant)
- {
- static pt_on_fpoly answ;
- fpolygon* fp;
- answ.t = -1;
- for (fp=univ.p; fp!=0; fp=fp->link)
- if (fintersects(r, &fp->bb, slant))
- select_in_fpoly(fp, r, slant, &answ);
- if (answ.t < 0)
- return 0;
- return &answ;
- }
- /**************************** Using the selection ****************************/
- pt_on_fpoly cur_sel; /* current selection if cur_sel.t>=0 */
- pt_on_fpoly prev_sel; /* previous selection if prev_sel.t>=0 (for slant) */
- Image* sel_bkg = 0; /* what's behind the red dot */
- void clear_txt(void)
- {
- Rectangle r;
- r.min = screen->r.min;
- r.min.x += lft_border;
- r.min.y += outersep;
- r.max.x = top_left;
- r.max.y = r.min.y + smaxch.y;
- draw(screen, r, display->white, display->opaque, r.min);
- top_left = r.min.x;
- }
- Rectangle sel_dot_box(const transform* tr)
- {
- Point ctr;
- Rectangle r;
- if (tr==0)
- ctr.x = ctr.y = Dotrad;
- else do_transform(&ctr, tr, &cur_sel.p);
- r.min.x=ctr.x-Dotrad; r.max.x=ctr.x+Dotrad+1;
- r.min.y=ctr.y-Dotrad; r.max.y=ctr.y+Dotrad+1;
- return r;
- }
- void unselect(const transform* tr)
- {
- transform tra;
- if (sel_bkg==0)
- sel_bkg = allocimage(display, sel_dot_box(0), CMAP8, 0, DWhite);
- clear_txt();
- if (cur_sel.t < 0)
- return;
- prev_sel = cur_sel;
- if (tr==0)
- {tra=cur_trans(); tr=&tra;}
- draw(screen, sel_dot_box(tr), sel_bkg, display->opaque, ZP);
- cur_sel.t = -1;
- }
- /* Text at top right is written first and this low-level routine clobbers it if
- the new top-left text would overwrite it. However, users of this routine should
- try to keep the new text short enough to avoid this.
- */
- void show_mytext(char* msg)
- {
- Point tmp, pt = screen->r.min;
- int siz;
- tmp = stringsize(display->defaultfont, msg);
- siz = tmp.x;
- pt.x=top_left; pt.y+=outersep;
- if (top_left+siz > top_right) {
- Rectangle r;
- r.min.y = pt.y;
- r.min.x = top_right;
- r.max.y = r.min.y + smaxch.y;
- r.max.x = top_left+siz;
- draw(screen, r, display->white, display->opaque, r.min);
- top_right = top_left+siz;
- }
- string(screen, pt, display->black, ZP, display->defaultfont, msg);
- top_left += siz;
- }
- double rnd(double x, double tol) /* round to enough digits for accuracy tol */
- {
- double t = pow(10, floor(log10(tol)));
- return t * floor(x/t + .5);
- }
- double t_tol(double xtol, double ytol)
- {
- int t = (int) floor(cur_sel.t);
- fpoint* p = cur_sel.fp->p;
- double dx, dy;
- if (t==cur_sel.t)
- return 1;
- dx = fabs(p[t+1].x - p[t].x);
- dy = fabs(p[t+1].y - p[t].y);
- xtol /= (xtol>dx) ? xtol : dx;
- ytol /= (ytol>dy) ? ytol : dy;
- return (xtol<ytol) ? xtol : ytol;
- }
- void say_where(const transform* tr)
- {
- double xtol=dxuntransform(tr,1), ytol=dyuntransform(tr,-1);
- char buf[100];
- int n, nmax = (top_right - top_left)/smaxch.x;
- if (nmax >= 100)
- nmax = 100-1;
- n = sprintf(buf,"(%.14g,%.14g) at t=%.14g",
- rnd(cur_sel.p.x,xtol), rnd(cur_sel.p.y,ytol),
- rnd(cur_sel.t, t_tol(xtol,ytol)));
- if (cur_sel.fp->nam[0] != 0)
- sprintf(buf+n," %.*s", nmax-n-1, cur_sel.fp->nam);
- show_mytext(buf);
- }
- void reselect(const transform* tr) /* uselect(); set cur_sel; call this */
- {
- Point pt2, pt3;
- fpoint p2;
- transform tra;
- if (cur_sel.t < 0)
- return;
- if (tr==0)
- {tra=cur_trans(); tr=&tra;}
- do_transform(&p2, tr, &cur_sel.p);
- if (fabs(p2.x)+fabs(p2.y)>1e8 || (pt2.x=p2.x, pt2.y=p2.y, is_off_screen(pt2)))
- {cur_sel.t= -1; return;}
- pt3.x=pt2.x-Dotrad; pt3.y=pt2.y-Dotrad;
- draw(sel_bkg, sel_dot_box(0), screen, display->opaque, pt3);
- fillellipse(screen, pt2, Dotrad, Dotrad, clr_im(DRed), pt2);
- say_where(tr);
- }
- void do_select(Point pt)
- {
- transform tr = cur_trans();
- fpoint pt1, pt2, ctr;
- frectangle r;
- double slant;
- pt_on_fpoly* psel;
- unselect(&tr);
- do_untransform(&ctr, &tr, &pt);
- pt1.x=pt.x-fuzz; pt1.y=pt.y+fuzz;
- pt2.x=pt.x+fuzz; pt2.y=pt.y-fuzz;
- do_untransform(&r.min, &tr, &pt1);
- do_untransform(&r.max, &tr, &pt2);
- slant = u_slant_amt(&univ);
- slant_frect(&r, -slant);
- psel = select_in_univ(&r, slant);
- if (psel==0)
- return;
- if (logfil!=0) {
- fprintf(logfil,"%.14g\t%.14g\n", psel->p.x, psel->p.y);
- fflush(logfil);
- }
- cur_sel = *psel;
- reselect(&tr);
- }
- /***************************** Prompting for text *****************************/
- void unshow_mytext(char* msg)
- {
- Rectangle r;
- Point siz = stringsize(display->defaultfont, msg);
- top_left -= siz.x;
- r.min.y = screen->r.min.y + outersep;
- r.min.x = top_left;
- r.max.y = r.min.y + siz.y;
- r.max.x = r.min.x + siz.x;
- draw(screen, r, display->white, display->opaque, r.min);
- }
- /* Show the given prompt and read a line of user input. The text appears at the
- top left. If it runs into the top right text, we stop echoing but let the user
- continue typing blind if he wants to.
- */
- char* prompt_text(char* prompt)
- {
- static char buf[200];
- int n0, n=0, nshown=0;
- Rune c;
- unselect(0);
- show_mytext(prompt);
- while (n<200-1-UTFmax && (c=ekbd())!='\n') {
- if (c=='\b') {
- buf[n] = 0;
- if (n > 0)
- do n--;
- while (n>0 && (buf[n-1]&0xc0)==0x80);
- if (n < nshown)
- {unshow_mytext(buf+n); nshown=n;}
- } else {
- n0 = n;
- n += runetochar(buf+n, &c);
- buf[n] = 0;
- if (nshown==n0 && top_right-top_left >= smaxch.x)
- {show_mytext(buf+n0); nshown=n;}
- }
- }
- buf[n] = 0;
- while (ecanmouse())
- emouse();
- return buf;
- }
- /**************************** Redrawing the screen ****************************/
- /* Let p0 and its successors define a piecewise-linear function of a paramter t,
- and draw the 0<=t<=n1 portion using transform *tr.
- */
- void draw_fpts(const fpoint* p0, double n1, const transform* tr, int thick,
- Image* clr)
- {
- int n = (int) n1;
- const fpoint* p = p0 + n;
- fpoint pp;
- Point qq, q;
- if (n1 > n) {
- pp.x = p[0].x + (n1-n)*(p[1].x - p[0].x);
- pp.y = p[0].y + (n1-n)*(p[1].y - p[0].y);
- } else pp = *p--;
- do_transform(&qq, tr, &pp);
- if (n1==0)
- fillellipse(screen, qq, 1+thick, 1+thick, clr, qq);
- for (; p>=p0; p--) {
- do_transform(&q, tr, p);
- if(plotdots)
- fillellipse(screen, q, Dotrad, Dotrad, clr, q);
- else
- line(screen, qq, q, Enddisc, Enddisc, thick, clr, qq);
- qq = q;
- }
- }
- void draw_1fpoly(const fpolygon* fp, const transform* tr, Image* clr,
- const frectangle *udisp, double slant)
- {
- fpoint *p0=fp->p, *pn=fp->p+fp->n;
- double l1, l2;
- if (p0==pn && fcontains(udisp,*p0))
- {draw_fpts(p0, 0, tr, fp->thick, clr); return;}
- while ((l1=out_length(p0,pn,*udisp,slant)) < pn-p0) {
- fpoint p0sav;
- int i1 = (int) l1;
- p0+=i1; l1-=i1;
- p0sav = *p0;
- p0[0].x += l1*(p0[1].x - p0[0].x);
- p0[0].y += l1*(p0[1].y - p0[0].y);
- l2 = in_length(p0, pn, *udisp, slant);
- draw_fpts(p0, l2, tr, fp->thick, clr);
- *p0 = p0sav;
- p0 += (l2>0) ? ((int) ceil(l2)) : 1;
- }
- }
- double get_clip_data(const fpolygons *u, frectangle *r)
- {
- double slant = set_unslanted_y(u, &r->min.y, &r->max.y);
- r->min.x = u->disp.min.x;
- r->max.x = u->disp.max.x;
- return slant;
- }
- void draw_fpoly(const fpolygon* fp, const transform* tr, Image* clr)
- {
- frectangle r;
- double slant = get_clip_data(&univ, &r);
- draw_1fpoly(fp, tr, clr, &r, slant);
- }
- void eresized(int new)
- {
- transform tr;
- fpolygon* fp;
- frectangle clipr;
- double slant;
- if(new && getwindow(display, Refmesg) < 0) {
- fprintf(stderr,"can't reattach to window\n");
- exits("reshap");
- }
- draw(screen, screen->r, display->white, display->opaque, screen->r.min);
- tr = draw_frame();
- slant = get_clip_data(&univ, &clipr);
- for (fp=univ.p; fp!=0; fp=fp->link)
- if (fintersects(&clipr, &fp->bb, slant))
- draw_1fpoly(fp, &tr, fp->clr, &clipr, slant);
- reselect(0);
- if (mv_bkgd!=0 && mv_bkgd->repl==0) {
- freeimage(mv_bkgd);
- mv_bkgd = display->white;
- }
- flushimage(display, 1);
- }
- /********************************* Recoloring *********************************/
- int draw_palette(int n) /* n is number of colors; returns patch dy */
- {
- int y0 = screen->r.min.y + top_border;
- int dy = (screen->r.max.y - bot_border - y0)/n;
- Rectangle r;
- int i;
- r.min.y = y0;
- r.min.x = screen->r.max.x - rt_border + framewd;
- r.max.y = y0 + dy;
- r.max.x = screen->r.max.x;
- for (i=0; i<n; i++) {
- draw(screen, r, clrtab[i].im, display->opaque, r.min);
- r.min.y = r.max.y;
- r.max.y += dy;
- }
- return dy;
- }
- Image* palette_color(Point pt, int dy, int n)
- { /* mouse at pt, patch size dy, n colors */
- int yy;
- if (screen->r.max.x - pt.x > rt_border - framewd)
- return 0;
- yy = pt.y - (screen->r.min.y + top_border);
- if (yy<0 || yy>=n*dy)
- return 0;
- return clrtab[yy/dy].im;
- }
- void all_set_clr(fpolygons* fps, Image* clr)
- {
- fpolygon* p;
- for (p=fps->p; p!=0; p=p->link)
- p->clr = clr;
- }
-
- void do_recolor(int but, Mouse* m, int alluniv)
- {
- int nclr = clr_id(DWhite);
- int dy = draw_palette(nclr);
- Image* clr;
- if (!get_1click(but, m, 0)) {
- eresized(0);
- return;
- }
- clr = palette_color(m->xy, dy, nclr);
- if (clr != 0) {
- if (alluniv)
- all_set_clr(&univ, clr);
- else cur_sel.fp->clr = clr;
- }
- eresized(0);
- lift_button(but, m, Never);
- }
- /****************************** Move and rotate ******************************/
- void prepare_mv(const fpolygon* fp)
- {
- Rectangle r = screen->r;
- Image* scr0;
- int dt = 1 + fp->thick;
- r.min.x+=lft_border-dt; r.min.y+=top_border-dt;
- r.max.x-=rt_border-dt; r.max.y-=bot_border-dt;
- if (mv_bkgd!=0 && mv_bkgd->repl==0)
- freeimage(mv_bkgd);
- mv_bkgd = allocimage(display, r, CMAP8, 0, DNofill);
- if (mv_bkgd==0)
- mv_bkgd = display->white;
- else { transform tr = cur_trans();
- draw(mv_bkgd, r, screen, display->opaque, r.min);
- draw(mv_bkgd, sel_dot_box(&tr), sel_bkg, display->opaque, ZP);
- scr0 = screen;
- screen = mv_bkgd;
- draw_fpoly(fp, &tr, display->white);
- screen = scr0;
- }
- }
- void move_fp(fpolygon* fp, double dx, double dy)
- {
- fpoint *p, *pn=fp->p+fp->n;
- for (p=fp->p; p<=pn; p++) {
- (p->x) += dx;
- (p->y) += dy;
- }
- (fp->bb.min.x)+=dx; (fp->bb.min.y)+=dy;
- (fp->bb.max.x)+=dx; (fp->bb.max.y)+=dy;
- }
- void rotate_fp(fpolygon* fp, fpoint o, double theta)
- {
- double s=sin(theta), c=cos(theta);
- fpoint *p, *pn=fp->p+fp->n;
- for (p=fp->p; p<=pn; p++) {
- double x=p->x-o.x, y=p->y-o.y;
- (p->x) = o.x + c*x - s*y;
- (p->y) = o.y + s*x + c*y;
- }
- set_fbb(fp);
- }
- /* Move the selected fpolygon so the selected point tracks the mouse, and return
- the total amount of movement. Button but has already been held down for at
- least Mv_delay milliseconds and the mouse might have moved some distance.
- */
- fpoint do_move(int but, Mouse* m)
- {
- transform tr = cur_trans();
- int bbit = Button_bit(but);
- fpolygon* fp = cur_sel.fp;
- fpoint loc, loc0=cur_sel.p;
- double tsav = cur_sel.t;
- unselect(&tr);
- do { latest_mouse(but, m);
- (fp->thick)++; /* line() DISAGREES WITH ITSELF */
- draw_fpoly(fp, &tr, mv_bkgd);
- (fp->thick)--;
- do_untransform(&loc, &tr, &m->xy);
- move_fp(fp, loc.x-cur_sel.p.x, loc.y-cur_sel.p.y);
- cur_sel.p = loc;
- draw_fpoly(fp, &tr, fp->clr);
- } while (m->buttons & bbit);
- cur_sel.t = tsav;
- reselect(&tr);
- loc.x -= loc0.x;
- loc.y -= loc0.y;
- return loc;
- }
- double dir_angle(const Point* pt, const transform* tr)
- {
- fpoint p;
- double dy, dx;
- do_untransform(&p, tr, pt);
- dy=p.y-cur_sel.p.y; dx=p.x-cur_sel.p.x;
- return (dx==0 && dy==0) ? 0.0 : atan2(dy, dx);
- }
- /* Rotate the selected fpolygon around the selection point so as to track the
- direction angle from the selected point to m->xy. Stop when button but goes
- up and return the total amount of rotation in radians.
- */
- double do_rotate(int but, Mouse* m)
- {
- transform tr = cur_trans();
- int bbit = Button_bit(but);
- fpolygon* fp = cur_sel.fp;
- double theta0 = dir_angle(&m->xy, &tr);
- double th, theta = theta0;
- do { latest_mouse(but, m);
- (fp->thick)++; /* line() DISAGREES WITH ITSELF */
- draw_fpoly(fp, &tr, mv_bkgd);
- (fp->thick)--;
- th = dir_angle(&m->xy, &tr);
- rotate_fp(fp, cur_sel.p, th-theta);
- theta = th;
- draw_fpoly(fp, &tr, fp->clr);
- } while (m->buttons & bbit);
- unselect(&tr);
- cur_sel = prev_sel;
- reselect(&tr);
- return theta - theta0;
- }
- /********************************* Edit menu *********************************/
- typedef enum e_index {
- Erecolor, Ethick, Edelete, Eundo, Erotate, Eoptions,
- Emove
- } e_index;
- char* e_items[Eoptions+1];
- Menu e_menu = {e_items, 0, 0};
- typedef struct e_action {
- e_index typ; /* What type of action */
- fpolygon* fp; /* fpolygon the action applies to */
- Image* clr; /* color to use if typ==Erecolor */
- double amt; /* rotation angle or line thickness */
- fpoint pt; /* movement vector or rotation center */
- struct e_action* link; /* next in a stack */
- } e_action;
- e_action* unact = 0; /* heads a linked list of actions */
- e_action* do_undo(e_action*); /* pop off an e_action and (un)do it */
- e_action* save_act(e_action*,e_index); /* append new e_action for status quo */
- void save_mv(fpoint movement)
- {
- unact = save_act(unact, Emove);
- unact->pt = movement;
- }
- void init_e_menu(void)
- {
- char* u = "can't undo";
- e_items[Erecolor] = "recolor";
- e_items[Edelete] = "delete";
- e_items[Erotate] = "rotate";
- e_items[Eoptions-cantmv] = 0;
- e_items[Ethick] = (cur_sel.fp->thick >0) ? "thin" : "thick";
- if (unact!=0)
- switch (unact->typ) {
- case Erecolor: u="uncolor"; break;
- case Ethick: u=(unact->fp->thick==0) ? "unthin" : "unthicken";
- break;
- case Edelete: u="undelete"; break;
- case Emove: u="unmove"; break;
- case Erotate: u="unrotate"; break;
- }
- e_items[Eundo] = u;
- }
- void do_emenu(int but, Mouse* m)
- {
- int h;
- if (cur_sel.t < 0)
- return;
- init_e_menu();
- h = emenuhit(but, m, &e_menu);
- switch(h) {
- case Ethick: unact = save_act(unact, h);
- cur_sel.fp->thick ^= 1;
- eresized(0);
- break;
- case Edelete: unact = save_act(unact, h);
- fp_remove(&univ, cur_sel.fp);
- unselect(0);
- eresized(0);
- break;
- case Erecolor: unact = save_act(unact, h);
- do_recolor(but, m, 0);
- break;
- case Erotate: unact = save_act(unact, h);
- prepare_mv(cur_sel.fp);
- if (get_1click(but, m, 0)) {
- unact->pt = cur_sel.p;
- unact->amt = do_rotate(but, m);
- }
- break;
- case Eundo: unact = do_undo(unact);
- break;
- }
- }
- /******************************* Undoing edits *******************************/
- e_action* save_act(e_action* a0, e_index typ)
- { /* append new e_action for status quo */
- e_action* a = malloc(sizeof(e_action));
- a->link = a0;
- a->pt.x = a->pt.y = 0.0;
- a->amt = cur_sel.fp->thick;
- a->clr = cur_sel.fp->clr;
- a->fp = cur_sel.fp;
- a->typ = typ;
- return a;
- }
- /* This would be trivial except it's nice to preserve the selection in order to make
- it easy to undo a series of moves. (There's no do_unrotate() because it's harder
- and less important to preserve the selection in that case.)
- */
- void do_unmove(e_action* a)
- {
- double tsav = cur_sel.t;
- unselect(0);
- move_fp(a->fp, -a->pt.x, -a->pt.y);
- if (a->fp == cur_sel.fp) {
- cur_sel.p.x -= a->pt.x;
- cur_sel.p.y -= a->pt.y;
- }
- cur_sel.t = tsav;
- reselect(0);
- }
- e_action* do_undo(e_action* a0) /* pop off an e_action and (un)do it */
- {
- e_action* a = a0;
- if (a==0)
- return 0;
- switch(a->typ) {
- case Ethick: a->fp->thick = a->amt;
- eresized(0);
- break;
- case Erecolor: a->fp->clr = a->clr;
- eresized(0);
- break;
- case Edelete:
- a->fp->link = univ.p;
- univ.p = a->fp;
- grow_bb(&univ.bb, &a->fp->bb);
- eresized(0);
- break;
- case Emove:
- do_unmove(a);
- eresized(0);
- break;
- case Erotate:
- unselect(0);
- rotate_fp(a->fp, a->pt, -a->amt);
- eresized(0);
- break;
- }
- a0 = a->link;
- free(a);
- return a0;
- }
- /********************************* Main menu *********************************/
- enum m_index { Mzoom_in, Mzoom_out, Munzoom, Mslant, Munslant,
- Msquare_up, Mrecenter, Mrecolor, Mrestack, Mread,
- Mwrite, Mexit};
- char* m_items[] = {"zoom in", "zoom out", "unzoom", "slant", "unslant",
- "square up", "recenter", "recolor", "restack", "read",
- "write", "exit", 0};
- Menu m_menu = {m_items, 0, 0};
- void do_mmenu(int but, Mouse* m)
- {
- int e, h = emenuhit(but, m, &m_menu);
- switch (h) {
- case Mzoom_in:
- disp_zoomin(egetrect(but,m));
- eresized(0);
- break;
- case Mzoom_out:
- disp_zoomout(egetrect(but,m));
- eresized(0);
- break;
- case Msquare_up:
- disp_squareup();
- eresized(0);
- break;
- case Munzoom:
- init_disp();
- eresized(0);
- break;
- case Mrecenter:
- if (get_1click(but, m, &bullseye)) {
- recenter_disp(m->xy);
- eresized(0);
- lift_button(but, m, Never);
- }
- break;
- case Mslant:
- if (cur_sel.t>=0 && prev_sel.t>=0) {
- slant_disp(prev_sel.p, cur_sel.p);
- eresized(0);
- }
- break;
- case Munslant:
- univ.slant_ht = univ.disp.max.y - univ.disp.min.y;
- eresized(0);
- break;
- case Mrecolor:
- do_recolor(but, m, 1);
- break;
- case Mrestack:
- fps_invert(&univ);
- eresized(0);
- break;
- case Mread:
- e = doinput(prompt_text("File:"));
- if (e==0)
- eresized(0);
- else if (e<0)
- show_mytext(" - can't read");
- else {
- char ebuf[80];
- snprintf(ebuf, 80, " - error line %d", e);
- show_mytext(ebuf);
- }
- break;
- case Mwrite:
- if (!dooutput(prompt_text("File:")))
- show_mytext(" - can't write");
- break;
- case Mexit:
- exits("");
- }
- }
- /****************************** Handling events ******************************/
- void doevent(void)
- {
- ulong etype;
- int mobile;
- ulong mvtime;
- Event ev;
- etype = eread(Emouse|Ekeyboard, &ev);
- if(etype & Emouse) {
- if (ev.mouse.buttons & But1) {
- do_select(ev.mouse.xy);
- mvtime = Never;
- mobile = !cantmv && cur_sel.t>=0;
- if (mobile) {
- mvtime = ev.mouse.msec + Mv_delay;
- prepare_mv(cur_sel.fp);
- }
- if (!lift_button(1, &ev.mouse, mvtime) && mobile)
- save_mv(do_move(1, &ev.mouse));
- } else if (ev.mouse.buttons & But2)
- do_emenu(2, &ev.mouse);
- else if (ev.mouse.buttons & But3)
- do_mmenu(3, &ev.mouse);
- }
- /* no need to check (etype & Ekeyboard)--there are no keyboard commands */
- }
- /******************************** Main program ********************************/
- extern char* argv0;
- void usage(void)
- {
- int i;
- fprintf(stderr,"Usage %s [options] [infile]\n", argv0);
- fprintf(stderr,
- "option ::= -l logfile | -m\n"
- "\n"
- "Read a polygonal line graph in an ASCII format (one x y pair per line, delimited\n"
- "by spaces with a label after each polyline), and view it interactively. Use\n"
- "standard input if no infile is specified.\n"
- "Option -l specifies a file in which to log the coordinates of each point selected.\n"
- "(Clicking a point with button one selects it and displays its coordinates and\n"
- "the label of its polylone.) Option -m allows polylines to be moved and rotated.\n"
- "The polyline labels can use the following color names:"
- );
- for (i=0; clrtab[i].c!=DNofill; i++)
- fprintf(stderr,"%s%8s", (i%8==0 ? "\n" : " "), clrtab[i].nam);
- fputc('\n', stderr);
- exits("usage");
- }
- void main(int argc, char *argv[])
- {
- int e;
- ARGBEGIN {
- case 'm': cantmv=0;
- break;
- case 'l': logfil = fopen(ARGF(),"w");
- break;
- case 'p':
- plotdots++;
- break;
- default: usage();
- } ARGEND
- if(initdraw(0, 0, "gview") < 0)
- exits("initdraw");
- einit(Emouse|Ekeyboard);
- do {
- e = doinput(*argv ? *argv : "-");
- if (e < 0) {
- fprintf(stderr,"Cannot read input file %s\n", *argv);
- exits("no valid input file");
- } else if (e > 0) {
- fprintf(stderr,"Bad syntax at line %d of file %s\n", e, *argv ? *argv : "-");
- exits("bad syntax in input");
- }
- } while (*argv && *++argv);
- init_disp();
- init_clrtab();
- set_default_clrs(&univ, 0);
- adjust_border(display->defaultfont);
- cur_sel.t = prev_sel.t = -1;
- eresized(0);
- for(;;)
- doevent();
- }
|