tploot-ff.c - ploot - simple plotting tools HTML 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 }