plotting complete - ploot - simple plotting tools HTML git clone git://bitreich.org/ploot git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/ploot DIR Log DIR Files DIR Refs DIR Tags DIR README DIR LICENSE --- DIR commit df6b2deeefb42f4102a9bba819e7d06d15a3aebf DIR parent 980b7ae7316438998c953a8e098d894aec001c57 HTML Author: Josuah Demangeon <mail@josuah.net> Date: Tue, 1 May 2018 18:35:48 +0200 plotting complete Diffstat: M Makefile | 5 +++-- M ffdraw.c | 56 +++++++++++++++++++++++-------- M ffdraw.h | 27 +++++++++++++++++++++------ M main.c | 295 +++++++++++++++++++++++-------- M ploot.c | 34 ++++++++++++++++---------------- 5 files changed, 308 insertions(+), 109 deletions(-) --- DIR diff --git a/Makefile b/Makefile @@ -1,13 +1,14 @@ CFLAGS = -Wall -Wextra -Werror -std=c89 -pedantic -D_POSIX_C_SOURCE=200809L +LDFLAGS = -static SRC = main.c ffdraw.c font_14x7.c - OBJ = $(SRC:.c=.o) +LIB = -lm all:x ploot ploot: $(OBJ) - ${CC} -static -o $@ $(OBJ) + ${CC} $(LDFLAGS) -o $@ $(OBJ) $(LIB) install:x ploot mkdir -p ${PREFIX}/bin DIR diff --git a/ffdraw.c b/ffdraw.c @@ -12,27 +12,25 @@ #include "ffdraw.h" -#define WIDTH 100 -#define HEIGHT 100 - -Color buffer[WIDTH * HEIGHT]; - /* * Convert (x,y) coordinates to (row,col) for printing into the buffer. * The buffer only contain one number, so the coordinate is a single integer: * width * x + y. + * The coordinates are shifted by offx and offy to permit relative coordinates. */ void -ffdraw_pixel(Canvas *can, Color col, +ffdraw_pixel(Canvas *can, Color *col, int x, int y) { - if (x >= can->h || y >= can->w) + x += can->x; + y += can->y; + if (x < 0 || x >= can->h || y < 0 || y >= can->w) return; memcpy(can->b + can->w * (can->h - 1 - x) + y, col, sizeof(*can->b)); } void -ffdraw_rectangle(Canvas *can, Color col, +ffdraw_rectangle(Canvas *can, Color *col, int x1, int y1, int x2, int y2) { @@ -50,7 +48,7 @@ ffdraw_rectangle(Canvas *can, Color col, * From Bresenham's line algorithm and dcat's tplot. */ void -ffdraw_line(Canvas *can, Color col, +ffdraw_line(Canvas *can, Color *col, int x0, int y0, int x1, int y1) { @@ -81,18 +79,18 @@ ffdraw_line(Canvas *can, Color col, } /* - * Draw a coloured glyph from font f centerd on x, y + * Draw a coloured glyph from font f centered on x. */ void -ffdraw_char(Canvas *can, Color col, char c, Font *f, +ffdraw_char(Canvas *can, Color *col, char c, Font *f, int x, int y) { int xf, yf; if (c & 0x80) c = '\0'; + x -= f->h / 2; - y -= f->w / 2; for (xf = 0; xf < f->h; xf++) for (yf = 0; yf < f->w; yf++) @@ -104,15 +102,45 @@ ffdraw_char(Canvas *can, Color col, char c, Font *f, * Draw a left aligned string without wrapping it. */ void -ffdraw_str(Canvas *can, Color col, char *s, Font *f, +ffdraw_str_left(Canvas *can, Color *col, char *s, Font *f, int x, int y) { for (; *s != '\0'; y += f->w, s++) ffdraw_char(can, col, *s, f, x, y); } +/* + * Draw a center aligned string without wrapping it. + */ +void +ffdraw_str_center(Canvas *can, Color *col, char *s, Font *f, + int x, int y) +{ + y -= f->w * strlen(s) / 2; + ffdraw_str_left(can, col, s, f, x, y); +} + +/* + * Draw a right aligned string without wrapping it. + */ +void +ffdraw_str_right(Canvas *can, Color *col, char *s, Font *f, + int x, int y) +{ + y -= f->w * strlen(s); + ffdraw_str_left(can, col, s, f, x, y); +} + void -ffdraw_fill(Canvas *can, Color col) +ffdraw_fill(Canvas *can, Color *col) { + int x, y; + + x = can->x; can->x = 0; + y = can->y; can->y = 0; + ffdraw_rectangle(can, col, 0, 0, can->h - 1, can->w - 1); + + can->x = x; + can->y = y; } DIR diff --git a/ffdraw.h b/ffdraw.h @@ -1,3 +1,5 @@ +#include <time.h> + #define MIN(x, y) ((x) < (y) ? (x) : (y)) #define MAX(x, y) ((x) > (y) ? (x) : (y)) @@ -7,6 +9,8 @@ typedef struct { int w; /* width */ int h; /* height */ Color *b; /* buffer */ + int x; /* x offset */ + int y; /* x offset */ } Canvas; typedef struct { @@ -15,10 +19,21 @@ typedef struct { char *b[128]; /* buffer */ } Font; +typedef struct { + Color col; /* for drawing the curve and the legend */ +/* time_t *t; / * array of timestamps */ + double *v; /* array of values */ + int n; /* number of values */ + time_t step; + char *name; /* for the legend */ +} Vlist; + /* ffdraw.c */ -void ffdraw_pixel (Canvas *, Color, int, int); -void ffdraw_rectangle(Canvas *, Color, int, int, int, int); -void ffdraw_line (Canvas *, Color, int, int, int, int); -void ffdraw_char (Canvas *, Color, char, Font *, int, int); -void ffdraw_str (Canvas *, Color, char *, Font *, int, int); -void ffdraw_fill (Canvas *, Color); +void ffdraw_pixel (Canvas *, Color *, int, int); +void ffdraw_rectangle(Canvas *, Color *, int, int, int, int); +void ffdraw_line (Canvas *, Color *, int, int, int, int); +void ffdraw_char (Canvas *, Color *, char, Font *, int, int); +void ffdraw_str_left(Canvas *, Color *, char *, Font *, int, int); +void ffdraw_str_center(Canvas *, Color *, char *, Font *, int, int); +void ffdraw_str_right(Canvas *, Color *, char *, Font *, int, int); +void ffdraw_fill (Canvas *, Color *); DIR diff --git a/main.c b/main.c @@ -1,134 +1,289 @@ #include <arpa/inet.h> -#include <stdlib.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> #include <time.h> +#include <math.h> + #include "ffdraw.h" #include "font_14x7.h" +#define ABS(x) ((x) < 0 ? -(x) : (x)) +#define LEN(x) (sizeof(x) / sizeof(*x)) + +/* + * Sizes and positions: + * + * Title on the first row legend + * y ^ here + * label |- + - + - + - + - with + * here |- + - + - + - + - the + * +--+---+---+---+--> full + * x label here height + */ + +#define MARGIN 4 + #define FONT_H 14 #define FONT_W 7 -/* as you see, no css skills needed */ +#define TITLE_X (IMAGE_H - TITLE_H) +#define TITLE_Y (XLABEL_W) +#define TITLE_H (FONT_H * 2) +#define TITLE_W (PLOT_W) + +#define XLABEL_X (PLOT_X) +#define XLABEL_Y (0) +#define XLABEL_H (PLOT_H) +#define XLABEL_W (FONT_W * 9 + MARGIN) -#define MARGIN 2 +#define YLABEL_X (0) +#define YLABEL_Y (PLOT_Y) +#define YLABEL_H (FONT_H * 2) +#define YLABEL_W (PLOT_W) + +#define PLOT_X (YLABEL_H) +#define PLOT_Y (XLABEL_W) +#define PLOT_W 700 +#define PLOT_H 200 + +#define LEGEND_X (YLABEL_H) +#define LEGEND_Y (IMAGE_W - LEGEND_W) +#define LEGEND_W (FONT_W + 150 + FONT_W) +#define LEGEND_H (PLOT_H) + +#define IMAGE_H (TITLE_H + PLOT_H + YLABEL_H) +#define IMAGE_W (XLABEL_W + PLOT_W + LEGEND_W) + +Color buffer[IMAGE_W * IMAGE_H]; + +Color c_axis = { 0xffff, 0xffff, 0xffff, 0xfff }; +Font *font = &font_14x7; -/* height */ +int +ffplot_t2y(time_t t, time_t tmin, time_t tmax) +{ + return (t - tmin) * PLOT_W / (tmax - tmin); +} -#define TITLE_H (MARGIN + FONT_H + MARGIN) -#define PLOT_H 100 -#define XLABEL_H (MARGIN + FONT_H + MARGIN) +int +ffplot_v2x(double v, double vmin, double vmax) +{ + return (v - vmin) * PLOT_H / (vmax - vmin); +} -#define IMAGE_H (TITLE_H + PLOT_H + XLABEL_H) +/* + * Set 'str' to a human-readable form of 'num' with always a width of 8 (+ 1 + * the '\0' terminator). Buffer overflow is ensured not to happen due to the + * max size of a double. Return the exponent. + */ +int +humanize(char *str, double val) +{ + int exp, precision; + char label[] = { '\0', 'M', 'G', 'T', 'E' }; -#define TITLE_MAX (IMAGE_H) -#define TITLE_MIN (IMAGE_H - TITLE_H) -#define PLOT_X_MAX (IMAGE_H - TITLE_H) -#define PLOT_X_MIN (XLABEL_H) -#define XLABEL_MAX (XLABEL_H) -#define XLABEL_MIN (0) + for (exp = 0; ABS(val) > 1000; exp++) + val /= 1000; -/* width */ + precision = (ABS(val) < 10) ? 2 : (ABS(val) < 100) ? 1 : 0; + precision += (exp == 0); -#define YLABEL_W (MARGIN + 20 + MARGIN) -#define PLOT_W 500 -#define LEGEND_W (MARGIN + 70 + MARGIN) + snprintf(str, 9, "%+.*f %c", precision, val, label[exp]); + str[8] = '\0'; + if (val >= 0) + str[0] = ' '; -#define IMAGE_W (YLABEL_W + PLOT_W + LEGEND_W) + return exp * 3; +} -#define LEGEND_MAX (IMAGE_W) -#define LEGEND_MIN (IMAGE_W - LEGEND_W) -#define PLOT_Y_MAX (IMAGE_W - LEGEND_W) -#define PLOT_Y_MIN (YLABEL_W) -#define YLABEL_MAX (YLABEL_W) -#define YLABEL_MIN (0) +void +ffplot_xaxis(Canvas *can, Color *label, Color *grid, + double vmin, double vmax, double vstep) +{ + double v; + int x; + char str[8 + 1]; -#define MID(x, y) ((x - y) / 2) + for (v = vmax - fmod(vmax, vstep); v >= vmin; v -= vstep) { + x = ffplot_v2x(v, vmin, vmax); -Color buffer[IMAGE_W * IMAGE_H]; + ffdraw_line(can, grid, + x, XLABEL_W, + x, XLABEL_W + PLOT_W); -Color c_axis = { 0xffff, 0xffff, 0xffff, 0xfff }; -Font *font = &font_14x7; + humanize(str, v); + ffdraw_str_right(can, label, str, font, + x, XLABEL_W - MARGIN); + } +} void -ffplot_xaxis(Canvas *can, Color label, Color grid, time_t tmax, time_t tstep) +ffplot_yaxis(Canvas *can, Color *label, Color *grid, + time_t tmin, time_t tmax, time_t tstep) { time_t t; - int y, ystep; + int y; char str[sizeof("YYYY/MM/DD")], *fmt; - if (tstep < 3600) { - fmt = " %H:%M:%S "; - ystep = sizeof(" HH:MM:SS ") * FONT_W; - } else { - fmt = " %Y/%m/%d "; - ystep = sizeof(" YYYY/MM/DD ") * FONT_W; - } + fmt = (tstep < 3600 * 24) ? " %H:%M:%S " : " %Y/%m/%d "; - t = tmax % tstep; - y = PLOT_Y_MAX + PLOT_W % ystep - ystep; + for (t = tmax - tmax % tstep; t >= tmin; t -= tstep) { + y = ffplot_t2y(t, tmin, tmax); - while (y > PLOT_Y_MIN) { - strftime(str, sizeof(str), fmt, localtime(&t)); - ffdraw_str(can, label, str, font, - XLABEL_MIN + XLABEL_H / 2, y - ystep / 2 + FONT_W); ffdraw_line(can, grid, - PLOT_X_MIN, y, - PLOT_X_MAX, y); - y -= ystep; - t -= tstep; + YLABEL_H, y, + YLABEL_H + PLOT_H, y); + + strftime(str, sizeof(str), fmt, localtime(&t)); + ffdraw_str_center(can, label, str, font, + YLABEL_H / 2, y); } } void -ffplot_(Canvas *can) +ffplot_title(Canvas *can, + Color *ct, char *title, + Color *cu, char *unit) { - (void)can; + ffdraw_str_left(can, ct, title, font, + TITLE_H / 2, 0); + ffdraw_str_right(can, cu, unit, font, + TITLE_H / 2, TITLE_W); } void -ffplot_graph(Canvas *can) +ffplot_graph(Canvas *can, Vlist *v, + double vmin, double vmax, + time_t tmin, time_t tmax) { - (void)can; + time_t t; + double *vp; + int x, y, n, xlast, ylast, first; + + first = 1; + t = tmin; + for (vp = v->v, n = v->n; n-- > 0; n--, vp++) { + x = ffplot_v2x(*vp, vmin, vmax); + y = ffplot_t2y(t, tmin, tmax); + + if (!first) + ffdraw_line(can, &v->col, xlast, ylast, x, y); + + xlast = x; + ylast = y; + t += v->step; + first = 0; + } } void -ffplot_legend(Canvas *can) +ffplot_plot(Canvas *can, Vlist **v, + double vmin, double vmax, + time_t tmin, time_t tmax) { - (void)can; + for (; *v != NULL; v++) + ffplot_graph(can, *v, vmin, vmax, tmin, tmax); } -static void -ffdraw(Canvas *can) +void +ffplot_legend(Canvas *can, Color *label_fg, Vlist **v) { - Color col1 = { 0x2222, 0x2222, 0x2222, 0xffff }; - Color label = { 0x3333, 0xffff, 0x8888, 0xffff }; - Color grid = { 0x4444, 0x4444, 0x4444, 0xffff }; + int n, x, y; - ffdraw_fill(can, col1); - ffplot_xaxis(can, label, grid, 3600 * 24 * 30, 360); -/* - ffdraw_line(can, col2, 49,1,9,79); - ffdraw_str(can, col2, "R\\S`T'UaVbWcYdZe\nfghb\tjoi\rklmnopqrstuvwxyz{|}", font, 44, 10); -*/ + for (n = 0; *v != NULL; v++, n++) { + x = LEGEND_H - n * (FONT_H + MARGIN) - FONT_H / 2; + + y = MARGIN + FONT_W; + ffdraw_str_left(can, &(*v)->col, "\1", font, x, y); + + y += FONT_W * 2; + ffdraw_str_left(can, label_fg, (*v)->name, font, x, y); + } +} + +void +ffdraw(Canvas *can, char *title, char *units, Vlist **vlistv, + double vmin, double vmax, double vstep, + time_t tmin, time_t tmax, time_t tstep) +{ + Color plot_bg = { 0x2222, 0x2222, 0x2222, 0xffff }; + Color grid_bg = { 0x2929, 0x2929, 0x2929, 0xffff }; + Color grid_fg = { 0x3737, 0x3737, 0x3737, 0xffff }; + Color label_fg = { 0x8888, 0x8888, 0x8888, 0xffff }; + Color title_fg = { 0xdddd, 0xdddd, 0xdddd, 0xffff }; + + can->x = 0; + can->y = 0; + ffdraw_fill(can, &plot_bg); + + can->x = PLOT_X; + can->y = PLOT_Y; + ffdraw_rectangle(can, &grid_bg, 0, 0, PLOT_H, PLOT_W); + + can->x = YLABEL_X; + can->y = YLABEL_Y; + ffplot_yaxis(can, &label_fg, &grid_fg, tmin, tmax, tstep); + + can->x = XLABEL_X; + can->y = XLABEL_Y; + ffplot_xaxis(can, &label_fg, &grid_fg, vmin, vmax, vstep); + + can->x = TITLE_X; + can->y = TITLE_Y; + ffplot_title(can, &title_fg, title, &label_fg, units); + + can->x = PLOT_X; + can->y = PLOT_Y; + ffplot_plot(can, vlistv, vmin, vmax, tmin, tmax); + + can->x = LEGEND_X; + can->y = LEGEND_Y; + ffplot_legend(can, &label_fg, vlistv); } int main(void) { + Canvas can = { IMAGE_W, IMAGE_H, buffer, 0, 0 }; + double v1[] = { 0.1, 30, -3, 42, 559, 343, 10, 345, 0 }; + double v2[] = { 30, -3, 42, 559, 343, 10, 345, 0, 0.3 }; + double v3[] = { 0, 0.3, 30, -3, 42, 5, 43, 345, 0, 10, + 0.3, 30, -3, 42, 59, 33, 35, 0, 40, 0.3, 30, + 0.3, 30, -3, 42, 55, 3, 5, 0, 100, 0.3, 30, + 95, 43, 45, 0, 40, 0.3, 30, 0.3, 30, -3, 42, + 0.3, 30, -3, 42, 5, 43, 3, 0, 100, 0.3, 30, + 0.3, 30, -3, 42, 59, 43, 45, 0, 4, 0.3, 30, + -3, 42, 559, 343, 45, 0, 10 }; + Vlist vl1 = { { 0x0000, 0xffff, 0xdddd, 0xffff }, NULL, LEN(v1), 500, "available ravens" }; + Vlist vl2 = { { 0xffff, 0x9999, 0x4444, 0xffff }, NULL, LEN(v2), 500, "used ravens" }; + Vlist vl3 = { { 0x1111, 0xffff, 0x5555, 0xffff }, NULL, LEN(v3), 57, "free ravens" }; + Vlist *vlistv[] = { NULL, NULL, NULL, NULL }; + double vmin, vmax, vstep; + time_t tmin, tmax, tstep; uint32_t w, h; - Canvas can; - can.b = buffer; - can.w = IMAGE_W; - can.h = IMAGE_H; + vlistv[0] = &vl1; vl1.v = v1; + vlistv[1] = &vl2; vl2.v = v2; + vlistv[2] = &vl3; vl3.v = v3; + vlistv[3] = NULL; + + vmin = -30; vmax = 700; vstep = 120; + tmin = 0; tmax = 2000; tstep = 300; + + ffdraw(&can, "Council of the ravens", "(feather per second)", vlistv, + vmin, vmax, vstep, + tmin, tmax, tstep); + w = htonl(IMAGE_W); h = htonl(IMAGE_H); + fputs("farbfeld", stdout); fwrite(&w, sizeof(w), 1, stdout); fwrite(&h, sizeof(h), 1, stdout); - ffdraw(&can); fwrite(can.b, IMAGE_W * IMAGE_H, sizeof(*can.b), stdout); + return 0; } DIR diff --git a/ploot.c b/ploot.c @@ -17,7 +17,7 @@ char *argv0; /* -** Add `val' at the current position `pos' of the `ring' buffer and set pos to +** Add 'val' at the current position 'pos' of the 'ring' buffer and set pos to ** the next postion. */ #define RING_ADD(rbuf, len, pos, val) \ @@ -27,8 +27,8 @@ do { \ } while (0) /* -** Copy the ring buffer `rbuf' content with current position `pos' into the -** buffer `buf'. Both buffer of length `len'. +** Copy the ring buffer 'rbuf' content with current position 'pos' into the +** buffer 'buf'. Both buffer of length 'len'. */ #define RING_COPY(buf, rbuf, len, pos) \ do { \ @@ -44,7 +44,7 @@ char *tflag = NULL; time_t oflag = 0; /* -** Set `str' to a human-readable form of `num' with always a width of 7 (+ 1 +** Set 'str' to a human-readable form of 'num' with always a width of 7 (+ 1 ** the '\0' terminator). Buffer overflow is ensured not to happen due to the ** max size of a double. */ @@ -67,7 +67,7 @@ humanize(char *str, double val) } /* -** Returns the maximal double of values between `beg' and `end'. +** Returns the maximal double of values between 'beg' and 'end'. */ double maxdv(double *beg, double *end) @@ -83,7 +83,7 @@ maxdv(double *beg, double *end) } /* -** If not null, print the title `str' centered on width. +** If not null, print the title 'str' centered on width. */ void title(char *str, int width) @@ -95,7 +95,7 @@ title(char *str, int width) /* ** Print vertical axis with humanized number from time to time, with occurences -** determined after the position on the vertical axis from the bottom `pos'. +** determined after the position on the vertical axis from the bottom 'pos'. */ void vaxis(double val, int pos) @@ -111,7 +111,7 @@ vaxis(double val, int pos) } /* -** Print horizontal axis for up to `col' values along with dates if reading time +** Print horizontal axis for up to 'col' values along with dates if reading time ** series. */ void @@ -152,8 +152,8 @@ line(double *beg, double *end, double top, double bot) } /* -** Plot values between `beg' and `end' in a plot of height `height'. -** If `str' is not NULL, it is set as a title above the graph. +** Plot values between 'beg' and 'end' in a plot of height 'height'. +** If 'str' is not NULL, it is set as a title above the graph. */ void plot(double *beg, double *end, int height, char *str, time_t start) @@ -181,8 +181,8 @@ plot(double *beg, double *end, int height, char *str, time_t start) } /* -** Read a simple format with one double per line and save the last `MAX_WIDTH' -** values into `buf' which must be at least MAX_VAL wide and return a pointer +** Read a simple format with one double per line and save the last 'MAX_WIDTH' +** values into 'buf' which must be at least MAX_VAL wide and return a pointer ** to the last element or NULL if the input contains error. */ double * @@ -203,8 +203,8 @@ read_simple(double buf[MAX_VAL]) /* ** Read a format with blank separated time_t-double pairs, one per line and save -** the last `MAX_WIDTH' values into `tbuf' and `vbuf' which must both be at -** least MAX_VAL wide and return a pointer to the last element of `vbuf' or +** the last 'MAX_WIDTH' values into 'tbuf' and 'vbuf' which must both be at +** least MAX_VAL wide and return a pointer to the last element of 'vbuf' or ** NULL if the input contains error. */ time_t * @@ -229,8 +229,8 @@ read_time_series(double *vbuf, time_t *tbuf) } /* -** Walk from `tbeg' and `tend' and add offset in `tbuf' every time there is no -** value in `step' amount of time, by setting a value to -1. +** Walk from 'tbeg' and 'tend' and add offset in 'tbuf' every time there is no +** value in 'step' amount of time, by setting a value to -1. */ double * skip_gaps(time_t *tbeg, time_t *tend, double *vbuf, time_t step) @@ -246,7 +246,7 @@ skip_gaps(time_t *tbeg, time_t *tend, double *vbuf, time_t step) toff += *tp % step; toff = *tbeg + toff / (tend - tbeg) + step / 2; - /* Fill `vbuf' with gap added at each time gap using vrbuf as + /* Fill 'vbuf' with gap added at each time gap using vrbuf as ** intermediate ring buffer. */ len = LEN(vrbuf); for (p = pos = 0, tp = tbeg, vp = vbuf; tp < tend; p++, vp++, tp++) {