graphcurses

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

graphcurses.c (7120B)


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