355 lines · 7.2 KB · C
RawDownload
#include <u.h>#include <libc.h>#include <bio.h>#include <thread.h>#include <draw.h>#include <mouse.h>#include <keyboard.h>#include "a.h" enum{	BACK,	TEXT,	AXIS,	DIVS,	LINE,	BULL,	BEAR,	NCOLS,}; enum{	Padding = 4,}; Chart chart;char *title = "^FCHI";Mousectl *mc;Keyboardctl *kc;Image *cols[NCOLS];Font *afont;Rectangle chartr;Rectangle arear;Point pmin;Point pmax;Point pt;Point pl;int offset;int maxprices; #define MIN(x,y) ((x) < (y) ? (x) : (y))#define MAX(x,y) ((x) > (y) ? (x) : (y)) doubleparsedouble(char *s){	char *e;	double d; 	d = strtod(s, &e);	if(e == nil || e == s)		return -1;	return d;} voidloadprices(Chart *chart, int fd){	Biobuf *bp;	char *s, *f[5];	int n, l;	Price *price; 	bp = Bfdopen(fd, OREAD);	if(bp == nil)		sysfatal("Bfdopen: %r");	chart->ymin = 1000000000;	chart->ymax = 0.0;	for(l = 0; ;l++){		s = Brdstr(bp, '\n', 1);		if(s == nil)			break;		n = getfields(s, f, nelem(f), 0, ",");		if(n != 5){			fprint(2, "invalid input at line %d\n", l+1);			continue;		}		price = &chart->prices[chart->nprices];		if(tmparse(&price->date, "YYYY-MM-DD", f[0], nil, nil) == nil){			fprint(2, "invalid date '%s' at line %d\n", f[0], l+1);			continue;		}		price->open  = parsedouble(f[1]);		price->high  = parsedouble(f[2]);		price->low   = parsedouble(f[3]);		price->close = parsedouble(f[4]);		chart->ymin  = MIN(chart->ymin, price->low);		chart->ymax  = MAX(chart->ymax, price->high);		chart->nprices += 1;	}	if(chart->nprices == 0)		sysfatal("could not parse input");} Pointstringdbl(Image *b, Point p, Image *c, Point sp, Font *f, double d){	char buf[32] = {0}; 	snprint(buf, sizeof buf, "%f", d);	return string(b, p, c, sp, f, buf);} voiddrawlegend(Price *price){	char buf[255] = {0};	Point p;	Image *c; 	p = Pt(chartr.max.x, chartr.min.y - 1);	draw(screen, Rpt(pl, p), cols[BACK], nil, ZP);	if(price == nil){		flushimage(display, 1);		return;	}	c = price->open < price->close ? cols[BULL] : cols[BEAR];	p = pl;	if(title != nil)		p = string(screen, p, cols[TEXT], ZP, font, " - ");	snprint(buf, sizeof buf, "%τ", tmfmt(&price->date, "DD/MM/YYYY"));	p = string(screen, p, cols[TEXT], ZP, font, buf);	p = string(screen, p, cols[TEXT], ZP, font, " O:");	p = stringdbl(screen, p, c, ZP, font, price->open);	p = string(screen, p, cols[TEXT], ZP, font, " H:");	p = stringdbl(screen, p, c, ZP, font, price->high);	p = string(screen, p, cols[TEXT], ZP, font, " L:");	p = stringdbl(screen, p, c, ZP, font, price->low);	p = string(screen, p, cols[TEXT], ZP, font, " C:");	p = stringdbl(screen, p, c, ZP, font, price->close);	flushimage(display, 1);} voiddrawyaxis(void){	char buf[32] = {0};	Point p, q;	int i, n;	double v, dv; 	border(screen, arear, 1, cols[AXIS], ZP);	n = (Dy(arear)-4*Padding) / 10;	dv = (chart.ymax - chart.ymin) / 10;	p = addpt(arear.min, Pt(1, 2*Padding));	q = addpt(p, Pt(Dx(arear) - 2, 0));	pmax = q;	for(i = 0; i <= 10; i++){		line(screen, p, q, 0, 0, 0, cols[DIVS], ZP);		line(screen, addpt(q, Pt(1,0)), addpt(q, Pt(5,0)), 0, 0, 0, cols[AXIS], ZP);		if(i == 10) v = chart.ymin;		else        v = chart.ymax - i*dv;		snprint(buf, sizeof buf, "%f", v);		string(screen, addpt(q, Pt(5+Padding, -font->height/3)), cols[AXIS], ZP, afont, buf);		p.y += n;		q.y += n;	}	pmin = subpt(q, Pt(0, n));} voiddrawxtick(Price *price, int x){	Point p, q;	char buf[12] = {0};	int w; 	p = Pt(x, arear.min.y + 1);	q = Pt(x, arear.max.y - 1);	line(screen, p, q, 0, 0, 0, cols[DIVS], ZP);	line(screen, addpt(q, Pt(0,1)), addpt(q, Pt(0,5)), 0, 0, 0, cols[AXIS], ZP);	snprint(buf, sizeof buf, "%τ", tmfmt(&price->date, "DD/MM/YYYY"));	w = stringwidth(afont, buf);	string(screen, addpt(q, Pt(-w/2, 5+Padding)), cols[AXIS], ZP, afont, buf);} intpointy(double v){	int y;	double Δv, Δp, Δy; 	Δv = v - chart.ymin;	Δp = pmax.y - pmin.y;	Δy = chart.ymax - chart.ymin;	y = pmin.y + Δp * (Δv / Δy);	return y;} voidredraw(void){	Point open, high, low, close;	Image *c;	Price *price;	int i, x; 	draw(screen, screen->r, cols[BACK], nil, ZP);	if(title != nil)		pl = string(screen, pt, cols[TEXT], ZP, font, title);	else		pl = pt;	drawyaxis();	for(i = offset; i-offset < maxprices-1 && i < chart.nprices; i++){		price = &chart.prices[i];		c = price->open < price->close ? cols[BULL] : cols[BEAR];		x = arear.min.x + 6*(i-offset+1) + 2;		if(x+4 >= arear.max.x){			fprint(2, "should not happen\n");			break;		}		open  = Pt(x, pointy(price->open));		high  = Pt(x, pointy(price->high));		low   = Pt(x, pointy(price->low));		close = Pt(x, pointy(price->close));		if(i > 0 && i%15 == 0)			drawxtick(price, x);		line(screen, high, low, 0, 0, 0, cols[LINE], ZP);		line(screen, open, close, 0, 0, 2, c, ZP);	}	flushimage(display, 1);} voidresize(void){	Point p, q;	int wmin, wmax, w;	char buf[32] = {0}; 	snprint(buf, sizeof buf, "%f", chart.ymin);	wmin = stringwidth(afont, buf);	snprint(buf, sizeof buf, "%f", chart.ymax);	wmax = stringwidth(afont, buf);	w = MAX(wmin, wmax) + Padding + Padding;	pt = addpt(screen->r.min, Pt(Padding, Padding));	p  = addpt(pt, Pt(0, font->height + Padding));	q  = subpt(screen->r.max, Pt(Padding, Padding + font->height + Padding));	chartr = Rpt(p, q);	arear  = chartr;	arear.max.x -= w;	maxprices = 1+(Dx(arear) - 6)/6;	if(maxprices >= chart.nprices)		offset = 0;	redraw();} Image*initcol(ulong c){	Image *i; 	i = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, c);	if(i == nil)		sysfatal("allocimage: %r");	return i;} voidinitcols(void){	cols[BACK] = initcol(0x282828ff);	cols[TEXT] = initcol(0xebdbb2ff);	cols[AXIS] = initcol(0x858891ff);	cols[DIVS] = initcol(0x333333ff);	cols[LINE] = initcol(0xa89984ff);	cols[BULL] = initcol(0x98971aff);	cols[BEAR] = initcol(0xcc241dff);}	 voidthreadmain(int argc, char *argv[]){	Mouse m;	Rune k;	Alt a[] = {		{ nil, &m,  CHANRCV },		{ nil, nil, CHANRCV },		{ nil, &k,  CHANRCV },		{ nil, nil, CHANEND },	};	int n, l; 	title = nil;	ARGBEGIN{	case 't':		title = ARGF();		break;	}ARGEND;	tmfmtinstall();	loadprices(&chart, 0);	if(initdraw(nil, nil, argv0) < 0)		sysfatal("initdraw: %r");	display->locking = 0;	if((mc = initmouse(nil, screen)) == nil)		sysfatal("initmouse: %r");	if((kc = initkeyboard(nil)) == nil)		sysfatal("initkeyboard: %r");	a[0].c = mc->c;	a[1].c = mc->resizec;	a[2].c = kc->c;	initcols();	afont = openfont(display, "/lib/font/bit/misc/unicode.6x13.font");	if(afont == nil)		sysfatal("openfont: %r");	offset = 0;	resize();	l = 0;	for(;;){		switch(alt(a)){		case 0:			if(ptinrect(m.xy, arear)){				n = offset + ((m.xy.x - arear.min.x) / 6) - 1;				if(n > 0 && n < chart.nprices){					drawlegend(&chart.prices[n]);					l = 1;				}else if(l){					drawlegend(nil);					l = 0;				}			}else if(l){				drawlegend(nil);				l = 0;			}			break;		case 1:			if(getwindow(display, Refnone) < 0)				sysfatal("getwindow: %r");			resize();			break;		case 2:			switch(k){			case Kleft:				if(offset > 0 && maxprices < chart.nprices){					offset -= 10;					redraw();				}				break;			case Kright:				if(offset+maxprices < chart.nprices){					offset += 10;					redraw();				}				break;			case 'q':			case Kdel:				threadexitsall(nil);			}			break;		}	}}