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 }