ploot-farbfeld.c - ploot - simple plotting tools
(HTM) git clone git://bitreich.org/ploot git://enlrupgkhuxnvlhsf6lc3fziv5h2hhfrinws65d7roiv6bfj7d652fid.onion/ploot
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Tags
(DIR) README
(DIR) LICENSE
---
ploot-farbfeld.c (10488B)
---
1 #include <arpa/inet.h>
2 #include <ctype.h>
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <limits.h>
6 #include <math.h>
7 #include <stddef.h>
8 #include <stdint.h>
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <time.h>
13 #include <unistd.h>
14 #include "tsv.h"
15 #include "font.h"
16 #include "util.h"
17
18 #ifndef __OpenBSD__
19 #define pledge(...) 0
20 #endif
21
22 #define MARGIN 8
23
24 #define IMAGE_H (TITLE_H + PLOT_H + XLABEL_H)
25 #define IMAGE_W (MARGIN + YLABEL_W + PLOT_W + MARGIN)
26
27 #define TITLE_X (MARGIN)
28 #define TITLE_Y (IMAGE_H - TITLE_H / 2)
29 #define TITLE_H ((font)->height * 2)
30 #define TITLE_W (PLOT_W)
31
32 #define YLABEL_X (MARGIN)
33 #define YLABEL_Y (PLOT_Y)
34 #define YLABEL_H (PLOT_H)
35 #define YLABEL_W (40 + MARGIN)
36
37 #define XLABEL_X (PLOT_X)
38 #define XLABEL_Y (0)
39 #define XLABEL_H ((font)->height * 2)
40 #define XLABEL_W (PLOT_W)
41
42 #define PLOT_X (YLABEL_X + YLABEL_W)
43 #define PLOT_Y (XLABEL_H)
44 #define PLOT_W (700)
45 #define PLOT_H (160)
46
47 #define LEGEND_X (IMAGE_W / 2)
48 #define LEGEND_Y (TITLE_Y)
49 #define LEGEND_H (PLOT_H)
50
51 struct ffcolor {
52 uint16_t red;
53 uint16_t green;
54 uint16_t blue;
55 uint16_t alpha;
56 };
57
58 struct ffplot {
59 int w, h, x, y; /* width, height and coordinamtes */
60 struct ffcolor *buf;
61 };
62
63 static struct colorname {
64 char *name;
65 struct ffcolor color;
66 } colorname[] = {
67 /* name red green blue alpha */
68 { "red", { 0xffff, 0x4444, 0x4444, 0xffff } },
69 { "orange", { 0xffff, 0x9999, 0x4444, 0xffff } },
70 { "yellow", { 0xffff, 0xffff, 0x4444, 0xffff } },
71 { "green", { 0x2222, 0xffff, 0x5555, 0xffff } },
72 { "cyan", { 0x0000, 0xffff, 0xdddd, 0xffff } },
73 { "blue", { 0x2222, 0x9999, 0xffff, 0xffff } },
74 { NULL, { 0, 0, 0, 0 } }
75 };
76
77 static char *flag_title = "";
78 static struct font *font = &font13;
79
80 /*
81 * Convert (x,y) coordinates to (row,col) for printing into the buffer.
82 * The buffer only contain one number, so the coordinate is a single integer:
83 * width * y + y.
84 * The coordinates are shifted by offx and offy to permit relative coordinates.
85 *
86 * The convention used: y
87 * - (0,0) is at the lower left corner of the plotvas. |
88 * - (0,1) is above it. +--x
89 */
90 static void
91 ffplot_pixel(struct ffplot *plot, struct ffcolor *color,
92 int x, int y)
93 {
94 x += plot->x;
95 y += plot->y;
96 if (x < 0 || x >= plot->w || y < 0 || y >= plot->h)
97 return;
98 memcpy(plot->buf + plot->w * (plot->h - 1 - y) + x, color, sizeof(*plot->buf));
99 }
100
101 static void
102 ffplot_rectangle(struct ffplot *plot, struct ffcolor *color,
103 int y1, int x1,
104 int y2, int x2)
105 {
106 int x, y, ymin, xmin, ymax, xmax;
107
108 ymin = MIN(y1, y2); ymax = MAX(y1, y2);
109 xmin = MIN(x1, x2); xmax = MAX(x1, x2);
110
111 for (y = ymin; y <= ymax; y++)
112 for (x = xmin; x <= xmax; x++)
113 ffplot_pixel(plot, color, x, y);
114 }
115
116 /*
117 * From Bresenham's line algorithm and dcat's tplot.
118 */
119 static void
120 ffplot_line(struct ffplot *plot, struct ffcolor *color,
121 int x0, int y0,
122 int x1, int y1)
123 {
124 int dy, dx, sy, sx, err, e;
125
126 sx = x0 < x1 ? 1 : -1;
127 sy = y0 < y1 ? 1 : -1;
128 dx = ABS(x1 - x0);
129 dy = ABS(y1 - y0);
130 err = (dy > dx ? dy : -dx) / 2;
131
132 for (;;) {
133 ffplot_pixel(plot, color, x0, y0);
134
135 if (y0 == y1 && x0 == x1)
136 break;
137
138 e = err;
139 if (e > -dy) {
140 y0 += sy;
141 err -= dx;
142 }
143 if (e < dx) {
144 x0 += sx;
145 err += dy;
146 }
147 }
148 }
149
150 /*
151 * Draw a coloured glyph from font f centered on y.
152 */
153 static int
154 ffplot_char(struct ffplot *plot, struct ffcolor *color, struct font *ft, char c,
155 int x, int y)
156 {
157 int yf, xf, wf;
158
159 if (c & 0x80)
160 c = '\0';
161 y -= ft->height / 2;
162 wf = font_width(ft, c);
163 for (xf = 0; xf < wf; xf++)
164 for (yf = 0; yf < ft->height; yf++)
165 if (ft->glyph[(int)c][wf * (ft->height - yf) + xf] == 3)
166 ffplot_pixel(plot, color, x + xf, y + yf);
167 return wf + 1;
168 }
169
170 /*
171 * Draw a left aligned string without wrapping it.
172 */
173 static size_t
174 ffplot_text_left(struct ffplot *plot, struct ffcolor *color, struct font *ft,
175 char *s, int x, int y)
176 {
177 for (; *s != '\0'; s++)
178 x += ffplot_char(plot, color, ft, *s, x, y);
179 return x;
180 }
181
182 /*
183 * Draw a center aligned string without wrapping it.
184 */
185 static size_t
186 ffplot_text_center(struct ffplot *plot, struct ffcolor *color, struct font *ft,
187 char *s, int x, int y)
188 {
189 x -= font_strlen(ft, s) / 2;
190 return ffplot_text_left(plot, color, ft, s, x, y);
191 }
192
193 /*
194 * Draw a right aligned string without wrapping it.
195 */
196 static size_t
197 ffplot_text_right(struct ffplot *plot, struct ffcolor *color, struct font *ft,
198 char *s, int x, int y)
199 {
200 x -= font_strlen(ft, s);
201 return ffplot_text_left(plot, color, ft, s, x, y);
202 }
203
204 static void
205 ffplot_print(FILE *fp, struct ffplot *plot)
206 {
207 uint32_t w, h;
208
209 w = htonl(plot->w);
210 h = htonl(plot->h);
211
212 fprintf(stdout, "farbfeld");
213 fwrite(&w, sizeof(w), 1, fp);
214 fwrite(&h, sizeof(h), 1, fp);
215 fwrite(plot->buf, plot->w * plot->h, sizeof(*plot->buf), fp);
216 }
217
218 static int
219 ffplot_t2x(time_t t, time_t tmin, time_t tmax)
220 {
221 if (tmin == tmax)
222 return PLOT_W;
223 return (t - tmin) * PLOT_W / (tmax - tmin);
224 }
225
226 static int
227 ffplot_v2y(double v, double vmin, double vmax)
228 {
229 if (vmin == vmax)
230 return PLOT_H;
231 return (v - vmin) * PLOT_H / (vmax - vmin);
232 }
233
234 static void
235 ffplot_xaxis(struct ffplot *plot, struct ffcolor *label, struct ffcolor *grid,
236 time_t tmin, time_t tmax, time_t tstep)
237 {
238 time_t t;
239 int x;
240 char str[sizeof("MM/DD HH/MM")], *fmt;
241
242 if (tstep < 3600 * 12)
243 fmt = "%H:%M:%S";
244 else if (tstep < 3600 * 24)
245 fmt = "%m/%d %H:%M";
246 else
247 fmt = "%X/%m/%d";
248
249 for (t = tmax - tmax % tstep; t >= tmin; t -= tstep) {
250 x = ffplot_t2x(t, tmin, tmax);
251
252 ffplot_line(plot, grid,
253 x, XLABEL_H,
254 x, XLABEL_H + PLOT_H);
255
256 strftime(str, sizeof(str), fmt, localtime(&t));
257 ffplot_text_center(plot, label, font, str,
258 x, XLABEL_H / 2);
259 }
260 }
261
262 static void
263 ffplot_yaxis(struct ffplot *plot, struct ffcolor *label, struct ffcolor *grid,
264 double vmin, double vmax, double vstep)
265 {
266 double v;
267 int y;
268 char str[8 + 1];
269
270 for (v = vmax - fmod(vmax, vstep); v >= vmin; v -= vstep) {
271 y = ffplot_v2y(v, vmin, vmax);
272
273 ffplot_line(plot, grid,
274 YLABEL_W, y,
275 YLABEL_W + PLOT_W, y);
276
277 humanize(str, v);
278 ffplot_text_right(plot, label, font, str,
279 YLABEL_W - MARGIN, y);
280 }
281 }
282
283 static void
284 ffplot_title(struct ffplot *plot, struct ffcolor *ct, char *title)
285 {
286 ffplot_text_left(plot, ct, font, title, TITLE_H / 2, 0);
287 }
288
289 static void
290 ffplot_plot(struct ffplot *plot, struct tsv *vl, struct ffcolor *color,
291 double vmin, double vmax,
292 time_t tmin, time_t tmax)
293 {
294 time_t *tp;
295 double *vp;
296 int x, y, n, ylast, xlast, first;
297
298 first = 1;
299 for (tp = vl->t, vp = vl->v, n = vl->n; n > 0; n--, vp++, tp++) {
300 y = ffplot_v2y(*vp, vmin, vmax);
301 x = ffplot_t2x(*tp, tmin, tmax);
302
303 if (!first)
304 ffplot_line(plot, color, xlast, ylast, x, y);
305
306 ylast = y;
307 xlast = x;
308 first = 0;
309 }
310 }
311
312 static void
313 ffplot_values(struct ffplot *plot, struct tsv *vl, struct ffcolor **cl, size_t ncol,
314 time_t tmin, time_t tmax,
315 double vmin, double vmax)
316 {
317 for (; ncol > 0; ncol--, vl++, cl++)
318 ffplot_plot(plot, vl, *cl, vmin, vmax, tmin, tmax);
319 }
320
321 static void
322 ffplot_legend(struct ffplot *plot, struct ffcolor *fg, struct tsv *vl, struct ffcolor **cl, size_t ncol)
323 {
324 size_t x, y;
325
326 x = y = 0;
327 for (; ncol > 0; ncol--, vl++, cl++) {
328 x = ffplot_text_left(plot, *cl, font, "-", x, y) + MARGIN;
329 x = ffplot_text_left(plot, fg, font, vl->label, x, y);
330 x = ffplot_text_left(plot, fg, font, " ", x, y);
331 }
332 }
333
334 /*
335 * Plot the 'n' values list of the 'v' arrax with title 'name' label.
336 *
337 * Title Legend
338 * x ^
339 * label | - + - + - + - + -
340 * here | - + - + - + - + -
341 * +---+---+---+---+-->
342 * x label here
343 */
344 static void
345 plot(struct tsv *vl, struct ffcolor **cl, size_t ncol, char *name)
346 {
347 struct ffplot plot = { IMAGE_W, IMAGE_H, 0, 0, NULL };
348 struct ffcolor plot_bg = { 0x2222, 0x2222, 0x2222, 0xffff };
349 struct ffcolor grid_bg = { 0x2929, 0x2929, 0x2929, 0xffff };
350 struct ffcolor grid_fg = { 0x3737, 0x3737, 0x3737, 0xffff };
351 struct ffcolor label_fg = { 0x8888, 0x8888, 0x8888, 0xffff };
352 struct ffcolor title_fg = { 0xdddd, 0xdddd, 0xdddd, 0xffff };
353 double vmin, vmax, vstep;
354 time_t tmin, tmax, tstep;
355
356 tsv_min_max(vl, ncol, &tmin, &tmax, &vmin, &vmax);
357 tstep = scale_time_t(tmin, tmax, 7);
358 vstep = scale_double(vmin, vmax, 7);
359
360 if ((plot.buf = calloc(IMAGE_H * IMAGE_W, sizeof *plot.buf)) == NULL)
361 err(1, "calloc: %s", strerror(errno));
362
363 plot.y = 0;
364 plot.x = 0;
365 ffplot_rectangle(&plot, &plot_bg, 0, 0, IMAGE_H - 1, IMAGE_W - 1);
366
367 plot.x = PLOT_X;
368 plot.y = PLOT_Y;
369 ffplot_rectangle(&plot, &grid_bg, 0, 0, PLOT_H, PLOT_W);
370
371 plot.x = XLABEL_X;
372 plot.y = XLABEL_Y;
373 ffplot_xaxis(&plot, &label_fg, &grid_fg, tmin, tmax, tstep);
374
375 plot.x = YLABEL_X;
376 plot.y = YLABEL_Y;
377 ffplot_yaxis(&plot, &label_fg, &grid_fg, vmin, vmax, vstep);
378
379 plot.x = TITLE_X;
380 plot.y = TITLE_Y;
381 ffplot_title(&plot, &title_fg, name);
382
383 plot.x = PLOT_X;
384 plot.y = PLOT_Y;
385 ffplot_values(&plot, vl, cl, ncol, tmin, tmax, vmin, vmax);
386
387 plot.x = LEGEND_X;
388 plot.y = LEGEND_Y;
389 ffplot_legend(&plot, &label_fg, vl, cl, ncol);
390
391 ffplot_print(stdout, &plot);
392 }
393
394 static struct ffcolor *
395 name_to_color(char *name)
396 {
397 struct colorname *cn;
398
399 for (cn = colorname; cn->name != NULL; cn++)
400 if (strcmp(name, cn->name) == 0)
401 return &cn->color;
402 return NULL;
403 }
404
405 static void
406 argv_to_color(struct ffcolor **cl, char **argv)
407 {
408 for (; *argv != NULL; cl++, argv++)
409 if ((*cl = name_to_color(*argv)) == NULL)
410 err(1, "unknown color name: %s", *argv);
411 }
412
413 static void
414 usage(void)
415 {
416 fprintf(stderr, "usage: %s [-t title] {", arg0);
417 fputs(colorname->name, stderr);
418 for (struct colorname *cn = colorname + 1; cn->name != NULL; cn++)
419 fprintf(stderr, ",%s", cn->name);
420 fputs("}...\n", stderr);
421 exit(1);
422 }
423
424 int
425 main(int argc, char **argv)
426 {
427 struct tsv *vl;
428 struct ffcolor **cl;
429 size_t ncol;
430 int c;
431
432 if (pledge("stdio", "") < 0)
433 err(1, "pledge: %s", strerror(errno));
434
435 arg0 = *argv;
436 while ((c = getopt(argc, argv, "t:")) > -1) {
437 switch (c) {
438 case 't':
439 flag_title = optarg;
440 break;
441 default:
442 usage();
443 }
444 }
445 argc -= optind;
446 argv += optind;
447
448 if (argc == 0)
449 usage();
450
451 if ((cl = calloc(argc, sizeof *cl)) == NULL)
452 err(1, "calloc: %s", strerror(errno));
453
454 tsv_labels(stdin, &vl, &ncol);
455 if (ncol > (size_t)argc)
456 err(1, "too many columns or not enough arguments");
457 else if (ncol < (size_t)argc)
458 err(1, "too many arguments or not enough columns");
459 tsv_values(stdin, vl, ncol);
460 argv_to_color(cl, argv);
461
462 plot(vl, cl, argc, flag_title);
463
464 free(vl);
465 free(cl);
466 return 0;
467 }