graphcurses

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

commit 28095584f05d388a201fb0e531a4ef472d796f2f
parent 16a21f76b8d01f750dbbcb1187bf11e14defb9e4
Author: Christos Margiolis <christos@margiolis.net>
Date:   Thu, 10 Sep 2020 08:35:11 +0300

added derivatives plotting

Diffstat:
M.gitignore | 3+--
MMakefile | 33+++++++++++++--------------------
MREADME.md | 4++--
Agraphcurses.c | 283+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/main.c | 107-------------------------------------------------------------------------------
Dsrc/plane.c | 107-------------------------------------------------------------------------------
Dsrc/plane.h | 37-------------------------------------
7 files changed, 299 insertions(+), 275 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,2 +1 @@ -/obj/ -/bin/ +*.o diff --git a/Makefile b/Makefile @@ -1,17 +1,8 @@ -TARGET = graphcurses +TARGET = graphcurses INSTALL_PATH = /usr/local/bin -SRC_DIR = src -OBJ_DIR = obj -BIN_DIR = bin - -SRC = $(wildcard $(SRC_DIR)/*.c) -OBJ = $(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o) - -CP=cp -MOVE = mv -MKDIR_P = mkdir -p -RM_DIR=rm -rf +SRC = $(wildcard *.c) +OBJ = $(SRC:%.c=%.o) CC = gcc CPPFLAGS += -Iinclude -pedantic -U__STRICT_ANSI__ @@ -19,24 +10,26 @@ CFLAGS += -Wall -std=c99 -O3 LDFLAGS += -Llib LDLIBS += -lm -lmatheval -lncurses +CP=cp +MOVE = mv +MKDIR_P = mkdir -p +RM_DIR=rm -rf + .PHONY: all clean all: $(TARGET) $(TARGET): $(OBJ) - $(MKDIR_P) $(BIN_DIR) $(CC) $(LDFLAGS) $^ $(LDLIBS) -o $@ - $(MOVE) $(TARGET) $(BIN_DIR) -$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c - $(MKDIR_P) $(OBJ_DIR) +%.o: %.c $(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@ run: - ./$(BIN_DIR)/$(TARGET) + ./$(TARGET) install: $(TARGET) - $(CP) $(BIN_DIR)/$(TARGET) $(INSTALL_PATH) - + $(CP) $(TARGET) $(INSTALL_PATH) + clean: - $(RM_DIR) $(OBJ_DIR) $(BIN_DIR) + $(RM) $(OBJ) $(TARGET) diff --git a/README.md b/README.md @@ -29,8 +29,8 @@ The binary will be installed at `/usr/local/bin/` * Improve key handling * Add slope, curvature etc. -* Add an options window +* ~~Add an options window~~ * Add coordinates using cursor pointing -* *(Perhaps)* add derivative calculator +* ~~Add derivative calculator~~ * Fix axes plotting bug * Add point numbering on axes diff --git a/graphcurses.c b/graphcurses.c @@ -0,0 +1,283 @@ +#include <math.h> +#include <matheval.h> +#include <ncurses.h> +#include <stdio.h> +#include <stdlib.h> + +#define XMIN_PLANE (-2.0f * M_PI) +#define XMAX_PLANE ( 2.0f * M_PI) +#define YMIN_PLANE -M_PI +#define YMAX_PLANE M_PI +#define XSCALE_PLANE 1.0f +#define YSCALE_PLANE 1.0f +#define SHIFT_STEP 1.0f +#define ZOOM_IN_FACTOR (1.0f / 1.05f) +#define ZOOM_OUT_FACTOR 1.05f +#define BUFFSIZE 256 + +#define YMAX() (getmaxy(stdscr)) +#define XMAX() (getmaxx(stdscr)) +#define CENTER(x, y) ((x) / 2 - (y) / 2) +#define PLANE_SCALE(val, omin, omax, nmin, nmax) \ + ((((val) - (omin)) / ((omax) - (omin))) * ((nmax) - (nmin)) + (nmin)) +#define PLANE_XSTEP(p, xstep) \ + xstep = (p->xmax - p->xmin) / (p->xmaxs + 1.0f); +#define PLANE_YSTEP(p, ystep) \ + ystep = (p->xmax - p->ymin) / (p->ymaxs + 1.0f); + +struct Plane { + float (*f)(float); + void *df; + float ymin, ymax; + float xmin, xmax; + float xscale, yscale; + int ymaxs, xmaxs; + int derivative_show; +}; + +static void *f = NULL; + +static void curses_init(void); +static void func_get(struct Plane *, char *); +static void expression_validate(struct Plane *); +static float expression_evaluate(float); +static void keys_handle(struct Plane *, int); +static void plane_init(struct Plane *); +static void plane_shift(struct Plane *, float, float); +static void zoom_restore(struct Plane *); +static void zoom_handle(struct Plane *, float); +static void axes_draw(const struct Plane *); +static void graph_draw(const struct Plane *); +static void graph_plot(const struct Plane *, float, float); +static void menu_options(void); +static void menu_fill(struct _win_st *); + +void +curses_init(void) +{ + initscr(); + cbreak(); + noecho(); + curs_set(0); + keypad(stdscr, 1); + start_color(); + init_pair(1, COLOR_WHITE, COLOR_BLACK); + init_pair(2, COLOR_CYAN, COLOR_BLACK); + init_pair(3, COLOR_RED, COLOR_BLACK); +} + +void +func_get(struct Plane *p, char *buf) +{ + move(0, 0); + clrtoeol(); + printw("f(x) = "); + echo(); + refresh(); + getnstr(buf, BUFFSIZE); + zoom_restore(p); + refresh(); + noecho(); +} + +void +expression_validate(struct Plane *p) +{ + char *buf = (char *)malloc(BUFFSIZE + sizeof(char)); + func_get(p, buf); + while (!(f = evaluator_create(buf))) { + printw("Error in expression! Try again"); + func_get(p, buf); + refresh(); + } + p->df = evaluator_derivative_x(f); + free(buf); +} + +float +expression_evaluate(float x) +{ + return evaluator_evaluate_x(f, x); +} + +void +keys_handle(struct Plane *p, int key) +{ + switch (key) { + case 'k': case KEY_UP: plane_shift(p, 0.0f, SHIFT_STEP); break; + case 'j': case KEY_DOWN: plane_shift(p, 0.0f, -SHIFT_STEP); break; + case 'h': case KEY_LEFT: plane_shift(p, -SHIFT_STEP, 0.0f); break; + case 'l': case KEY_RIGHT: plane_shift(p, SHIFT_STEP, 0.0f); break; + case '+': zoom_handle(p, ZOOM_IN_FACTOR); break; + case '-': zoom_handle(p, ZOOM_OUT_FACTOR); break; + case 'd': p->derivative_show = !p->derivative_show; break; + case 'r': zoom_restore(p); break; + case 'f': expression_validate(p); break; + case 'm': menu_options(); break; + } +} + +void +plane_init(struct Plane *p) +{ + p->xmin = XMIN_PLANE; + p->xmax = XMAX_PLANE; + p->ymin = YMIN_PLANE; + p->ymax = YMAX_PLANE; + p->xscale = XSCALE_PLANE; + p->yscale = YSCALE_PLANE; + p->xmaxs = XMAX(); + p->ymaxs = YMAX(); +} + +void +plane_shift(struct Plane *p, float xshift, float yshift) +{ + xshift *= (p->xmax - p->xmin) / 16.0f; + yshift *= (p->ymax - p->ymin) / 16.0f; + p->xmin += xshift; + p->xmax += xshift; + p->ymin += yshift; + p->ymax += yshift; +} + +void +zoom_restore(struct Plane *p) +{ + p->xmin = XMIN_PLANE; + p->xmax = XMAX_PLANE; + p->ymin = YMIN_PLANE; + p->ymax = YMAX_PLANE; + p->xscale = XSCALE_PLANE; + p->yscale = YSCALE_PLANE; +} + +void +zoom_handle(struct Plane *p, float factor) +{ + float xctr = (p->xmin + p->ymax) / 2.0f; + float yctr = (p->ymin + p->ymax) / 2.0f; + p->xmin = PLANE_SCALE(factor, 1.0f, 0.0f, p->xmin, xctr); + p->xmax = PLANE_SCALE(factor, 1.0f, 0.0f, p->xmax, xctr); + p->ymin = PLANE_SCALE(factor, 1.0f, 0.0f, p->ymin, yctr); + p->ymax = PLANE_SCALE(factor, 1.0f, 0.0f, p->ymax, yctr); +} + +void +axes_draw(const struct Plane *p) +{ + float x0 = PLANE_SCALE(0.0f, p->xmin, p->xmax, 0.0f, p->xmaxs); + float y0 = PLANE_SCALE(0.0f, p->ymin, p->ymax, p->ymaxs, 0.0f); + float xstep, ystep, i; + PLANE_XSTEP(p, xstep); + PLANE_YSTEP(p, ystep); + for (i = 0.0f; i < p->xmaxs; i += xstep) { + float plotx = p->xmin + xstep * i; + int tick = fabs(fmod(plotx, p->xscale)) < xstep; + mvaddch(y0, i, tick ? ACS_PLUS : ACS_HLINE); + } + for (i = 0.0f; i < p->ymaxs; i += ystep) { + float ploty = p->ymin + ystep * i; + int tick = fabs(fmod(ploty, p->yscale)) < ystep; + mvaddch(i, x0, tick ? ACS_PLUS : ACS_VLINE); + } + mvaddch(y0, x0, ACS_PLUS); +} + +void +graph_draw(const struct Plane *p) +{ + float x, xstep; + PLANE_XSTEP(p, xstep); + for (x = p->xmin; x <= p->xmax; x += xstep) { + float y = p->f(x); + attron(COLOR_PAIR(2)); + graph_plot(p, x, y); + if (p->derivative_show) { + float dy = evaluator_evaluate_x(p->df, x); + attron(COLOR_PAIR(3)); + graph_plot(p, x, dy); + } + } + attroff(COLOR_PAIR(3)); + attroff(COLOR_PAIR(2)); +} + +void +graph_plot(const struct Plane *p, float x, float y) +{ + float xp = PLANE_SCALE(x, p->xmin, p->xmax, 0.0f, p->xmaxs); + float yp = PLANE_SCALE(y, p->ymin, p->ymax, p->ymaxs, 0.0f); + mvaddch(yp, xp, '.'); +} + +void +menu_options(void) +{ + int w = 33, h = 14; + int wy = CENTER(YMAX(), h); + int wx = CENTER(XMAX(), w); + WINDOW *opts = newwin(h, w, wy, wx); + werase(opts); + box(opts, 0, 0); + menu_fill(opts); + wrefresh(opts); + wgetch(opts); + werase(opts); + wrefresh(opts); + delwin(opts); +} + +void +menu_fill(WINDOW *opts) +{ + mvwprintw(opts, 1, 1, "q Quit"); + mvwprintw(opts, 2, 1, "Up/k Move up"); + mvwprintw(opts, 3, 1, "Down/j Move down"); + mvwprintw(opts, 4, 1, "Left/h Move left"); + mvwprintw(opts, 5, 1, "Right/l Move right"); + mvwprintw(opts, 6, 1, "d Show derivative"); + mvwprintw(opts, 7, 1, "f New function"); + mvwprintw(opts, 8, 1, "r Restore zoom"); + mvwprintw(opts, 9, 1, "+ Zoom in"); + mvwprintw(opts, 10, 1, "- Zoom out"); + mvwprintw(opts, 12, 1, "Press any key to quit the menu"); +} + +int +main(int argc, char **argv) +{ +#ifndef NCURSES_VERSION + fprintf(stderr, "ncurses is needed in order to run this program.\n"); + return EXIT_FAILURE; +#endif /* NCURSES_VERSION */ + curses_init(); + struct Plane p; + plane_init(&p); + zoom_restore(&p); + expression_validate(&p); + p.derivative_show = 0; + p.f = expression_evaluate; + + int key = 0; + for (; key != 'q'; key = getch()) { + keys_handle(&p, key); + erase(); + attron(COLOR_PAIR(1)); + attron(A_REVERSE); + attron(A_BOLD); + mvprintw(0, 0, "f(x) = %s", evaluator_get_string(f)); + if (p.derivative_show) + mvprintw(1, 0, "f'(x) = %s", evaluator_get_string(p.df)); + attroff(A_REVERSE); + attroff(A_BOLD); + axes_draw(&p); + attroff(COLOR_PAIR(1)); + graph_draw(&p); + refresh(); + } + + endwin(); + evaluator_destroy(f); + return EXIT_SUCCESS; +} diff --git a/src/main.c b/src/main.c @@ -1,107 +0,0 @@ -#include "plane.h" - -#define BUFFSIZE 256 - -static void *f = NULL; - -static void curses_init(void); -static void func_get(struct Plane *, char *); -static void expression_validate(struct Plane *); -static float expression_evaluate(float); -static void keys_handle(struct Plane *, int); - -int -main(int argc, char **argv) -{ -#ifndef NCURSES_VERSION - fprintf(stderr, "ncurses is needed in order to run this program.\n"); - return EXIT_FAILURE; -#endif /* NCURSES_VERSION */ - curses_init(); - struct Plane p; - plane_init(&p); - zoom_restore(&p); - expression_validate(&p); - p.yfunc = expression_evaluate; - - int key = 0; - for (; key != 'q'; key = getch()) { - attron(COLOR_PAIR(1)); - keys_handle(&p, key); - erase(); - attron(A_REVERSE); - attron(A_BOLD); - mvprintw(0, 0, "f(x) = %s", evaluator_get_string(f)); - attroff(A_REVERSE); - attroff(A_BOLD); - axes_draw(&p); - attroff(COLOR_PAIR(1)); - graph_draw(&p); - refresh(); - } - - endwin(); - evaluator_destroy(f); - return EXIT_SUCCESS; -} - -void -curses_init(void) -{ - initscr(); - cbreak(); - noecho(); - curs_set(0); - keypad(stdscr, 1); - start_color(); - init_pair(1, COLOR_WHITE, COLOR_BLACK); - init_pair(2, COLOR_YELLOW, COLOR_BLACK); -} - -void -func_get(struct Plane *p, char *buf) -{ - move(0, 0); - clrtoeol(); - printw("f(x) = "); - echo(); - refresh(); - getnstr(buf, BUFFSIZE); - zoom_restore(p); - refresh(); - noecho(); -} - -void -expression_validate(struct Plane *p) -{ - char *buf = (char *)malloc(BUFFSIZE + sizeof(char)); - func_get(p, buf); - while (!(f = evaluator_create(buf))) { - printw("Error in expression! Try again"); - func_get(p, buf); - refresh(); - } - free(buf); -} - -float -expression_evaluate(float x) -{ - return evaluator_evaluate_x(f, x); -} - -void -keys_handle(struct Plane *p, int key) -{ - switch (key) { - case 'k': case KEY_UP: plane_shift(p, 0.0f, SHIFT_STEP); break; - case 'j': case KEY_DOWN: plane_shift(p, 0.0f, -SHIFT_STEP); break; - case 'h': case KEY_LEFT: plane_shift(p, -SHIFT_STEP, 0.0f); break; - case 'l': case KEY_RIGHT: plane_shift(p, SHIFT_STEP, 0.0f); break; - case '+': zoom_handle(p, ZOOM_IN_FACTOR); break; - case '-': zoom_handle(p, ZOOM_OUT_FACTOR); break; - case 'r': zoom_restore(p); break; - case 'f': expression_validate(p); break; - } -} diff --git a/src/plane.c b/src/plane.c @@ -1,107 +0,0 @@ -#include "plane.h" - -static float plane_scale(float, float, float, float, float); -static void get_step(const struct Plane *, float *, float *); -static void graph_plot(const struct Plane *, float, float); - -void -plane_init(struct Plane *p) -{ - p->xmin = XMIN_PLANE; - p->xmax = XMAX_PLANE; - p->ymin = YMIN_PLANE; - p->ymax = YMAX_PLANE; - p->xscale = XSCALE_PLANE; - p->yscale = YSCALE_PLANE; - p->xmaxs = getmaxx(stdscr); - p->ymaxs = getmaxy(stdscr); -} - -void -plane_shift(struct Plane *p, float xshift, float yshift) -{ - xshift *= (p->xmax - p->xmin) / 16.0f; - yshift *= (p->ymax - p->ymin) / 16.0f; - p->xmin += xshift; - p->xmax += xshift; - p->ymin += yshift; - p->ymax += yshift; -} - -float -plane_scale(float val, float omin, float omax, float nmin, float nmax) -{ - float s = (val - omin) / (omax - omin); - return s * (nmax - nmin) + nmin; -} - -void -zoom_restore(struct Plane *p) -{ - p->xmin = XMIN_PLANE; - p->xmax = XMAX_PLANE; - p->ymin = YMIN_PLANE; - p->ymax = YMAX_PLANE; - p->xscale = XSCALE_PLANE; - p->yscale = YSCALE_PLANE; -} - -void -zoom_handle(struct Plane *p, float factor) -{ - float xctr = (p->xmin + p->ymax) / 2.0f; - float yctr = (p->ymin + p->ymax) / 2.0f; - p->xmin = plane_scale(factor, 1.0f, 0.0f, p->xmin, xctr); - p->xmax = plane_scale(factor, 1.0f, 0.0f, p->xmax, xctr); - p->ymin = plane_scale(factor, 1.0f, 0.0f, p->ymin, yctr); - p->ymax = plane_scale(factor, 1.0f, 0.0f, p->ymax, yctr); -} - -void -get_step(const struct Plane *p, float *xstep, float *ystep) -{ - *xstep = (p->xmax - p->xmin) / (p->xmaxs + 1.0f); - *ystep = (p->ymax - p->ymin) / (p->ymaxs + 1.0f); -} - -void -axes_draw(const struct Plane *p) -{ - int i; - float x0 = plane_scale(0.0f, p->xmin, p->xmax, 0.0f, p->xmaxs); - float y0 = plane_scale(0.0f, p->ymin, p->ymax, p->ymaxs, 0.0f); - float xstep, ystep; - get_step(p, &xstep, &ystep); - for (i = 0; i < p->xmaxs; i++) { - float plotx = p->xmin + xstep * i; - int tick = fabs(fmod(plotx, p->xscale)) < xstep; - mvaddch(y0, i, tick ? ACS_PLUS : ACS_HLINE); - } - for (i = 0; i < p->ymaxs; i++) { - float ploty = p->ymin + ystep * i; - int tick = fabs(fmod(ploty, p->yscale)) < ystep; - mvaddch(i, x0, tick ? ACS_PLUS : ACS_VLINE); - } - mvaddch(y0, x0, ACS_PLUS); -} - -void -graph_draw(const struct Plane *p) -{ - float x, xstep, ystep; - get_step(p, &xstep, &ystep); - attron(COLOR_PAIR(2)); - for (x = p->xmin; x <= p->xmax; x += xstep) { - float y = p->yfunc(x); - graph_plot(p, x, y); - } - attroff(COLOR_PAIR(2)); -} - -void -graph_plot(const struct Plane *p, float x, float y) -{ - float xp = plane_scale(x, p->xmin, p->xmax, 0.0f, p->xmaxs); - float yp = plane_scale(y, p->ymin, p->ymax, p->ymaxs, 0.0f); - mvaddch(yp, xp, '.'); -} diff --git a/src/plane.h b/src/plane.h @@ -1,37 +0,0 @@ -#ifndef PLANE_H -#define PLANE_H - -#include <math.h> -#include <matheval.h> -#include <ncurses.h> -#include <stdio.h> -#include <stdlib.h> - -#define XMIN_PLANE (-2.0f * M_PI) -#define XMAX_PLANE ( 2.0f * M_PI) -#define YMIN_PLANE -M_PI -#define YMAX_PLANE M_PI -#define XSCALE_PLANE 1.0f -#define YSCALE_PLANE 1.0f -#define SHIFT_STEP 1.0f -#define ZOOM_IN_FACTOR (1.0f / 1.05f) -#define ZOOM_OUT_FACTOR 1.05f - -struct Plane { - float (*yfunc)(float); - float ymin, ymax; - float xmin, xmax; - float xscale, yscale; - int ymaxs, xmaxs; -}; - -extern struct Plane p; - -extern void plane_init(struct Plane *); -extern void plane_shift(struct Plane *, float, float); -extern void zoom_restore(struct Plane *); -extern void zoom_handle(struct Plane *, float); -extern void axes_draw(const struct Plane *); -extern void graph_draw(const struct Plane *); - -#endif /* PLANE_H */