diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
| commit | 37da2899f40661e3e9631e497da8dc59b971cbd0 (patch) | |
| tree | cbc6d4680e347d906f5fa7fca73214418741df72 /appl/math/gr.b | |
| parent | 54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff) | |
20060303a
Diffstat (limited to 'appl/math/gr.b')
| -rw-r--r-- | appl/math/gr.b | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/appl/math/gr.b b/appl/math/gr.b new file mode 100644 index 00000000..46899353 --- /dev/null +++ b/appl/math/gr.b @@ -0,0 +1,557 @@ +implement GR; + +include "sys.m"; + sys: Sys; + print, sprint: import sys; +include "math.m"; + math: Math; + ceil, fabs, floor, Infinity, log10, pow10, sqrt: import math; +include "draw.m"; + screen: ref Draw->Screen; +include "tk.m"; + tk: Tk; + Toplevel: import tk; +include "tkclient.m"; + tkclient: Tkclient; +include "gr.m"; + +gr_cfg := array[] of { + "frame .fc", + "frame .fc.b", + "label .fc.b.xy -text {0 0} -anchor e", + "pack .fc.b.xy -fill x", + "pack .fc.b -fill both -expand 1", + "canvas .fc.c -relief sunken -bd 2 -width 600 -height 480 -bg white"+ + " -font /fonts/lucidasans/unicode.8.font", + "pack .fc.c -fill both -expand 1", + "pack .Wm_t -fill x", + "pack .fc -fill both -expand 1", + "pack propagate . 0", + "bind .fc.c <ButtonPress-1> {send grcmd down1,%x,%y}", +}; + +TkCmd(t: ref Toplevel, arg: string): string +{ + rv := tk->cmd(t,arg); + if(rv!=nil && rv[0]=='!') + print("tk->cmd(%s): %s\n",arg,rv); + return rv; +} + + +open(ctxt: ref Draw->Context, title: string): ref Plot +{ + if(sys==nil){ + sys = load Sys Sys->PATH; + math = load Math Math->PATH; + tk = load Tk Tk->PATH; + tkclient = load Tkclient Tkclient->PATH; + tkclient->init(); + } + textsize := 8.; # textsize is in points, if no user transform + (t, tb) := tkclient->toplevel(ctxt, "", title, Tkclient->Appl); + cc := chan of string; + tk->namechan(t, cc, "grcmd"); + p := ref Plot(nil, Infinity,-Infinity,Infinity,-Infinity, textsize, t, tb, cc); + for (i:=0; i<len gr_cfg; i++) + tk->cmd(p.t,gr_cfg[i]); + tkclient->onscreen(p.t, nil); + tkclient->startinput(p.t, "kbd"::"ptr"::nil); + return p; +} + +Plot.bye(p: self ref Plot) +{ + cmdloop: for(;;) alt { + s := <-p.t.ctxt.kbd => + tk->keyboard(p.t, s); + s := <-p.t.ctxt.ptr => + tk->pointer(p.t, *s); + s := <-p.t.ctxt.ctl or + s = <-p.t.wreq or + s = <-p.titlechan => + if(s == "exit") + break cmdloop; + tkclient->wmctl(p.t, s); + case s{ + "size" => + canvw := int TkCmd(p.t, ".fc.c cget -width"); + canvh := int TkCmd(p.t, ".fc.c cget -height"); + TkCmd(p.t,".fc.b.xy configure -text {"+sprint("%d %d",canvw,canvh)+"}"); + } + press := <-p.canvaschan => + (nil,cmds) := sys->tokenize(press,","); + if(cmds==nil) continue; + case hd cmds { + "down1" => + xpos := real(hd tl cmds); + ypos := real(hd tl tl cmds); + x := (xpos-bx)/ax; + y := -(ypos-tky+by)/ay; + TkCmd(p.t,".fc.b.xy configure -text {"+sprint("%.3g %.3g",x,y)+"}"); + } + } + TkCmd(p.t,"destroy .;update"); + p.t = nil; +} + +Plot.equalxy(p: self ref Plot) +{ + r := 0.; + if( r < p.xmax - p.xmin ) r = p.xmax - p.xmin; + if( r < p.ymax - p.ymin ) r = p.ymax - p.ymin; + m := (p.xmax + p.xmin)/2.; + p.xmax = m + r/2.; + p.xmin = m - r/2.; + m = (p.ymax + p.ymin)/2.; + p.ymax = m + r/2.; + p.ymin = m - r/2.; +} + +Plot.graph(p: self ref Plot, x, y: array of real) +{ + n := len x; + op := OP(GR->GRAPH, n, array[n] of real, array[n] of real, nil); + while(n--){ + t := x[n]; + op.x[n] = t; + if(t < p.xmin) + p.xmin = t; + if(t > p.xmax) + p.xmax = t; + t = y[n]; + op.y[n] = t; + if(t < p.ymin) + p.ymin = t; + if(t > p.ymax) + p.ymax = t; + } + p.op = op :: p.op; +} + +Plot.text(p: self ref Plot, justify: int, s: string, x, y: real) +{ + op := OP(GR->TEXT, justify, array[1] of real, array[1] of real, s); + op.x[0] = x; + op.y[0] = y; + p.op = op :: p.op; +} + +Plot.pen(p: self ref Plot, nib: int) +{ + p.op = OP(GR->PEN, nib, nil, nil, nil) :: p.op; +} + + +#--------------------------------------------------------- +# The rest of this file is concerned with sending the "display list" +# to Tk. The only interesting parts of the problem are picking axes +# and drawing dashed lines properly. + +ax, bx, ay, by: real; # transform user to pixels +tky: con 630.; # Tk_y = tky - y +nseg: int; # how many segments in current stroke path +pendown: int; # is pen currently drawing? +xoff := array[] of{"w","","e"}; # LJUST, CENTER, RJUST +yoff := array[] of{"n","","s","s"}; # HIGH, MED, BASE, LOW +linewidth: real; +toplevel: ref Toplevel; # p.t +tkcmd: string; + +mv(x, y: real) +{ + tkcmd = sprint(".fc.c create line %.1f %.1f", ax*x+bx, tky-(ay*y+by)); +} + +stroke() +{ + if(pendown){ + tkcmd += " -width 3"; # -capstyle round -joinstyle round + TkCmd(toplevel,tkcmd); + tkcmd = nil; + pendown = 0; + nseg = 0; + } +} + +vec(x, y: real) +{ + tkcmd += sprint(" %.1f %.1f", ax*x+bx, tky-(ay*y+by)); + pendown = 1; + nseg++; + if(nseg>1000){ + stroke(); + mv(x,y); + } +} + +circle(u, v, radius: real) +{ + x := ax*u+bx; + y := tky-(ay*v+by); + r := radius*(ax+ay)/2.; + tkcmd = sprint(".fc.c create oval %.1f %.1f %.1f %.1f -width 3", + x-r, y-r, x+r, y+r); + TkCmd(toplevel,tkcmd); + tkcmd = nil; +} + +text(s: string, x, y: real, xoff, yoff: string) +{ + # rot = rotation in degrees. 90 is used for y-axis + # x,y are in PostScript coordinate system, not user + anchor := yoff + xoff; + if(anchor!="") + anchor = "-anchor " + anchor + " "; + tkcmd = sprint(".fc.c create text %.1f %.1f %s-text '%s", + ax*x+bx, + tky-(ay*y+by), anchor, s); + TkCmd(toplevel,tkcmd); + tkcmd = nil; +} + +datarange(xmin, xmax, margin: real): (real,real) +{ + r := 1.e-30; + if( r < 0.001*fabs(xmin) ) + r = 0.001*fabs(xmin); + if( r < 0.001*fabs(xmax) ) + r = 0.001*fabs(xmax); + if( r < xmax-xmin ) + r = xmax-xmin; + r *= 1.+2.*margin; + x0 :=(xmin+xmax)/2. - r/2.; + return ( x0, x0 + r); +} + +dashed(ndash: int, x, y: array of real) +{ + cx, cy: real; # current position + d: real; # length undone in p[i],p[i+1] + t: real; # length undone in current dash + n := len x; + if(n!=len y || n<=0) + return; + + # choose precise dashlen + s := 0.; + for(i := 0; i < n - 1; i += 1){ + u := x[i+1] - x[i]; + v := y[i+1] - y[i]; + s += sqrt(u*u + v*v); + } + i = int floor(real ndash * s); + if(i < 2) + i = 2; + dashlen := s / real(2 * i - 1); + + t = dashlen; + ink := 1; + mv(x[0], y[0]); + cx = x[0]; + cy = y[0]; + for(i = 0; i < n - 1; i += 1){ + u := x[i+1] - x[i]; + v := y[i+1] - y[i]; + d = sqrt(u * u + v * v); + if(d > 0.){ + u /= d; + v /= d; + while(t <= d){ + cx += t * u; + cy += t * v; + if(ink){ + vec(cx, cy); + stroke(); + }else{ + mv(cx, cy); + } + d -= t; + t = dashlen; + ink = 1 - ink; + } + cx = x[i+1]; + cy = y[i+1]; + if(ink){ + vec(cx, cy); + }else{ + mv(cx, cy); + } + t -= d; + } + } + stroke(); +} + +labfmt(x:real): string +{ + lab := sprint("%.6g",x); + if(len lab>2){ + if(lab[0]=='0' && lab[1]=='.') + lab = lab[1:]; + else if(lab[0]=='-' && len lab>3 && lab[1]=='0' && lab[2]=='.') + lab = "-"+lab[2:]; + } + return lab; +} + +Plot.paint(p: self ref Plot, xlabel, xunit, ylabel, yunit: string) +{ + oplist: list of OP; + + # tunable parameters for dimensions of graph (fraction of box side) + margin: con 0.075; # separation of data from box boundary + ticksize := 0.02; + sep := ticksize; # separation of text from box boundary + + # derived coordinates of various feature points... + x0, x1, y0, y1: real; # box corners, in original coord + # radius := 0.2*p.textsize; # radius for circle marker + radius := 0.8*p.textsize; # radius for circle marker + + Pen := SOLID; + width := SOLID; + linewidth = 2.; + nseg = 0; + pendown = 0; + + if(xunit=="") xunit = nil; + if(yunit=="") yunit = nil; + + (x0,x1) = datarange(p.xmin,p.xmax,margin); + ax = (400.-2.*p.textsize)/((x1-x0)*(1.+2.*sep)); + bx = 506.-ax*x1; + (y0,y1) = datarange(p.ymin,p.ymax,margin); + ay = (400.-2.*p.textsize)/((y1-y0)*(1.+2.*sep)); + by = 596.-ay*y1; + # PostScript version + # magic numbers here come from BoundingBox: 106 196 506 596 + # (x0,x1) = datarange(p.xmin,p.xmax,margin); + # ax = (400.-2.*p.textsize)/((x1-x0)*(1.+2.*sep)); + # bx = 506.-ax*x1; + # (y0,y1) = datarange(p.ymin,p.ymax,margin); + # ay = (400.-2.*p.textsize)/((y1-y0)*(1.+2.*sep)); + # by = 596.-ay*y1; + + # convert from fraction of box to PostScript units + ticksize *= ax*(x1-x0); + sep *= ax*(x1-x0); + + # revert to original drawing order + log := p.op; + oplist = nil; + while(log!=nil){ + oplist = hd log :: oplist; + log = tl log; + } + p.op = oplist; + + toplevel = p.t; + #------------send display list to Tk----------------- + while(oplist!=nil){ + op := hd oplist; + n := op.n; + case op.code{ + GRAPH => + if(Pen == DASHED){ + dashed(17, op.x, op.y); + }else if(Pen == DOTTED){ + dashed(85, op.x, op.y); + }else{ + for(i:=0; i<n; i++){ + xx := op.x[i]; + yy := op.y[i]; + if(Pen == CIRCLE){ + circle(xx, yy, radius/(ax+ay)); + }else if(Pen == CROSS){ + mv(xx-radius/ax, yy); + vec(xx+radius/ax, yy); + stroke(); + mv(xx, yy-radius/ay); + vec(xx, yy+radius/ay); + stroke(); + }else if(Pen == INVIS){ + }else{ + if(i==0){ + mv(xx, yy); + }else{ + vec(xx, yy); + } + } + } + stroke(); + } + TEXT => + angle := 0.; + if(op.n&UP) angle = 90.; + text(op.t,op.x[0],op.y[0],xoff[n&7],yoff[(n>>3)&7]); + PEN => + Pen = n; + if( Pen==SOLID && width!=SOLID ){ + linewidth = 2.; + width=SOLID; + }else if( Pen==REFERENCE && width!=REFERENCE ){ + linewidth = 0.8; + width=REFERENCE; + } + } + oplist = tl oplist; + } + + #--------------------now add axes----------------------- + mv(x0,y0); + vec(x1,y0); + vec(x1,y1); + vec(x0,y1); + vec(x0,y0); + stroke(); + + # x ticks + (lab1,labn,labinc,k,u,s) := mytic(x0,x1); + for (i := lab1; i <= labn; i += labinc){ + r := real i*s*u; + mv(r,y0); + vec(r,y0+ticksize/ay); + stroke(); + mv(r,y1); + vec(r,y1-ticksize/ay); + stroke(); + text(labfmt(real i*s),r,y0-sep/ay,"","n"); + } + yy := y0-(2.*sep+p.textsize)/ay; + labelstr := ""; + if(xlabel!=nil) + labelstr = xlabel; + if(k!=0||xunit!=nil) + labelstr += " /"; + if(k!=0) + labelstr += " ₁₀"+ string k; + if(xunit!=nil) + labelstr += " " + xunit; + text(labelstr,(x0+x1)/2.,yy,"","n"); + + # y ticks + (lab1,labn,labinc,k,u,s) = mytic(y0,y1); + for (i = lab1; i <= labn; i += labinc){ + r := real i*s*u; + mv(x0,r); + vec(x0+ticksize/ax,r); + stroke(); + mv(x1,r); + vec(x1-ticksize/ax,r); + stroke(); + text(labfmt(real i*s),x0-sep/ax,r,"e",""); + } + xx := x0-(4.*sep+p.textsize)/ax; + labelstr = ""; + if(ylabel!=nil) + labelstr = ylabel; + if(k!=0||yunit!=nil) + labelstr += " /"; + if(k!=0) + labelstr += " ₁₀"+ string k; + if(yunit!=nil) + labelstr += " " + yunit; + text(labelstr,xx,(y0+y1)/2.,"e",""); + + TkCmd(p.t, "update"); +} + + + +# automatic tic choice Eric Grosse 9 Dec 84 +# Input: low and high endpoints of expanded data range +# Output: lab1, labn, labinc, k, u, s where the tics are +# (lab1*s, (lab1+labinc)*s, ..., labn*s) * 10^k +# and u = 10^k. k is metric, i.e. k=0 mod 3. + +max3(a, b, c: real): real +{ + if(a<b) a=b; + if(a<c) a=c; + return(a); +} + +my_mod(i, n: int): int +{ + while(i< 0) i+=n; + while(i>=n) i-=n; + return(i); +} + +mytic(l, h: real): (int,int,int,int,real,real) +{ + lab1, labn, labinc, k, nlab, j, ndig, t1, tn: int; + u, s: real; + eps := .0001; + k = int floor( log10((h-l)/(3.+eps)) ); + u = pow10(k); + t1 = int ceil(l/u-eps); + tn = int floor(h/u+eps); + lab1 = t1; + labn = tn; + labinc = 1; + nlab = labn - lab1 + 1; + if( nlab>5 ){ + lab1 = t1 + my_mod(-t1,2); + labn = tn - my_mod( tn,2); + labinc = 2; + nlab = (labn-lab1)/labinc + 1; + if( nlab>5 ){ + lab1 = t1 + my_mod(-t1,5); + labn = tn - my_mod( tn,5); + labinc = 5; + nlab = (labn-lab1)/labinc + 1; + if( nlab>5 ){ + u *= 10.; + k++; + lab1 = int ceil(l/u-eps); + labn = int floor(h/u+eps); + nlab = labn - lab1 + 1; + labinc = 1; + } else if( nlab<3 ){ + lab1 = t1 + my_mod(-t1,4); + labn = tn - my_mod( tn,4); + labinc = 4; + nlab = (labn-lab1)/labinc + 1; + } + } + } + ndig = int(1.+floor(log10(max3(fabs(real lab1),fabs(real labn),1.e-30)))); + if( ((k<=0)&&(k>=-ndig)) # no zeros have to be added + || ((k<0)&&(k>=-3)) + || ((k>0)&&(ndig+k<=4)) ){ # even with zeros, label is small + s = u; + k = 0; + u = 1.; + }else if(k>0){ + s = 1.; + j = ndig; + while(k%3!=0){ + k--; + u/=10.; + s*=10.; + j++; + } + if(j-3>0){ + k+=3; + u*=1000.; + s/=1000.; + } + }else{ # k<0 + s = 1.; + j = ndig; + while(k%3!=0){ + k++; + u*=10.; + s/=10.; + j--; + } + if(j<0){ + k-=3; + u/=1000.; + s*=1000.; + } + } + return (lab1, labn, labinc, k, u, s); +} |
