graphcurses

Curses 2D graph generator
git clone git://git.margiolis.net/graphcurses.git
Log | Files | Refs | README | LICENSE

graphcurses.c (7158B)


      1 /* See LICENSE file for copyright and license details. */
      2 #include <err.h>
      3 #include <math.h>
      4 #include <signal.h>
      5 #include <stdio.h>
      6 #include <stdlib.h>
      7 #include <string.h>
      8 
      9 #include <curses.h>
     10 #include <matheval.h>
     11 
     12 #ifndef M_PI
     13 #define M_PI 3.14159265358979323846
     14 #endif /* M_PI */
     15 #ifndef SIGWINCH
     16 #define SIGWINCH 28
     17 #endif /* SIGWINCH */
     18 #define SHIFT_STEP 1.0f
     19 #define BUFSIZE 256
     20 
     21 #define YMAX (getmaxy(stdscr))
     22 #define XMAX (getmaxx(stdscr))
     23 #define CENTER(x, y) (((x) >> 1) - ((y) >> 1))
     24 #define PLANE_SCALE(val, omin, omax, nmin, nmax)			      \
     25 	((((val) - (omin)) / ((omax) - (omin))) * ((nmax) - (nmin)) + (nmin))
     26 #define PLANE_XSTEP(p, xstep)						      \
     27 	xstep = (p->xmax - p->xmin) / (p->xmaxs + 1.0f);		       
     28 #define PLANE_YSTEP(p, ystep)						      \
     29 	ystep = (p->xmax - p->ymin) / (p->ymaxs + 1.0f);
     30 #define ARRLEN(x) (sizeof(x) / sizeof(x[0]))
     31 
     32 struct plane {
     33 	float (*f)(float);
     34 	void *df;
     35 	float ymin;
     36 	float ymax;
     37 	float xmin;
     38 	float xmax;
     39 	float xscale;
     40 	float yscale;
     41 	int ymaxs;
     42 	int xmaxs;
     43 	int derivshow;
     44 };
     45 
     46 enum {
     47 	C_FG = 1,
     48 	C_F,
     49 	C_DF,
     50 };
     51 
     52 static void cursesinit(void);
     53 static void exprvalidate(void);
     54 static float expreval(float);
     55 static void planeinit(void);
     56 static void planeshift(float, float);
     57 static void zoomrestore(void);
     58 static void zoomhandle(float);
     59 static void axesdraw(void);
     60 static void graphdraw(void);
     61 static void graphplot(float, float);
     62 static void menuopts(void);
     63 static void sighandler(int);
     64 static void cleanup(void);
     65 
     66 static struct plane *p;
     67 static void *f = NULL;
     68 static int colors[] = {
     69 	[C_FG] = COLOR_BLUE,
     70 	[C_F] = COLOR_YELLOW,
     71 	[C_DF] = COLOR_MAGENTA,
     72 };
     73 
     74 static void
     75 cursesinit(void)
     76 {
     77 	struct sigaction sa;
     78 	int i;
     79 
     80 	if (!initscr())
     81 		errx(1, "initscr");
     82 	cbreak();
     83 	noecho();
     84 	curs_set(0);
     85 	keypad(stdscr, 1);
     86 
     87 	start_color();
     88 	use_default_colors();
     89 	for (i = 1; i < ARRLEN(colors); i++)
     90 		(void)init_pair(i, colors[i], -1);
     91 
     92 	memset(&sa, 0, sizeof(sa));
     93 	sa.sa_handler = sighandler;
     94 	sa.sa_flags = SA_RESTART;
     95 	sigemptyset(&sa.sa_mask);
     96 	sigaction(SIGINT, &sa, NULL);
     97 	sigaction(SIGTERM, &sa, NULL);
     98 	sigaction(SIGWINCH, &sa, NULL);
     99 }
    100 
    101 static void
    102 exprvalidate(void)
    103 {
    104 	char *buf;
    105 
    106 	if ((buf = malloc(BUFSIZE)) == NULL)
    107 		err(1, "malloc");
    108 	attron(COLOR_PAIR(C_FG));
    109 	for (;;) {
    110 		move(0, 0);
    111 		clrtoeol();
    112 		printw("f(x) = ");
    113 		echo();
    114 		refresh();
    115 		if (getnstr(buf, BUFSIZE) == ERR)
    116 			continue;
    117 		zoomrestore();
    118 		refresh();
    119 		noecho();
    120 		if (!(f = evaluator_create(buf)))
    121 			printw("Error in expression! Try again");
    122 		else
    123 			break;
    124 		refresh();
    125 	}
    126 	attroff(COLOR_PAIR(C_FG));
    127 	p->df = evaluator_derivative_x(f);
    128 	free(buf);
    129 }
    130 
    131 static float
    132 expreval(float x)
    133 {
    134 	return evaluator_evaluate_x(f, x);
    135 }
    136 
    137 static void
    138 planeinit(void)
    139 {
    140 	if ((p = malloc(sizeof(struct plane))) == NULL)
    141 		err(1, "malloc");
    142 	p->xmaxs = XMAX;
    143 	p->ymaxs = YMAX;
    144 	p->derivshow = 0;
    145 	p->f = expreval;
    146 	zoomrestore();
    147 }
    148 
    149 static void
    150 planeshift(float xshift, float yshift)
    151 {
    152 	xshift *= (p->xmax - p->xmin) / 16.0f;
    153 	yshift *= (p->ymax - p->ymin) / 16.0f;
    154 	p->xmin += xshift;
    155 	p->xmax += xshift;
    156 	p->ymin += yshift;
    157 	p->ymax += yshift;
    158 }
    159 
    160 static void
    161 zoomrestore(void)
    162 {
    163 	p->xmin = -2.0f * M_PI;
    164 	p->xmax = 2.0f * M_PI;
    165 	p->ymin = -M_PI;
    166 	p->ymax = M_PI;
    167 	p->xscale = 1.0f;
    168 	p->yscale = 1.0f;
    169 }
    170 
    171 static void
    172 zoomhandle(float factor)
    173 {
    174 	float xctr = (p->xmin + p->ymax) / 2.0f;
    175 	float yctr = (p->ymin + p->ymax) / 2.0f;
    176 
    177 	p->xmin = PLANE_SCALE(factor, 1.0f, 0.0f, p->xmin, xctr);
    178 	p->xmax = PLANE_SCALE(factor, 1.0f, 0.0f, p->xmax, xctr);
    179 	p->ymin = PLANE_SCALE(factor, 1.0f, 0.0f, p->ymin, yctr);
    180 	p->ymax = PLANE_SCALE(factor, 1.0f, 0.0f, p->ymax, yctr);  
    181 }
    182 
    183 static void
    184 axesdraw(void)
    185 {
    186 	float x0, y0, xstep, ystep, plotx, ploty, i;
    187 	int tick;
    188 
    189 	x0 = PLANE_SCALE(0.0f, p->xmin, p->xmax, 0.0f, p->xmaxs);
    190 	y0 = PLANE_SCALE(0.0f, p->ymin, p->ymax, p->ymaxs, 0.0f);
    191 	PLANE_XSTEP(p, xstep);
    192 	PLANE_YSTEP(p, ystep);
    193 
    194 	for (i = 0.0f; i < p->xmaxs; i += xstep) {
    195 		plotx = p->xmin + xstep * i;
    196 		tick = fabs(fmod(plotx, p->xscale)) < xstep;
    197 		mvaddch(y0, i, tick ? ACS_PLUS : ACS_HLINE);
    198 	}
    199 	for (i = 0.0f; i < p->ymaxs; i += ystep) {
    200 		ploty = p->ymin + ystep * i;
    201 		tick = fabs(fmod(ploty, p->yscale)) < ystep;
    202 		mvaddch(i, x0, tick ? ACS_PLUS : ACS_VLINE);
    203 	}
    204 	mvaddch(y0, x0, ACS_PLUS);
    205 }
    206 
    207 static void
    208 graphdraw(void)
    209 {
    210 	float x, y, dy, xstep;
    211 
    212 	PLANE_XSTEP(p, xstep);
    213 	for (x = p->xmin; x <= p->xmax; x += xstep) {
    214 		y = p->f(x);
    215 		attron(COLOR_PAIR(C_F));
    216 		graphplot(x, y);
    217 		if (p->derivshow) {
    218 			dy = evaluator_evaluate_x(p->df, x);
    219 			attron(COLOR_PAIR(C_DF));
    220 			graphplot(x, dy);
    221 		}
    222 	}
    223 	attroff(COLOR_PAIR(C_F) | COLOR_PAIR(C_DF));
    224 }
    225 
    226 static void
    227 graphplot(float x, float y)
    228 {
    229 	float xp = PLANE_SCALE(x, p->xmin, p->xmax, 0.0f, p->xmaxs);
    230 	float yp = PLANE_SCALE(y, p->ymin, p->ymax, p->ymaxs, 0.0f);
    231 
    232 	mvaddch(yp, xp, '.');
    233 }
    234 
    235 static void
    236 menuopts(void)
    237 {
    238 	WINDOW *opts;
    239 	int w, h, wy, wx;
    240 
    241 	w = 33;
    242 	h = 14;
    243 	wy = CENTER(YMAX, h);
    244 	wx = CENTER(XMAX, w);
    245 	if ((opts = newwin(h, w, wy, wx)) == NULL)
    246 		errx(1, "newwin");
    247 	werase(opts);
    248 	wattron(opts, COLOR_PAIR(C_FG));
    249 	box(opts, 0, 0);
    250 
    251 	/* fill menu */
    252 	mvwprintw(opts, 1,  1, "q              Quit");
    253 	mvwprintw(opts, 2,  1, "Up/k           Move up");
    254 	mvwprintw(opts, 3,  1, "Down/j         Move down");
    255 	mvwprintw(opts, 4,  1, "Left/h         Move left");
    256 	mvwprintw(opts, 5,  1, "Right/l        Move right");
    257 	mvwprintw(opts, 6,  1, "d              Show derivative");
    258 	mvwprintw(opts, 7,  1, "f              New function");
    259 	mvwprintw(opts, 8,  1, "r              Restore zoom");
    260 	mvwprintw(opts, 9,  1, "+              Zoom in");
    261 	mvwprintw(opts, 10, 1, "-              Zoom out");
    262 	mvwprintw(opts, 12, 1, "Press any key to quit the menu");
    263 
    264 	wrefresh(opts);
    265 	wattroff(opts, COLOR_PAIR(C_FG));
    266 	(void)wgetch(opts);
    267 	werase(opts);
    268 	wrefresh(opts);
    269 	delwin(opts);
    270 }
    271 
    272 static void
    273 sighandler(int sig)
    274 {
    275 	switch (sig) {
    276 	case SIGINT: /* FALLTHROUGH */
    277 	case SIGTERM:
    278 		cleanup();
    279 		exit(0);
    280 	case SIGWINCH:
    281 		/* TODO: lol... */
    282 		break;
    283 	}
    284 }
    285 
    286 static void
    287 cleanup(void)
    288 {
    289 	endwin();
    290 	evaluator_destroy(f);
    291 	free(p);
    292 }
    293 
    294 int
    295 main(int argc, char *argv[])
    296 {
    297 	int key = 0;
    298 
    299 	cursesinit();
    300 	planeinit();
    301 	exprvalidate();
    302 
    303 	for (; key != 'q'; key = getch()) {
    304 		switch (key) {
    305 		case KEY_UP:	/* FALLTHROUGH */
    306 		case 'k':
    307 			planeshift(0.0f,  SHIFT_STEP);
    308 			break;
    309 		case KEY_DOWN:	/* FALLTHROUGH */
    310 		case 'j':
    311 			planeshift(0.0f, -SHIFT_STEP);
    312 			break;
    313 		case KEY_LEFT:	/* FALLTHROUGH */
    314 		case 'h':
    315 			planeshift(-SHIFT_STEP, 0.0f);
    316 			break;
    317 		case KEY_RIGHT:	/* FALLTHROUGH */
    318 		case 'l':
    319 			planeshift(SHIFT_STEP, 0.0f);
    320 			break;
    321 		case '+':
    322 			zoomhandle(1.0f / 1.05f);
    323 			break;
    324 		case '-':
    325 			zoomhandle(1.05f);
    326 			break;
    327 		case 'd':
    328 			p->derivshow ^= 1;
    329 			break;
    330 		case 'r':
    331 			zoomrestore();
    332 			break;
    333 		case 'f':
    334 			exprvalidate();
    335 			break;
    336 		case 'c':
    337 			menuopts();
    338 			break;
    339 		}
    340 		erase();
    341 		attron(COLOR_PAIR(C_FG) | A_REVERSE | A_BOLD);
    342 		mvprintw(0, 0, "f(x) = %s", evaluator_get_string(f));
    343 		/* TODO: print controls opt */
    344 		if (p->derivshow)
    345 			mvprintw(1, 0, "f'(x) = %s", evaluator_get_string(p->df));
    346 		attroff(A_REVERSE | A_BOLD);
    347 		axesdraw();
    348 		attroff(COLOR_PAIR(C_FG));
    349 		graphdraw();
    350 		refresh();
    351 	}
    352 	cleanup();
    353 
    354 	return 0;
    355 }