tploot-ff.c - ploot - simple plotting tools | |
git clone git://bitreich.org/ploot git://hg6vgqziawt5s4dj.onion/ploot | |
Log | |
Files | |
Refs | |
Tags | |
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… | |
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*5… | |
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 int… | |
207 * width * x + y. | |
208 * The coordinates are shifted by offx and offy to permit relative coord… | |
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-… | |
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), … | |
530 else if (color(&v->col, *argv) == -1) | |
531 fprintf(stderr, "unknown color: %s\n", *argv), e… | |
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))) == N… | |
544 perror("reallocating values buffer"), exit(1); | |
545 if ((v->t = realloc(v->t, bufsize * sizeof(*v->t))) == N… | |
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), … | |
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 } |