shithub: riscv

Download patch

ref: 1f8d347c92a948bafde638c4864bc46096058dd3
parent: 40f6e00b9c4b0f5bdca1cc7caf27af23a6b10786
author: aiju <devnull@localhost>
date: Sun May 20 05:14:16 EDT 2018

fplot: draw axes; zoom more naturally; unzoom

--- a/sys/man/1/fplot
+++ b/sys/man/1/fplot
@@ -27,6 +27,11 @@
 .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 @@
 .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).
--- a/sys/src/cmd/fplot.c
+++ b/sys/src/cmd/fplot.c
@@ -75,16 +75,16 @@
 	"/",	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 @@
 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 @@
 		case OUNARY: case OBINARY:
 			o->f();
 		}
+	if(*sp < gymin) gymin = *sp;
+	if(*sp > gymax) gymax = *sp;
 	return *sp;
 }
 
@@ -401,11 +410,158 @@
 {
 	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)
 {
 	int i;
@@ -413,6 +569,8 @@
 	color = display->black;
 	for(i = 0; i < nfns; i++)
 		drawgraph(&fns[i], &screen->r);
+	if(!aflag)
+		drawaxes();
 	flushimage(display, 1);
 }
 
@@ -419,7 +577,7 @@
 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 @@
 	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);
@@ -447,6 +608,20 @@
 }
 
 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)
 {
 	int i, max, cur;
@@ -497,8 +672,10 @@
 	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 @@
 		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;
 				}
 			}
 		}