tploot-ff.c - ploot - simple plotting tools
(HTM) git clone git://bitreich.org/ploot git://hg6vgqziawt5s4dj.onion/ploot
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) Tags
(DIR) README
---
tploot-ff.c (13227B)
---
1 #include <arpa/inet.h>
2
3 #include <math.h>
4 #include <stdint.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <time.h>
9 #include <time.h>
10 #include <stdlib.h>
11 #include <stdio.h>
12 #include <fcntl.h>
13 #include <limits.h>
14 #include <string.h>
15 #include <ctype.h>
16 #include <time.h>
17 #include <stdint.h>
18
19 #include "arg.h"
20 #include "util.h"
21 #include "font.h"
22
23 #define MARGIN 4
24
25 #define XDENSITY 7 /* nb of values on x axis */
26 #define YDENSITY 7 /* nb of values on y axis */
27
28 #define TITLE_X (IMAGE_H - TITLE_H)
29 #define TITLE_Y (XLABEL_W)
30 #define TITLE_H (FONT_H * 2)
31 #define TITLE_W (PLOT_W)
32
33 #define XLABEL_X (PLOT_X)
34 #define XLABEL_Y (0)
35 #define XLABEL_H (PLOT_H)
36 #define XLABEL_W (FONT_W * 9 + MARGIN)
37
38 #define YLABEL_X (0)
39 #define YLABEL_Y (PLOT_Y)
40 #define YLABEL_H (FONT_H * 2)
41 #define YLABEL_W (PLOT_W)
42
43 #define PLOT_X (YLABEL_H)
44 #define PLOT_Y (XLABEL_W)
45 #define PLOT_W 700
46 #define PLOT_H 160
47
48 #define LEGEND_X (YLABEL_H)
49 #define LEGEND_Y (IMAGE_W - LEGEND_W)
50 #define LEGEND_W (FONT_W + 150 + FONT_W)
51 #define LEGEND_H (PLOT_H)
52
53 #define IMAGE_H (TITLE_H + PLOT_H + YLABEL_H)
54 #define IMAGE_W (XLABEL_W + PLOT_W + LEGEND_W)
55
56 typedef uint16_t Color[4];
57 typedef struct clist Clist;
58 typedef struct vlist Vlist;
59 typedef struct canvas Canvas;
60 typedef struct font Font;
61
62 struct vlist {
63 Color col; /* color to use to draw the line */
64 time_t *t; /* array of timestamps */
65 double *v; /* array of values */
66 int n; /* number of values */
67 char *label; /* for the legend */
68 };
69
70 struct canvas {
71 int w; /* width */
72 int h; /* height */
73 int x; /* x offset */
74 int y; /* x offset */
75 Color b[IMAGE_W * IMAGE_H];
76 };
77
78 struct font {
79 int w; /* width */
80 int h; /* height */
81 char **b; /* buffer */
82 };
83
84 struct clist {
85 char *name;
86 Color col;
87 };
88
89 char *argv0;
90 char *tflag = "";
91 char *uflag = "";
92
93 Clist clist[] = {
94 /* name red green blue alpha */
95 { "red", { 0xffff, 0x4444, 0x4444, 0xffff } },
96 { "orange", { 0xffff, 0x9999, 0x4444, 0xffff } },
97 { "yellow", { 0xffff, 0xffff, 0x4444, 0xffff } },
98 { "green", { 0x2222, 0xffff, 0x5555, 0xffff } },
99 { "cyan", { 0x0000, 0xffff, 0xdddd, 0xffff } },
100 { "blue", { 0x2222, 0x9999, 0xffff, 0xffff } },
101 { NULL, { 0, 0, 0, 0 } }
102 };
103
104 Font font = { FONT_W, FONT_H, glyph };
105
106 static int
107 color(Color *col, char *name)
108 {
109 Clist *c;
110
111 for (c = clist; c->name != NULL; c++) {
112 if (strcmp(name, c->name) == 0) {
113 memcpy(col, c->col, sizeof(*col));
114 return 0;
115 }
116 }
117
118 return -1;
119 }
120
121 static void
122 scale_minmax(Vlist *v, int n,
123 double *vmin, double *vmax,
124 time_t *tmin, time_t *tmax)
125 {
126 int i;
127
128 *vmin = *vmax = 0;
129 *tmin = *tmax = *v->t;
130
131 for (; n-- > 0; v++) {
132 for (i = 0; i < v->n; i++) {
133 if (v->v[i] < *vmin)
134 *vmin = v->v[i];
135 if (v->v[i] > *vmax)
136 *vmax = v->v[i];
137 if (v->t[i] < *tmin)
138 *tmin = v->t[i];
139 if (v->t[i] > *tmax)
140 *tmax = v->t[i];
141 }
142 }
143 }
144
145 static void
146 scale_tstep(time_t *step, int density, time_t min, time_t max)
147 {
148 time_t dt, *s, scale[] = {
149 1, 5, 2, 10, 20, 30, 60, 60*2, 60*5, 60*10, 60*20, 60*30, 3600,
150 3600*2, 3600*5, 3600*10, 3600*18, 3600*24, 3600*24*2,
151 3600*24*5, 3600*24*10, 3600*24*20, 3600*24*30, 3600*24*50,
152 3600*24*100, 3600*24*365
153 };
154
155 dt = max - min;
156
157 for (s = scale; s < scale + LEN(scale); s++) {
158 if (dt < *s * density) {
159 *step = *s;
160 break;
161 }
162 }
163 }
164
165 static void
166 scale_vstep(double *step, int density, double min, double max)
167 {
168 double dv, *s, scale[] = { 1, 2, 3, 5 };
169 int i;
170
171 dv = max - min;
172
173 if (dv > 1) {
174 for (i = 1; i != 0; i *= 10) {
175 for (s = scale; s < scale + LEN(scale); s++) {
176 if (dv < *s * i * density) {
177 *step = *s * i;
178 return;
179 }
180 }
181 }
182 } else {
183 for (i = 1; i != 0; i *= 10) {
184 for (s = scale + LEN(scale) - 1; s >= scale; s--) {
185 if (dv > *s / i * density / 2) {
186 *step = *s / i;
187 return;
188 }
189 }
190 }
191 }
192 }
193
194 static void
195 scale(Vlist *v, int n,
196 double *vmin, double *vmax, double *vstep,
197 time_t *tmin, time_t *tmax, time_t *tstep)
198 {
199 scale_minmax(v, n, vmin, vmax, tmin, tmax);
200 scale_tstep(tstep, YDENSITY, *tmin, *tmax);
201 scale_vstep(vstep, XDENSITY, *vmin, *vmax);
202 }
203
204 /*
205 * Convert (x,y) coordinates to (row,col) for printing into the buffer.
206 * The buffer only contain one number, so the coordinate is a single integer:
207 * width * x + y.
208 * The coordinates are shifted by offx and offy to permit relative coordinates.
209 *
210 * The convention used: y
211 * - (0,0) is at the lower left corner of the canvas. |
212 * - (0,1) is above it. +--x
213 */
214 static void
215 ff_pixel(Canvas *can, Color *col,
216 int x, int y)
217 {
218 x += can->x;
219 y += can->y;
220 if (x < 0 || x >= can->h || y < 0 || y >= can->w)
221 return;
222 memcpy(can->b + can->w * (can->h - 1 - x) + y, col, sizeof(*can->b));
223 }
224
225 static void
226 ff_rectangle(Canvas *can, Color *col,
227 int x1, int y1,
228 int x2, int y2)
229 {
230 int x, y, xmin, ymin, xmax, ymax;
231
232 xmin = MIN(x1, x2); xmax = MAX(x1, x2);
233 ymin = MIN(y1, y2); ymax = MAX(y1, y2);
234
235 for (x = xmin; x <= xmax; x++)
236 for (y = ymin; y <= ymax; y++)
237 ff_pixel(can, col, x, y);
238 }
239
240 /*
241 * From Bresenham's line algorithm and dcat's tplot.
242 */
243 static void
244 ff_line(Canvas *can, Color *col,
245 int x0, int y0,
246 int x1, int y1)
247 {
248 int dx, dy, sx, sy, err, e;
249
250 sx = x0 < x1 ? 1 : -1;
251 sy = y0 < y1 ? 1 : -1;
252 dx = abs(x1 - x0);
253 dy = abs(y1 - y0);
254 err = (dx > dy ? dx : -dy) / 2;
255
256 for (;;) {
257 ff_pixel(can, col, x0, y0);
258
259 if (x0 == x1 && y0 == y1)
260 break;
261
262 e = err;
263 if (e > -dx) {
264 x0 += sx;
265 err -= dy;
266 }
267 if (e < dy) {
268 y0 += sy;
269 err += dx;
270 }
271 }
272 }
273
274 /*
275 * Draw a coloured glyph from font f centered on x.
276 */
277 static void
278 ff_char(Canvas *can, Color *col, char c, Font *f,
279 int x, int y)
280 {
281 int xf, yf;
282
283 if (c & 0x80)
284 c = '\0';
285
286
287 x -= f->h / 2;
288
289 for (xf = 0; xf < f->h; xf++)
290 for (yf = 0; yf < f->w; yf++)
291 if (f->b[(int)c][f->w * (f->h - xf) + yf] == 1)
292 ff_pixel(can, col, x + xf, y + yf);
293 }
294
295 /*
296 * Draw a left aligned string without wrapping it.
297 */
298 static void
299 ff_str_left(Canvas *can, Color *col, char *s, Font *f,
300 int x, int y)
301 {
302 for (; *s != '\0'; y += f->w, s++)
303 ff_char(can, col, *s, f, x, y);
304 }
305
306 /*
307 * Draw a center aligned string without wrapping it.
308 */
309 static void
310 ff_str_center(Canvas *can, Color *col, char *s, Font *f,
311 int x, int y)
312 {
313 y -= f->w * strlen(s) / 2;
314 ff_str_left(can, col, s, f, x, y);
315 }
316
317 /*
318 * Draw a right aligned string without wrapping it.
319 */
320 static void
321 ff_str_right(Canvas *can, Color *col, char *s, Font *f,
322 int x, int y)
323 {
324 y -= f->w * strlen(s);
325 ff_str_left(can, col, s, f, x, y);
326 }
327
328 static void
329 ff_print(Canvas *can)
330 {
331 uint32_t w, h;
332
333 w = htonl(can->w);
334 h = htonl(can->h);
335
336 fputs("farbfeld", stdout);
337 fwrite(&w, sizeof(w), 1, stdout);
338 fwrite(&h, sizeof(h), 1, stdout);
339 fwrite(can->b, can->w * can->h, sizeof(*can->b), stdout);
340 }
341
342 static int
343 ff_t2y(time_t t, time_t tmin, time_t tmax)
344 {
345 return (t - tmin) * PLOT_W / (tmax - tmin);
346 }
347
348 static int
349 ff_v2x(double v, double vmin, double vmax)
350 {
351 return (v - vmin) * PLOT_H / (vmax - vmin);
352 }
353
354 static void
355 ff_xaxis(Canvas *can, Color *label, Color *grid,
356 double vmin, double vmax, double vstep)
357 {
358 double v;
359 int x;
360 char str[8 + 1];
361
362 for (v = vmax - fmod(vmax, vstep); v >= vmin; v -= vstep) {
363 x = ff_v2x(v, vmin, vmax);
364
365 ff_line(can, grid,
366 x, XLABEL_W,
367 x, XLABEL_W + PLOT_W);
368
369 humanize(str, v);
370 ff_str_right(can, label, str, &font,
371 x, XLABEL_W - MARGIN);
372 }
373 }
374
375 static void
376 ff_yaxis(Canvas *can, Color *label, Color *grid,
377 time_t tmin, time_t tmax, time_t tstep)
378 {
379 time_t t;
380 int y;
381 char str[sizeof("MM/DD HH/MM")], *fmt;
382
383 if (tstep < 3600 * 12)
384 fmt = "%H:%M:%S";
385 else if (tstep < 3600 * 24)
386 fmt = "%m/%d %H:%M";
387 else
388 fmt = "%Y/%m/%d";
389
390 for (t = tmax - tmax % tstep; t >= tmin; t -= tstep) {
391 y = ff_t2y(t, tmin, tmax);
392
393 ff_line(can, grid,
394 YLABEL_H, y,
395 YLABEL_H + PLOT_H, y);
396
397 strftime(str, sizeof(str), fmt, localtime(&t));
398 ff_str_center(can, label, str, &font,
399 YLABEL_H / 2, y);
400 }
401 }
402
403 static void
404 ff_title(Canvas *can,
405 Color *ct, char *title,
406 Color *cu, char *unit)
407 {
408 ff_str_left(can, ct, title, &font,
409 TITLE_H / 2, 0);
410 ff_str_right(can, cu, unit, &font,
411 TITLE_H / 2, TITLE_W);
412 }
413
414 static void
415 ff_plot(Canvas *can, Vlist *v,
416 double vmin, double vmax,
417 time_t tmin, time_t tmax)
418 {
419 time_t *tp;
420 double *vp;
421 int x, y, n, xlast, ylast, first;
422
423 first = 1;
424 for (tp = v->t, vp = v->v, n = v->n; n > 0; n--, vp++, tp++) {
425 x = ff_v2x(*vp, vmin, vmax);
426 y = ff_t2y(*tp, tmin, tmax);
427
428 if (!first)
429 ff_line(can, &v->col, xlast, ylast, x, y);
430
431 xlast = x;
432 ylast = y;
433 first = 0;
434 }
435 }
436
437 static void
438 ff_values(Canvas *can, Vlist *v, int n,
439 double vmin, double vmax,
440 time_t tmin, time_t tmax)
441 {
442 for (; n > 0; n--, v++)
443 ff_plot(can, v, vmin, vmax, tmin, tmax);
444 }
445
446 static void
447 ff_legend(Canvas *can, Color *label_fg, Vlist *v, int n)
448 {
449 int i, x, y;
450
451 for (i = 0; i < n; i++, v++) {
452 x = LEGEND_H - i * (FONT_H + MARGIN) - FONT_H / 2;
453
454 y = MARGIN + FONT_W;
455 ff_str_left(can, &v->col, "\1", &font, x, y);
456
457 y += FONT_W * 2;
458 ff_str_left(can, label_fg, v->label, &font, x, y);
459 }
460 }
461
462 /*
463 * Plot the 'n' values list of the 'v' array with title 'name' and
464 * 'units' label.
465 *
466 * Title (units)
467 * y ^ Legend
468 * label |- + - + - + - + - ....
469 * here |- + - + - + - + - ....
470 * +--+---+---+---+-->
471 * x label here
472 */
473 static void
474 ff(Vlist *v, int n, char *name, char *units)
475 {
476 Canvas can = { IMAGE_W, IMAGE_H, 0, 0, { { 0 }, { 0 } } };
477 Color plot_bg = { 0x2222, 0x2222, 0x2222, 0xffff };
478 Color grid_bg = { 0x2929, 0x2929, 0x2929, 0xffff };
479 Color grid_fg = { 0x3737, 0x3737, 0x3737, 0xffff };
480 Color label_fg = { 0x8888, 0x8888, 0x8888, 0xffff };
481 Color title_fg = { 0xdddd, 0xdddd, 0xdddd, 0xffff };
482 double vmin, vmax, vstep;
483 time_t tmin, tmax, tstep;
484
485 scale(v, n, &vmin, &vmax, &vstep, &tmin, &tmax, &tstep);
486
487 can.x = 0;
488 can.y = 0;
489 ff_rectangle(&can, &plot_bg, 0, 0, IMAGE_H - 1, IMAGE_W - 1);
490
491 can.x = PLOT_X;
492 can.y = PLOT_Y;
493 ff_rectangle(&can, &grid_bg, 0, 0, PLOT_H, PLOT_W);
494
495 can.x = YLABEL_X;
496 can.y = YLABEL_Y;
497 ff_yaxis(&can, &label_fg, &grid_fg, tmin, tmax, tstep);
498
499 can.x = XLABEL_X;
500 can.y = XLABEL_Y;
501 ff_xaxis(&can, &label_fg, &grid_fg, vmin, vmax, vstep);
502
503 can.x = TITLE_X;
504 can.y = TITLE_Y;
505 ff_title(&can, &title_fg, name, &label_fg, units);
506
507 can.x = PLOT_X;
508 can.y = PLOT_Y;
509 ff_values(&can, v, n, vmin, vmax, tmin, tmax);
510
511 can.x = LEGEND_X;
512 can.y = LEGEND_Y;
513 ff_legend(&can, &label_fg, v, n);
514
515 ff_print(&can);
516 }
517
518 static void
519 csv_labels(Vlist *v, char **argv, char *buf)
520 {
521 if (esfgets(buf, LINE_MAX, stdin) == NULL)
522 fputs("missing label line\n", stderr), exit(1);
523
524 if (strcmp(strsep(&buf, ","), "epoch") != 0)
525 fputs("first label must be \"epoch\"\n", stderr), exit(1);
526
527 for (; *argv != NULL; v++, argv++) {
528 if ((v->label = strsep(&buf, ",")) == NULL)
529 fputs("more arguments than columns\n", stderr), exit(1);
530 else if (color(&v->col, *argv) == -1)
531 fprintf(stderr, "unknown color: %s\n", *argv), exit(1);
532 }
533
534 if (strsep(&buf, ",") != NULL)
535 fputs("more columns than arguments\n", stderr), exit(1);
536 }
537
538 static int
539 csv_addval(Vlist *v, int bufsize, int nval, double field, time_t epoch)
540 {
541 if (nval >= bufsize) {
542 bufsize = bufsize * 2 + 1;
543 if ((v->v = realloc(v->v, bufsize * sizeof(*v->v))) == NULL)
544 perror("reallocating values buffer"), exit(1);
545 if ((v->t = realloc(v->t, bufsize * sizeof(*v->t))) == NULL)
546 perror("reallocating values buffer"), exit(1);
547 }
548 v->v[nval] = field;
549 v->t[nval] = epoch;
550 v->n = nval + 1;
551
552 return bufsize;
553 }
554
555 /*
556 * Add to each column the value on the current row.
557 */
558 static int
559 csv_addrow(Vlist *v, int bufsize, int ncol, int nval, char *line)
560 {
561 time_t epoch;
562 int bs;
563 char *field, *dot;
564
565 if ((field = strsep(&line, ",")) == NULL)
566 fprintf(stderr, "%d: missing epoch\n", nval), exit(1);
567
568 if ((dot = strchr(field, '.')) != NULL)
569 *dot = '\0';
570 epoch = eatol(field);
571 for (; (field = strsep(&line, ",")) != NULL; ncol--, v++) {
572 if (ncol <= 0)
573 fprintf(stderr, "%d: too many fields\n", nval), exit(1);
574 bs = csv_addval(v, bufsize, nval, eatof(field), epoch);
575 }
576 if (ncol > 0)
577 fprintf(stderr, "%d: too few fields\n", nval), exit(1);
578
579 return bs;
580 }
581
582 /*
583 * < ncol >
584 * epoch,a1,b1,c1 ^
585 * epoch,a2,b2,c2 nval
586 * epoch,a3,b3,c3 v
587 */
588 static void
589 csv_values(Vlist *v, int ncol)
590 {
591 int nval, bufsize;
592 char line[LINE_MAX];
593
594 bufsize = 0;
595 for (nval = 0; esfgets(line, sizeof(line), stdin) != NULL; nval++)
596 bufsize = csv_addrow(v, bufsize, ncol, nval, line);
597 if (nval == 0)
598 fputs("no value could be read\n", stderr), exit(1);
599 }
600
601 static void
602 usage(void)
603 {
604 Clist *c;
605
606 fprintf(stderr, "usage: %s [-t title] [-u unit] {", argv0);
607 fputs(clist->name, stderr);
608 for (c = clist + 1; c->name != NULL; c++)
609 fprintf(stderr, ",%s", c->name);
610 fputs("}...\n", stderr);
611 exit(1);
612 }
613
614 int
615 main(int argc, char **argv)
616 {
617 Vlist *v;
618 char labels[LINE_MAX];
619
620 ARGBEGIN {
621 case 't':
622 tflag = EARGF(usage());
623 break;
624 case 'u':
625 uflag = EARGF(usage());
626 break;
627 default:
628 usage();
629 } ARGEND;
630
631 if ((v = calloc(argc, sizeof(*v))) == NULL)
632 perror("calloc value list"), exit(1);
633
634 csv_labels(v, argv, labels);
635 csv_values(v, argc);
636
637 ff(v, argc, tflag, uflag);
638
639 return 0;
640 }