rename ploot-ff to ploot-farbfeld, to make it obvious what -ff is - 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 ffb9fc9caeaf3a79f5ab4c7fcbbf4994c1037582 DIR parent 1c1a69494de95f4f5a3a439a16fac98026e2aa09 HTML Author: Josuah Demangeon <me@josuah.net> Date: Sat, 15 Feb 2020 14:52:07 +0100 rename ploot-ff to ploot-farbfeld, to make it obvious what -ff is While very good, not everybody knows the farbfeld file format (as opposed to png), and if ploot support multiple output format, it will end-up hard to know what extension maps to what. Diffstat: M .gitignore | 2 +- M Makefile | 4 ++-- M README | 10 +++++----- M ploot-csv.7 | 2 +- A ploot-farbfeld.1 | 103 +++++++++++++++++++++++++++++++ A ploot-farbfeld.c | 467 +++++++++++++++++++++++++++++++ M ploot-feed.1 | 2 +- D ploot-ff.1 | 103 ------------------------------- D ploot-ff.c | 463 ------------------------------- 9 files changed, 580 insertions(+), 576 deletions(-) --- DIR diff --git a/.gitignore b/.gitignore @@ -1,4 +1,4 @@ *.o *.core -ploot-ff +ploot-farbfeld ploot-feed DIR diff --git a/Makefile b/Makefile @@ -1,7 +1,7 @@ CFLAGS = -Wall -Wextra -std=c99 -pedantic -fPIC \ -D_POSIX_C_SOURCE=200809L LFLAGS = -static -BIN = ploot-ff ploot-feed +BIN = ploot-farbfeld ploot-feed ploot-braille LIB = -lm MANDIR = $(PREFIX)/share/man @@ -15,7 +15,7 @@ ${BIN}: ${SRC:.c=.o} ${BIN:=.o} install: $(BIN) mkdir -p ${PREFIX}/bin $(MANDIR)/man1 $(MANDIR)/man7 cp $(BIN) ${PREFIX}/bin - cp ploot-ff.1 ploot-feed.1 $(MANDIR)/man1 + cp ploot-farbfeld.1 ploot-feed.1 $(MANDIR)/man1 cp ploot-csv.7 $(MANDIR)/man7 clean: DIR diff --git a/README b/README @@ -1,11 +1,11 @@ ploot -================================================================================ +===== -ploot-ff --------------------------------------------------------------------------------- +ploot-farbfeld +-------------- -*ploot-ff* reads collectd-style comma separated values (CSV) and produces a plot +*ploot-farbfeld* reads collectd-style comma separated values (CSV) and produces a plot in the farbfeld [1] image format (pipe it to ff2png). It is an alternative to RRDtool [2]. @@ -18,7 +18,7 @@ name of the curves. ploot-feed --------------------------------------------------------------------------------- +---------- *ploot-feed* also reads collectd-style comma separated values (CSV) but produces a plain text continuous waterfall chart for live monitoring in the terminal. it DIR diff --git a/ploot-csv.7 b/ploot-csv.7 @@ -62,7 +62,7 @@ The remaining columns are values parsed as floating point numbers by .Sh SEE ALSO . .Xr ploot-feed 1 , -.Xr ploot-ff 1 +.Xr ploot-farbfeld 1 . .Sh HISTORY . DIR diff --git a/ploot-farbfeld.1 b/ploot-farbfeld.1 @@ -0,0 +1,103 @@ +.Dd $Mdocdate: August 08 2018$ +.Dt PLOOT-FF 1 +.Os +. +. +.Sh NAME +. +.Nm ploot-farbfeld +.Nd produce a farbfeld image of csv input +. +. +.Sh SYNOPSIS +. +.Nm ploot-farbfeld +.Op Fl t Ar title +.Op Fl u Ar unit +.Ar colors... +. +. +.Sh DESCRIPTION +. +The +.Nm +utility plots an image in the farbfeld format out of csv values coming from stdin. +. +.Bl -tag -width 6n +. +.It Fl t +Set the title of the plot printed at the top left corner. +. +.It Fl u +Set the unit description printed at the top right corner. +. +.It Ar colors +List of argument that specify the color for each column. +If the input csv have 5 columns in addition of the timestamp, there must +be 5 maxval arguments. +color_ts available are red, orange, yellow, green, cyan and blue. +. +.El +. +.Pp +The input format is documented in the +.Xr ploot-csv 7 +manual page. +. +. +.Sh EXIT STATUS +.Ex -std +. +. +.Sh EXAMPLES +. +.Bd -literal -offset indent +$ cat <<EOF >sample.txt +epoch,used_memory,free_memory +1533752053,160,401 +1533752054,180,381 +1533752055,301,260 +1533752056,303,258 +EOF +$ ploot-farbfeld -t demo -u MB red yellow <sample.txt +.Ed +. +. +.Sh SEE ALSO +. +.Xr ploot-farbfeld 1 , +.Xr ploot-csv 7 +. +.Pp +The +.Xr farbfeld 7 +image format: +.Lk https://tools.suckless.org/farbfeld/ +. +. +.Sh HISTORY +. +.Nm +earned its author a bitreich.org medal of misspelled program name. +. +.Pp +.Nm +was written at +.Lk gopher://bitreich.org/1/scm/ploot/ "Bitreich" +. +. +.Sh AUTHORS +. +.An Josuah Demangeon +.Aq Mt mail@josuah.net +. +. +.Sh BUGS +. +.Nm +does not make any math on the input: if the timestamps are not at regular +interval, ploot will still print one output line every 4 lines read, +regardless of the time interval. +. +.Pp +However, the timestamp printed on the left is always exact. DIR diff --git a/ploot-farbfeld.c b/ploot-farbfeld.c @@ -0,0 +1,467 @@ +#include <assert.h> +#include <ctype.h> +#include <fcntl.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> + +#include <arpa/inet.h> + +#include <math.h> + +#include "arg.h" +#include "log.h" +#include "def.h" + +#define MARGIN 4 + +#define IMAGE_H (TITLE_H + PLOT_H + XLABEL_H) +#define IMAGE_W (YLABEL_W + PLOT_W + LEGEND_W) + +#define TITLE_X (YLABEL_W) +#define TITLE_Y (IMAGE_H - TITLE_H) +#define TITLE_H ((font)->height * 2) +#define TITLE_W (PLOT_W) + +#define YLABEL_X (0) +#define YLABEL_Y (PLOT_Y) +#define YLABEL_H (PLOT_H) +#define YLABEL_W (40 + MARGIN) + +#define XLABEL_X (PLOT_X) +#define XLABEL_Y (0) +#define XLABEL_H ((font)->height * 2) +#define XLABEL_W (PLOT_W) + +#define PLOT_X (YLABEL_W) +#define PLOT_Y (XLABEL_H) +#define PLOT_W (700) +#define PLOT_H (160) + +#define LEGEND_X (IMAGE_W - LEGEND_W) +#define LEGEND_Y (TITLE_H + PLOT_H - (font)->height) +#define LEGEND_W (100) +#define LEGEND_H (PLOT_H) + +struct color { + uint16_t red; + uint16_t green; + uint16_t blue; + uint16_t alpha; +}; + +struct cname { + char *name; + struct color color; +}; + +struct canvas { + int w; /* width */ + int h; /* height */ + int x; /* x offset */ + int y; /* y offset */ + struct color *buf; +}; + +char const *arg0; +static char *tflag = ""; +static char *uflag = ""; +static struct font *font = &font13; + +static struct cname cname[] = { + /* name red green blue alpha */ + { "red", { 0xffff, 0x4444, 0x4444, 0xffff } }, + { "orange", { 0xffff, 0x9999, 0x4444, 0xffff } }, + { "yellow", { 0xffff, 0xffff, 0x4444, 0xffff } }, + { "green", { 0x2222, 0xffff, 0x5555, 0xffff } }, + { "cyan", { 0x0000, 0xffff, 0xdddd, 0xffff } }, + { "blue", { 0x2222, 0x9999, 0xffff, 0xffff } }, + { NULL, { 0, 0, 0, 0 } } +}; + +/* + * 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 * y + y. + * The coordinates are shifted by offx and offy to permit relative coordinates. + * + * The convention used: y + * - (0,0) is at the lower left corner of the canvas. | + * - (0,1) is above it. +--x + */ +static void +ff_pixel(struct canvas *can, struct color *color, + int x, int y) +{ + x += can->x; + y += can->y; + if (x < 0 || x >= can->w || y < 0 || y >= can->h) + return; + memcpy(can->buf + can->w * (can->h - 1 - y) + x, color, sizeof(*can->buf)); +} + +static void +ff_rectangle(struct canvas *can, struct color *color, + int y1, int x1, + int y2, int x2) +{ + int x, y, ymin, xmin, ymax, xmax; + + ymin = MIN(y1, y2); ymax = MAX(y1, y2); + xmin = MIN(x1, x2); xmax = MAX(x1, x2); + + for (y = ymin; y <= ymax; y++) + for (x = xmin; x <= xmax; x++) + ff_pixel(can, color, x, y); +} + +/* + * From Bresenham's line algorithm and dcat's tplot. + */ +static void +ff_line(struct canvas *can, struct color *color, + int x0, int y0, + int x1, int y1) +{ + int dy, dx, sy, sx, err, e; + + sx = x0 < x1 ? 1 : -1; + sy = y0 < y1 ? 1 : -1; + dx = abs(x1 - x0); + dy = abs(y1 - y0); + err = (dy > dx ? dy : -dx) / 2; + + for (;;) { + ff_pixel(can, color, x0, y0); + + if (y0 == y1 && x0 == x1) + break; + + e = err; + if (e > -dy) { + y0 += sy; + err -= dx; + } + if (e < dx) { + x0 += sx; + err += dy; + } + } +} + +/* + * Draw a coloured glyph from font f centered on y. + */ +static int +ff_char(struct canvas *can, struct color *color, char c, + int x, int y) +{ + int yf, xf, wf; + + if (c & 0x80) + c = '\0'; + y -= font->height / 2; + wf = font_width(font, c); + for (xf = 0; xf < wf; xf++) + for (yf = 0; yf < font->height; yf++) + if (font->glyph[(int)c][wf * (font->height - yf) + xf] == 3) + ff_pixel(can, color, x + xf, y + yf); + return wf + 1; +} + +/* + * Draw a left aligned string without wrapping it. + */ +static size_t +ff_text_left(struct canvas *can, struct color *color, char *s, + int x, int y) +{ + for (; *s != '\0'; s++) + x += ff_char(can, color, *s, x, y); + return x; +} + +/* + * Draw a center aligned string without wrapping it. + */ +static size_t +ff_text_center(struct canvas *can, struct color *color, char *s, + int x, int y) +{ + x -= font_strlen(font, s) / 2; + return ff_text_left(can, color, s, x, y); +} + +/* + * Draw a right aligned string without wrapping it. + */ +static size_t +ff_text_right(struct canvas *can, struct color *color, char *s, + int x, int y) +{ + x -= font_strlen(font, s); + return ff_text_left(can, color, s, x, y); +} + +static void +ff_print(struct canvas *can) +{ + uint32_t w, h; + + w = htonl(can->w); + h = htonl(can->h); + + fputs("farbfeld", stdout); + fwrite(&w, sizeof(w), 1, stdout); + fwrite(&h, sizeof(h), 1, stdout); + fwrite(can->buf, can->w * can->h, sizeof(*can->buf), stdout); +} + +static int +ff_t2x(time_t t, time_t tmin, time_t tmax) +{ + if (tmin == tmax) + return PLOT_W; + return (t - tmin) * PLOT_W / (tmax - tmin); +} + +static int +ff_v2y(double v, double vmin, double vmax) +{ + if (vmin == vmax) + return PLOT_H; + return (v - vmin) * PLOT_H / (vmax - vmin); +} + +static void +ff_xaxis(struct canvas *can, struct color *label, struct color *grid, + time_t tmin, time_t tmax, time_t tstep) +{ + time_t t; + int x; + char str[sizeof("MM/DD HH/MM")], *fmt; + + if (tstep < 3600 * 12) + fmt = "%H:%M:%S"; + else if (tstep < 3600 * 24) + fmt = "%m/%d %H:%M"; + else + fmt = "%X/%m/%d"; + + for (t = tmax - tmax % tstep; t >= tmin; t -= tstep) { + x = ff_t2x(t, tmin, tmax); + + ff_line(can, grid, + x, XLABEL_H, + x, XLABEL_H + PLOT_H); + + strftime(str, sizeof(str), fmt, localtime(&t)); + ff_text_center(can, label, str, + x, XLABEL_H / 2); + } +} + +static void +ff_yaxis(struct canvas *can, struct color *label, struct color *grid, + double vmin, double vmax, double vstep) +{ + double v; + int y; + char str[8 + 1]; + + for (v = vmax - fmod(vmax, vstep); v >= vmin; v -= vstep) { + y = ff_v2y(v, vmin, vmax); + + ff_line(can, grid, + YLABEL_W, y, + YLABEL_W + PLOT_W, y); + + humanize(str, v); + ff_text_right(can, label, str, + YLABEL_W - MARGIN, y); + } +} + +static void +ff_title(struct canvas *can, + struct color *ct, char *title, + struct color *cu, char *unit) +{ + ff_text_left(can, ct, title, TITLE_H / 2, 0); + ff_text_right(can, cu, unit, TITLE_H / 2, TITLE_W); +} + +static void +ff_plot(struct canvas *can, struct vlist *vl, struct color *color, + double vmin, double vmax, + time_t tmin, time_t tmax) +{ + time_t *tp; + double *vp; + int x, y, n, ylast, xlast, first; + + first = 1; + for (tp = vl->t, vp = vl->v, n = vl->n; n > 0; n--, vp++, tp++) { + y = ff_v2y(*vp, vmin, vmax); + x = ff_t2x(*tp, tmin, tmax); + + if (!first) + ff_line(can, color, xlast, ylast, x, y); + + ylast = y; + xlast = x; + first = 0; + } +} + +static void +ff_values(struct canvas *can, struct vlist *vl, struct color **cl, size_t ncol, + time_t tmin, time_t tmax, + double vmin, double vmax) +{ + for (; ncol > 0; ncol--, vl++, cl++) + ff_plot(can, vl, *cl, vmin, vmax, tmin, tmax); +} + +static void +ff_legend(struct canvas *can, struct color *fg, struct vlist *vl, struct color **cl, size_t ncol) +{ + size_t x, y; + + for (; ncol > 0; ncol--, vl++, cl++) { + y = -(ncol - 1) * (font->height + MARGIN); + x = MARGIN * 2; + x = ff_text_left(can, *cl, "-", x, y) + MARGIN; + x = ff_text_left(can, fg, vl->label, x, y); + } +} + +/* + * Plot the 'n' values list of the 'v' arrax with title 'name' and + * 'units' label. + * + * Title (units) + * x ^ Legend + * label | - + - + - + - + - .... + * here | - + - + - + - + - .... + * +---+---+---+---+--> + * x label here + */ +static void +ff(struct vlist *vl, struct color **cl, size_t ncol, char *name, char *units) +{ + struct canvas can = { IMAGE_W, IMAGE_H, 0, 0, NULL }; + struct color plot_bg = { 0x2222, 0x2222, 0x2222, 0xffff }; + struct color grid_bg = { 0x2929, 0x2929, 0x2929, 0xffff }; + struct color grid_fg = { 0x3737, 0x3737, 0x3737, 0xffff }; + struct color label_fg = { 0x8888, 0x8888, 0x8888, 0xffff }; + struct color title_fg = { 0xdddd, 0xdddd, 0xdddd, 0xffff }; + double vmin, vmax, vstep; + time_t tmin, tmax, tstep; + + scale(vl, ncol, &tmin, &tmax, &tstep, &vmin, &vmax, &vstep); + + assert(can.buf = calloc(IMAGE_H * IMAGE_W, sizeof *can.buf)); + + can.y = 0; + can.x = 0; + ff_rectangle(&can, &plot_bg, 0, 0, IMAGE_H - 1, IMAGE_W - 1); + + can.x = PLOT_X; + can.y = PLOT_Y; + ff_rectangle(&can, &grid_bg, 0, 0, PLOT_H, PLOT_W); + + can.x = XLABEL_X; + can.y = XLABEL_Y; + ff_xaxis(&can, &label_fg, &grid_fg, tmin, tmax, tstep); + + can.x = YLABEL_X; + can.y = YLABEL_Y; + ff_yaxis(&can, &label_fg, &grid_fg, vmin, vmax, vstep); + + can.x = TITLE_X; + can.y = TITLE_Y; + ff_title(&can, &title_fg, name, &label_fg, units); + + can.x = PLOT_X; + can.y = PLOT_Y; + ff_values(&can, vl, cl, ncol, tmin, tmax, vmin, vmax); + + can.x = LEGEND_X; + can.y = LEGEND_Y; + ff_legend(&can, &label_fg, vl, cl, ncol); + + ff_print(&can); +} + +static struct color * +name_to_color(char *name) +{ + struct cname *cn; + + for (cn = cname; cn->name != NULL; cn++) + if (strcmp(name, cn->name) == 0) + return &cn->color; + return NULL; +} + +static void +argv_to_color(struct color **cl, char **argv) +{ + for (; *argv != NULL; cl++, argv++) + if ((*cl = name_to_color(*argv)) == NULL) + err(1, "unknown color name: %s", *argv); +} + +static void +usage(void) +{ + fprintf(stderr, "usage: %s [-t title] [-u unit] {", arg0); + fputs(cname->name, stderr); + for (struct cname *cn = cname + 1; cn->name != NULL; cn++) + fprintf(stderr, ",%s", cn->name); + fputs("}...\n", stderr); + exit(1); +} + +int +main(int argc, char **argv) +{ + struct vlist *vl; + struct color **cl; + char labels[LINE_MAX]; + size_t ncol; + + ARG_SWITCH(argc, argv) { + case 't': + tflag = ARG; + break; + case 'u': + uflag = ARG; + break; + default: + usage(); + } + + if (argc == 0) + usage(); + + assert(cl = calloc(argc, sizeof(*cl))); + + csv_labels(stdin, labels, &vl, &ncol); + if (ncol > (size_t)argc) + err(1, "too many columns or not enough arguments"); + else if (ncol < (size_t)argc) + err(1, "too many arguments or not enough columns"); + csv_values(stdin, vl, ncol); + argv_to_color(cl, argv); + + ff(vl, cl, argc, tflag, uflag); + + free(vl); + free(cl); + return 0; +} DIR diff --git a/ploot-feed.1 b/ploot-feed.1 @@ -60,7 +60,7 @@ $ ploot-feed -w 80 1 1 <sample.txt . .Sh SEE ALSO . -.Xr ploot-ff 1 , +.Xr ploot-farbfeld 1 , .Xr ploot-format 7 . . DIR diff --git a/ploot-ff.1 b/ploot-ff.1 @@ -1,103 +0,0 @@ -.Dd $Mdocdate: August 08 2018$ -.Dt PLOOT-FF 1 -.Os -. -. -.Sh NAME -. -.Nm ploot-ff -.Nd produce a farbfeld image of csv input -. -. -.Sh SYNOPSIS -. -.Nm ploot-ff -.Op Fl t Ar title -.Op Fl u Ar unit -.Ar colors... -. -. -.Sh DESCRIPTION -. -The -.Nm -utility plots an image in the farbfeld format out of csv values coming from stdin. -. -.Bl -tag -width 6n -. -.It Fl t -Set the title of the plot printed at the top left corner. -. -.It Fl u -Set the unit description printed at the top right corner. -. -.It Ar colors -List of argument that specify the color for each column. -If the input csv have 5 columns in addition of the timestamp, there must -be 5 maxval arguments. -color_ts available are red, orange, yellow, green, cyan and blue. -. -.El -. -.Pp -The input format is documented in the -.Xr ploot-csv 7 -manual page. -. -. -.Sh EXIT STATUS -.Ex -std -. -. -.Sh EXAMPLES -. -.Bd -literal -offset indent -$ cat <<EOF >sample.txt -epoch,used_memory,free_memory -1533752053,160,401 -1533752054,180,381 -1533752055,301,260 -1533752056,303,258 -EOF -$ ploot-ff -t demo -u MB red yellow <sample.txt -.Ed -. -. -.Sh SEE ALSO -. -.Xr ploot-ff 1 , -.Xr ploot-csv 7 -. -.Pp -The -.Xr farbfeld 7 -image format: -.Lk https://tools.suckless.org/farbfeld/ -. -. -.Sh HISTORY -. -.Nm -earned its author a bitreich.org medal of misspelled program name. -. -.Pp -.Nm -was written at -.Lk gopher://bitreich.org/1/scm/ploot/ "Bitreich" -. -. -.Sh AUTHORS -. -.An Josuah Demangeon -.Aq Mt mail@josuah.net -. -. -.Sh BUGS -. -.Nm -does not make any math on the input: if the timestamps are not at regular -interval, ploot will still print one output line every 4 lines read, -regardless of the time interval. -. -.Pp -However, the timestamp printed on the left is always exact. DIR diff --git a/ploot-ff.c b/ploot-ff.c @@ -1,463 +0,0 @@ -#include <assert.h> -#include <ctype.h> -#include <fcntl.h> -#include <limits.h> -#include <stdint.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <time.h> - -#include <arpa/inet.h> - -#include <math.h> - -#include "arg.h" -#include "log.h" -#include "def.h" - -#define MARGIN 4 - -#define IMAGE_H (TITLE_H + PLOT_H + XLABEL_H) -#define IMAGE_W (YLABEL_W + PLOT_W + LEGEND_W) - -#define TITLE_X (YLABEL_W) -#define TITLE_Y (IMAGE_H - TITLE_H) -#define TITLE_H ((font)->height * 2) -#define TITLE_W (PLOT_W) - -#define YLABEL_X (0) -#define YLABEL_Y (PLOT_Y) -#define YLABEL_H (PLOT_H) -#define YLABEL_W (40 + MARGIN) - -#define XLABEL_X (PLOT_X) -#define XLABEL_Y (0) -#define XLABEL_H ((font)->height * 2) -#define XLABEL_W (PLOT_W) - -#define PLOT_X (YLABEL_W) -#define PLOT_Y (XLABEL_H) -#define PLOT_W (700) -#define PLOT_H (160) - -#define LEGEND_X (IMAGE_W - LEGEND_W) -#define LEGEND_Y (TITLE_H + PLOT_H - (font)->height) -#define LEGEND_W (100) -#define LEGEND_H (PLOT_H) - -struct color { - uint16_t red; - uint16_t green; - uint16_t blue; - uint16_t alpha; -}; - -struct cname { - char *name; - struct color color; -}; - -struct canvas { - int w; /* width */ - int h; /* height */ - int x; /* x offset */ - int y; /* y offset */ - struct color *buf; -}; - -char const *arg0; -static char *tflag = ""; -static char *uflag = ""; -static struct font *font = &font13; - -static struct cname cname[] = { - /* name red green blue alpha */ - { "red", { 0xffff, 0x4444, 0x4444, 0xffff } }, - { "orange", { 0xffff, 0x9999, 0x4444, 0xffff } }, - { "yellow", { 0xffff, 0xffff, 0x4444, 0xffff } }, - { "green", { 0x2222, 0xffff, 0x5555, 0xffff } }, - { "cyan", { 0x0000, 0xffff, 0xdddd, 0xffff } }, - { "blue", { 0x2222, 0x9999, 0xffff, 0xffff } }, - { NULL, { 0, 0, 0, 0 } } -}; - -/* - * 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 * y + y. - * The coordinates are shifted by offx and offy to permit relative coordinates. - * - * The convention used: y - * - (0,0) is at the lower left corner of the canvas. | - * - (0,1) is above it. +--x - */ -static void -ff_pixel(struct canvas *can, struct color *color, - int x, int y) -{ - x += can->x; - y += can->y; - if (x < 0 || x >= can->w || y < 0 || y >= can->h) - return; - memcpy(can->buf + can->w * (can->h - 1 - y) + x, color, sizeof(*can->buf)); -} - -static void -ff_rectangle(struct canvas *can, struct color *color, - int y1, int x1, - int y2, int x2) -{ - int x, y, ymin, xmin, ymax, xmax; - - ymin = MIN(y1, y2); ymax = MAX(y1, y2); - xmin = MIN(x1, x2); xmax = MAX(x1, x2); - - for (y = ymin; y <= ymax; y++) - for (x = xmin; x <= xmax; x++) - ff_pixel(can, color, x, y); -} - -/* - * From Bresenham's line algorithm and dcat's tplot. - */ -static void -ff_line(struct canvas *can, struct color *color, - int x0, int y0, - int x1, int y1) -{ - int dy, dx, sy, sx, err, e; - - sx = x0 < x1 ? 1 : -1; - sy = y0 < y1 ? 1 : -1; - dx = abs(x1 - x0); - dy = abs(y1 - y0); - err = (dy > dx ? dy : -dx) / 2; - - for (;;) { - ff_pixel(can, color, x0, y0); - - if (y0 == y1 && x0 == x1) - break; - - e = err; - if (e > -dy) { - y0 += sy; - err -= dx; - } - if (e < dx) { - x0 += sx; - err += dy; - } - } -} - -/* - * Draw a coloured glyph from font f centered on y. - */ -static int -ff_char(struct canvas *can, struct color *color, char c, - int x, int y) -{ - int yf, xf, wf; - - if (c & 0x80) - c = '\0'; - y -= font->height / 2; - wf = font_width(font, c); - for (xf = 0; xf < wf; xf++) - for (yf = 0; yf < font->height; yf++) - if (font->glyph[(int)c][wf * (font->height - yf) + xf] == 3) - ff_pixel(can, color, x + xf, y + yf); - return wf + 1; -} - -/* - * Draw a left aligned string without wrapping it. - */ -static size_t -ff_text_left(struct canvas *can, struct color *color, char *s, - int x, int y) -{ - for (; *s != '\0'; s++) - x += ff_char(can, color, *s, x, y); - return x; -} - -/* - * Draw a center aligned string without wrapping it. - */ -static size_t -ff_text_center(struct canvas *can, struct color *color, char *s, - int x, int y) -{ - x -= font_strlen(font, s) / 2; - return ff_text_left(can, color, s, x, y); -} - -/* - * Draw a right aligned string without wrapping it. - */ -static size_t -ff_text_right(struct canvas *can, struct color *color, char *s, - int x, int y) -{ - x -= font_strlen(font, s); - return ff_text_left(can, color, s, x, y); -} - -static void -ff_print(struct canvas *can) -{ - uint32_t w, h; - - w = htonl(can->w); - h = htonl(can->h); - - fputs("farbfeld", stdout); - fwrite(&w, sizeof(w), 1, stdout); - fwrite(&h, sizeof(h), 1, stdout); - fwrite(can->buf, can->w * can->h, sizeof(*can->buf), stdout); -} - -static int -ff_t2x(time_t t, time_t tmin, time_t tmax) -{ - if (tmin == tmax) - return PLOT_W; - return (t - tmin) * PLOT_W / (tmax - tmin); -} - -static int -ff_v2y(double v, double vmin, double vmax) -{ - if (vmin == vmax) - return PLOT_H; - return (v - vmin) * PLOT_H / (vmax - vmin); -} - -static void -ff_xaxis(struct canvas *can, struct color *label, struct color *grid, - time_t tmin, time_t tmax, time_t tstep) -{ - time_t t; - int x; - char str[sizeof("MM/DD HH/MM")], *fmt; - - if (tstep < 3600 * 12) - fmt = "%H:%M:%S"; - else if (tstep < 3600 * 24) - fmt = "%m/%d %H:%M"; - else - fmt = "%X/%m/%d"; - - for (t = tmax - tmax % tstep; t >= tmin; t -= tstep) { - x = ff_t2x(t, tmin, tmax); - - ff_line(can, grid, - x, XLABEL_H, - x, XLABEL_H + PLOT_H); - - strftime(str, sizeof(str), fmt, localtime(&t)); - ff_text_center(can, label, str, - x, XLABEL_H / 2); - } -} - -static void -ff_yaxis(struct canvas *can, struct color *label, struct color *grid, - double vmin, double vmax, double vstep) -{ - double v; - int y; - char str[8 + 1]; - - for (v = vmax - fmod(vmax, vstep); v >= vmin; v -= vstep) { - y = ff_v2y(v, vmin, vmax); - - ff_line(can, grid, - YLABEL_W, y, - YLABEL_W + PLOT_W, y); - - humanize(str, v); - ff_text_right(can, label, str, - YLABEL_W - MARGIN, y); - } -} - -static void -ff_title(struct canvas *can, - struct color *ct, char *title, - struct color *cu, char *unit) -{ - ff_text_left(can, ct, title, TITLE_H / 2, 0); - ff_text_right(can, cu, unit, TITLE_H / 2, TITLE_W); -} - -static void -ff_plot(struct canvas *can, struct vlist *vl, struct color *color, - double vmin, double vmax, - time_t tmin, time_t tmax) -{ - time_t *tp; - double *vp; - int x, y, n, ylast, xlast, first; - - first = 1; - for (tp = vl->t, vp = vl->v, n = vl->n; n > 0; n--, vp++, tp++) { - y = ff_v2y(*vp, vmin, vmax); - x = ff_t2x(*tp, tmin, tmax); - - if (!first) - ff_line(can, color, xlast, ylast, x, y); - - ylast = y; - xlast = x; - first = 0; - } -} - -static void -ff_values(struct canvas *can, struct vlist *vl, struct color **cl, size_t ncol, - time_t tmin, time_t tmax, - double vmin, double vmax) -{ - for (; ncol > 0; ncol--, vl++, cl++) - ff_plot(can, vl, *cl, vmin, vmax, tmin, tmax); -} - -static void -ff_legend(struct canvas *can, struct color *fg, struct vlist *vl, struct color **cl, size_t ncol) -{ - size_t i, x, y; - - for (i = 0; i < ncol; i++, vl++, cl++) { - x = MARGIN * 2; - x = ff_text_left(can, *cl, "\1", x, y) + MARGIN; - x = ff_text_left(can, fg, vl->label, x, y); - y = LEGEND_H - i * (font->height + MARGIN); - } -} - -/* - * Plot the 'n' values list of the 'v' arrax with title 'name' and - * 'units' label. - * - * Title (units) - * x ^ Legend - * label | - + - + - + - + - .... - * here | - + - + - + - + - .... - * +---+---+---+---+--> - * x label here - */ -static void -ff(struct vlist *vl, struct color **cl, size_t ncol, char *name, char *units) -{ - struct canvas can = { IMAGE_W, IMAGE_H, 0, 0, NULL }; - struct color plot_bg = { 0x2222, 0x2222, 0x2222, 0xffff }; - struct color grid_bg = { 0x2929, 0x2929, 0x2929, 0xffff }; - struct color grid_fg = { 0x3737, 0x3737, 0x3737, 0xffff }; - struct color label_fg = { 0x8888, 0x8888, 0x8888, 0xffff }; - struct color title_fg = { 0xdddd, 0xdddd, 0xdddd, 0xffff }; - double vmin, vmax, vstep; - time_t tmin, tmax, tstep; - - scale(vl, ncol, &tmin, &tmax, &tstep, &vmin, &vmax, &vstep); - - assert(can.buf = calloc(IMAGE_H * IMAGE_W, sizeof *can.buf)); - - can.y = 0; - can.x = 0; - ff_rectangle(&can, &plot_bg, 0, 0, IMAGE_H - 1, IMAGE_W - 1); - - can.x = PLOT_X; - can.y = PLOT_Y; - ff_rectangle(&can, &grid_bg, 0, 0, PLOT_H, PLOT_W); - - can.x = XLABEL_X; - can.y = XLABEL_Y; - ff_xaxis(&can, &label_fg, &grid_fg, tmin, tmax, tstep); - - can.x = YLABEL_X; - can.y = YLABEL_Y; - ff_yaxis(&can, &label_fg, &grid_fg, vmin, vmax, vstep); - - can.x = TITLE_X; - can.y = TITLE_Y; - ff_title(&can, &title_fg, name, &label_fg, units); - - can.x = PLOT_X; - can.y = PLOT_Y; - ff_values(&can, vl, cl, ncol, tmin, tmax, vmin, vmax); - - can.x = LEGEND_X; - can.y = LEGEND_Y; - ff_legend(&can, &label_fg, vl, cl, ncol); - - ff_print(&can); -} - -static struct color * -name_to_color(char *name) -{ - struct cname *cn; - - for (cn = cname; cn->name != NULL; cn++) - if (strcmp(name, cn->name) == 0) - return &cn->color; - return NULL; -} - -static void -argv_to_color(struct color **cl, char **argv) -{ - for (; *argv != NULL; cl++, argv++) - if ((*cl = name_to_color(*argv)) == NULL) - err(1, "unknown color name: %s", *argv); -} - -static void -usage(void) -{ - fprintf(stderr, "usage: %s [-t title] [-u unit] {", arg0); - fputs(cname->name, stderr); - for (struct cname *cn = cname + 1; cn->name != NULL; cn++) - fprintf(stderr, ",%s", cn->name); - fputs("}...\n", stderr); - exit(1); -} - -int -main(int argc, char **argv) -{ - struct vlist *vl; - struct color **cl; - char labels[LINE_MAX]; - - ARG_SWITCH(argc, argv) { - case 't': - tflag = ARG; - break; - case 'u': - uflag = ARG; - break; - default: - usage(); - } - - if (argc == 0) - usage(); - - assert(vl = calloc(argc, sizeof(*vl))); - assert(cl = calloc(argc, sizeof(*cl))); - - csv_labels(vl, argv, labels); - csv_values(vl, argc); - argv_to_color(cl, argv); - - ff(vl, cl, argc, tflag, uflag); - - free(vl); - free(cl); - return 0; -}