fplot: draw axes; zoom more naturally; unzoom

This commit is contained in:
aiju 2018-05-20 09:14:16 +00:00
parent 40f6e00b9c
commit 1f8d347c92
2 changed files with 218 additions and 17 deletions

View file

@ -27,6 +27,11 @@ option's argument (in the WIDTHxHEIGHT format). The
.B -r
option accepts as argument the x and y ranges, in the format
.LR "xmin:xmax ymin:ymax".
By default
.I fplot
draws coordinate axes and tick marks; the
.B -a
option inhibits this.
.PP
Each function to be plotted may be a combination of the independent variable x,
the elementary operations (+, -, *, / and %), and the functions described in
@ -55,9 +60,11 @@ either exits with
.LR "syntax error"
or an empty status.
.SH BUGS
Parentheses after unary operators are not special, e.g. sin(x)/2 is parsed as sin x/2 = sin(x/2) and not (sin x)/2.
.PP
There is no unary plus or minus.
.PP
Axes are not drawn in
.B -c
output.
.SH HISTORY
.I Fplot
first appeared in 9front (July, 2011).

View file

@ -75,16 +75,16 @@ struct Operator {
"/", OBINARY, 0, 100, div,
"%", OBINARY, 0, 100, mod,
"^", OBINARY, 1, 200, pot,
"sin", OUNARY, 0, 50, osin,
"cos", OUNARY, 0, 50, ocos,
"tan", OUNARY, 0, 50, otan,
"asin", OUNARY, 0, 50, oasin,
"acos", OUNARY, 0, 50, oacos,
"atan", OUNARY, 0, 50, oatan,
"sqrt", OUNARY, 0, 50, osqrt,
"exp", OUNARY, 0, 50, oexp,
"log", OUNARY, 0, 50, olog,
"ln", OUNARY, 0, 50, oln,
"sin", OUNARY, 0, 300, osin,
"cos", OUNARY, 0, 300, ocos,
"tan", OUNARY, 0, 300, otan,
"asin", OUNARY, 0, 300, oasin,
"acos", OUNARY, 0, 300, oacos,
"atan", OUNARY, 0, 300, oatan,
"sqrt", OUNARY, 0, 300, osqrt,
"exp", OUNARY, 0, 300, oexp,
"log", OUNARY, 0, 300, olog,
"ln", OUNARY, 0, 300, oln,
};
struct Constant {
@ -105,11 +105,18 @@ int nfns;
Token *opstackbot;
double xmin = -10, xmax = 10;
double ymin = -10, ymax = 10;
double gymin, gymax;
Image *color;
int cflag;
int cflag, aflag;
char *imagedata;
int picx = 640, picy = 480;
typedef struct FRectangle FRectangle;
struct FRectangle {
double xmin, xmax, ymin, ymax;
} *zoomst;
int nzoomst;
void *
emalloc(int size)
{
@ -321,6 +328,8 @@ calc(Code *c, double x)
case OUNARY: case OBINARY:
o->f();
}
if(*sp < gymin) gymin = *sp;
if(*sp > gymax) gymax = *sp;
return *sp;
}
@ -401,10 +410,157 @@ drawgraph(Code *co, Rectangle *r)
{
int x;
gymin = Inf(1);
gymax = Inf(-1);
for(x = r->min.x; x < r->max.x; x++)
drawinter(co, r, convx(r, x), convx(r, x + 1), 0);
}
void
tickfmt(double d, double m, int n, char *fmt)
{
double e1, e2;
int x;
e1 = log10(fabs(m));
e2 = log10(fabs(m + n * d));
if(e2 > e1) e1 = e2;
if(e2 >= 4 || e2 < -3){
x = ceil(e1-log10(d)-1);
snprint(fmt, 32, "%%.%de", x);
}else{
x = ceil(-log10(d));
snprint(fmt, 32, "%%.%df", x);
}
}
int
xticklabel(char *fmt, double g, int p, int x, int y)
{
char buf[32];
Rectangle lr;
int ny;
snprint(buf, sizeof(buf), fmt, g);
lr.min = Pt(p, y+2);
lr.max = addpt(lr.min, stringsize(display->defaultfont, buf));
lr = rectsubpt(lr, Pt(Dx(lr) / 2-1, 0));
if(lr.max.y >= screen->r.max.y){
ny = y - 7 - Dy(lr);
lr = rectsubpt(lr, Pt(0, lr.min.y - ny));
}
if(rectinrect(lr, screen->r) && (lr.min.x > x || lr.max.x <= x)){
string(screen, lr.min, display->black, ZP, display->defaultfont, buf);
return 1;
}
return 0;
}
int
yticklabel(char *fmt, double g, int p, int x, int y)
{
char buf[32];
Rectangle lr;
int nx;
snprint(buf, sizeof(buf), fmt, g);
lr.min = Pt(0, 0);
lr.max = stringsize(display->defaultfont, buf);
lr = rectaddpt(lr, Pt(x-Dx(lr)-2, p - Dy(lr) / 2));
if(lr.min.x < screen->r.min.x){
nx = x + 7;
lr = rectsubpt(lr, Pt(lr.min.x - nx, 0));
}
if(rectinrect(lr, screen->r) && (lr.min.y > y || lr.max.y <= y)){
string(screen, lr.min, display->black, ZP, display->defaultfont, buf);
return 1;
}
return 0;
}
int
calcm(double min, double max, int e, double *dp, double *mp)
{
double d, m, r;
d = pow(10, e>>1);
if((e & 1) != 0) d *= 5;
m = min;
if(min < 0 && max > 0)
m += fmod(-m, d);
else{
r = fmod(m, d);
if(r < 0)
m -= r;
else
m += d - r;
}
if(dp != nil) *dp = d;
if(mp != nil) *mp = m;
return (max-m)*0.999/d;
}
int
ticks(double min, double max, double *dp, double *mp)
{
int e, n;
double m;
int beste;
double bestm;
e = 2 * ceil(log10(max - min));
beste = 0;
bestm = Inf(1);
for(;e>-100;e--){
n = calcm(min, max, e, nil, nil);
if(n <= 0) continue;
if(n < 10) m = 10.0 / n;
else m = n / 10.0;
if(m < bestm){
beste = e;
bestm = m;
}
if(n > 10) break;
}
calcm(min, max, beste, dp, mp);
return (max - *mp) / *dp;
}
void
drawaxes(void)
{
int x, y, p;
double dx, dy, mx, my;
int nx, ny;
int i;
char fmt[32];
if(xmin < 0 && xmax > 0)
x = deconvx(&screen->r, 0);
else
x = screen->r.min.x+5;
line(screen, Pt(x, screen->r.min.y), Pt(x, screen->r.max.y), Endarrow, 0, 0, display->black, ZP);
if(ymin < 0 && ymax > 0)
y = deconvy(&screen->r, 0);
else
y = screen->r.max.y-5;
line(screen, Pt(screen->r.min.x, y), Pt(screen->r.max.x, y), 0, Endarrow, 0, display->black, ZP);
nx = ticks(xmin, xmax, &dx, &mx);
tickfmt(dx, mx, nx, fmt);
for(i = 0; i <= nx; i++){
p = deconvx(&screen->r, dx*i+mx);
if(xticklabel(fmt, dx*i+mx, p, x, y))
line(screen, Pt(p, y), Pt(p, y-5), 0, 0, 0, display->black, ZP);
}
ny = ticks(ymin, ymax, &dy, &my);
tickfmt(dy, my, ny, fmt);
for(i = 0; i <= ny; i++){
p = deconvy(&screen->r, dy*i+my);
if(yticklabel(fmt, dy*i+my, p, x, y))
line(screen, Pt(x, p), Pt(x+5, p), 0, 0, 0, display->black, ZP);
}
}
void
drawgraphs(void)
{
@ -413,13 +569,15 @@ drawgraphs(void)
color = display->black;
for(i = 0; i < nfns; i++)
drawgraph(&fns[i], &screen->r);
if(!aflag)
drawaxes();
flushimage(display, 1);
}
void
usage(void)
{
fprint(2, "usage: fplot [-c [-s size]] [-r range] functions ...\n");
fprint(2, "usage: fplot [-a] [-c [-s size]] [-r range] functions ...\n");
exits("usage");
}
@ -430,10 +588,13 @@ zoom(void)
Rectangle r;
double xmin_, xmax_, ymin_, ymax_;
m.buttons = 7;
m.buttons = 0;
r = egetrect(1, &m);
if(Dx(r) < 1 || Dy(r) < 1)
return;
zoomst = realloc(zoomst, sizeof(FRectangle) * (nzoomst + 1));
if(zoomst == nil) sysfatal("realloc: %r");
zoomst[nzoomst++] = (FRectangle){xmin, xmax, ymin, ymax};
xmin_ = convx(&screen->r, r.min.x);
xmax_ = convx(&screen->r, r.max.x);
ymin_ = convy(&screen->r, r.max.y);
@ -446,6 +607,20 @@ zoom(void)
drawgraphs();
}
void
unzoom(void)
{
if(nzoomst == 0) return;
xmin = zoomst[nzoomst - 1].xmin;
xmax = zoomst[nzoomst - 1].xmax;
ymin = zoomst[nzoomst - 1].ymin;
ymax = zoomst[nzoomst - 1].ymax;
zoomst = realloc(zoomst, sizeof(FRectangle) * --nzoomst);
if(zoomst == nil && nzoomst != 0) sysfatal("realloc: %r");
draw(screen, screen->r, display->white, nil, ZP);
drawgraphs();
}
void
parsefns(int n, char **s)
{
@ -497,8 +672,10 @@ main(int argc, char **argv)
Event e;
Rectangle r;
int i;
static int lbut;
ARGBEGIN {
case 'a': aflag++; break;
case 'r': parserange(EARGF(usage())); break;
case 's': parsesize(EARGF(usage())); break;
case 'c': cflag++; break;
@ -526,10 +703,27 @@ main(int argc, char **argv)
drawgraphs();
for(;;) {
switch(event(&e)) {
case Emouse:
if((e.mouse.buttons & 1) != 0)
zoom();
if((~e.mouse.buttons & lbut & 4) != 0)
unzoom();
lbut = e.mouse.buttons;
break;
case Ekeyboard:
switch(e.kbdc) {
case 'q': exits(nil);
case 'z': zoom();
case 'q': case 127: exits(nil); break;
case 'y':
if(!isInf(ymin, 1) && !isInf(ymax, -1)){
zoomst = realloc(zoomst, sizeof(FRectangle) * (nzoomst + 1));
if(zoomst == nil) sysfatal("realloc: %r");
zoomst[nzoomst++] = (FRectangle){xmin, xmax, ymin, ymax};
ymin = gymin-0.05*(gymax-gymin);
ymax = gymax+0.05*(gymax-gymin);
draw(screen, screen->r, display->white, nil, ZP);
drawgraphs();
}
break;
}
}
}