ref: 2183f9eaeca9fb5d57b637c15707e663caf33575
dir: /appl/math/gr.b/
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);
}