gfio: right justify y-axis labels
[fio.git] / graph.c
... / ...
CommitLineData
1/*
2 * gfio - gui front end for fio - the flexible io tester
3 *
4 * Copyright (C) 2012 Stephen M. Cameron <stephenmcameron@gmail.com>
5 *
6 * The license below covers all files distributed with fio unless otherwise
7 * noted in the file itself.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 2 as
11 * published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 *
22 */
23#include <string.h>
24#include <malloc.h>
25#include <math.h>
26#include <assert.h>
27
28#include <cairo.h>
29#include <gtk/gtk.h>
30
31#include "tickmarks.h"
32
33struct xyvalue {
34 double x, y;
35};
36
37struct graph_value {
38 struct graph_value *next;
39 void *value;
40};
41
42struct graph_label {
43 char *label;
44 struct graph_value *tail;
45 struct graph_value *values;
46 struct graph_label *next;
47 double r, g, b;
48 int value_count;
49 struct graph *parent;
50};
51
52struct graph {
53 char *title;
54 char *xtitle;
55 char *ytitle;
56 unsigned int xdim, ydim;
57 struct graph_label *labels;
58 struct graph_label *tail;
59 int per_label_limit;
60 const char *font;
61};
62
63struct graph *graph_new(unsigned int xdim, unsigned int ydim, const char *font)
64{
65 struct graph *g;
66
67 g = calloc(1, sizeof(*g));
68 g->xdim = xdim;
69 g->ydim = ydim;
70 g->per_label_limit = -1;
71 g->font = font;
72 if (!g->font)
73 g->font = "Sans";
74 return g;
75}
76
77static int count_labels(struct graph_label *labels)
78{
79 int count = 0;
80 struct graph_label *i;
81
82 for (i = labels; i; i = i->next)
83 count++;
84 return count;
85}
86
87static int count_values(struct graph_value *values)
88{
89 int count = 0;
90 struct graph_value *i;
91
92 for (i = values; i; i = i->next)
93 count++;
94 return count;
95}
96
97typedef double (*double_comparator)(double a, double b);
98
99static double mindouble(double a, double b)
100{
101 return a < b ? a : b;
102}
103
104static double maxdouble(double a, double b)
105{
106 return a < b ? b : a;
107}
108
109static double find_double_values(struct graph_value *values, double_comparator cmp)
110{
111 struct graph_value *i;
112 int first = 1;
113 double answer, tmp;
114
115 assert(values != NULL);
116 answer = 0.0; /* shut the compiler up, might need to think harder though. */
117 for (i = values; i; i = i->next) {
118 tmp = *(double *) i->value;
119 if (first) {
120 answer = tmp;
121 first = 0;
122 } else {
123 answer = cmp(answer, tmp);
124 }
125 }
126 return answer;
127}
128
129static double find_double_data(struct graph_label *labels, double_comparator cmp)
130{
131 struct graph_label *i;
132 int first = 1;
133 double answer, tmp;
134
135 assert(labels != NULL);
136 answer = 0.0; /* shut the compiler up, might need to think harder though. */
137 for (i = labels; i; i = i->next) {
138 tmp = find_double_values(i->values, cmp);
139 if (first) {
140 answer = tmp;
141 first = 0;
142 } else {
143 answer = cmp(tmp, answer);
144 }
145 }
146 return answer;
147}
148
149static double find_min_data(struct graph_label *labels)
150{
151 return find_double_data(labels, mindouble);
152}
153
154static double find_max_data(struct graph_label *labels)
155{
156 return find_double_data(labels, maxdouble);
157}
158
159static void draw_bars(struct graph *bg, cairo_t *cr, struct graph_label *lb,
160 double label_offset, double bar_width,
161 double mindata, double maxdata)
162{
163 struct graph_value *i;
164 double x1, y1, x2, y2;
165 int bar_num = 0;
166 double domain, range, v;
167
168 domain = (maxdata - mindata);
169 range = (double) bg->ydim * 0.80; /* FIXME */
170 cairo_stroke(cr);
171 for (i = lb->values; i; i = i->next) {
172
173 x1 = label_offset + (double) bar_num * bar_width + (bar_width * 0.05);
174 x2 = x1 + bar_width * 0.90;
175 y2 = bg->ydim * 0.90;
176 v = *(double *) i->value;
177 y1 = y2 - (((v - mindata) / domain) * range);
178 cairo_move_to(cr, x1, y1);
179 cairo_line_to(cr, x1, y2);
180 cairo_line_to(cr, x2, y2);
181 cairo_line_to(cr, x2, y1);
182 cairo_close_path(cr);
183 cairo_fill(cr);
184 cairo_stroke(cr);
185 bar_num++;
186 }
187}
188
189static void draw_aligned_text(struct graph *g, cairo_t *cr, double x, double y,
190 double fontsize, const char *text, int alignment)
191{
192#define CENTERED 0
193#define LEFT_JUSTIFIED 1
194#define RIGHT_JUSTIFIED 2
195
196 double factor, direction;
197 cairo_text_extents_t extents;
198
199 switch(alignment) {
200 case CENTERED:
201 direction = -1.0;
202 factor = 0.5;
203 break;
204 case RIGHT_JUSTIFIED:
205 direction = -1.0;
206 factor = 1.0;
207 break;
208 case LEFT_JUSTIFIED:
209 default:
210 direction = 1.0;
211 factor = 1.0;
212 break;
213 }
214 cairo_select_font_face (cr, g->font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
215
216 cairo_set_font_size(cr, fontsize);
217 cairo_text_extents(cr, text, &extents);
218 x = x + direction * (factor * extents.width + extents.x_bearing);
219 y = y - (extents.height / 2 + extents.y_bearing);
220
221 cairo_move_to(cr, x, y);
222 cairo_show_text(cr, text);
223}
224
225static inline void draw_centered_text(struct graph *g, cairo_t *cr, double x, double y,
226 double fontsize, const char *text)
227{
228 draw_aligned_text(g, cr, x, y, fontsize, text, CENTERED);
229}
230
231static inline void draw_right_justified_text(struct graph *g, cairo_t *cr,
232 double x, double y,
233 double fontsize, const char *text)
234{
235 draw_aligned_text(g, cr, x, y, fontsize, text, RIGHT_JUSTIFIED);
236}
237
238static inline void draw_left_justified_text(struct graph *g, cairo_t *cr,
239 double x, double y,
240 double fontsize, const char *text)
241{
242 draw_aligned_text(g, cr, x, y, fontsize, text, LEFT_JUSTIFIED);
243}
244
245static void draw_vertical_centered_text(struct graph *g, cairo_t *cr, double x,
246 double y, double fontsize,
247 const char *text)
248{
249 double sx, sy;
250 cairo_text_extents_t extents;
251
252 cairo_select_font_face(cr, g->font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
253
254 cairo_set_font_size(cr, fontsize);
255 cairo_text_extents(cr, text, &extents);
256 sx = x;
257 sy = y;
258 y = y + (extents.width / 2.0 + extents.x_bearing);
259 x = x - (extents.height / 2.0 + extents.y_bearing);
260
261 cairo_move_to(cr, x, y);
262 cairo_save(cr);
263 cairo_translate(cr, -sx, -sy);
264 cairo_rotate(cr, -90.0 * M_PI / 180.0);
265 cairo_translate(cr, sx, sy);
266 cairo_show_text(cr, text);
267 cairo_restore(cr);
268}
269
270static void graph_draw_common(struct graph *g, cairo_t *cr,
271 double *x1, double *y1, double *x2, double *y2)
272{
273 cairo_set_source_rgb(cr, 0, 0, 0);
274 cairo_set_line_width (cr, 0.8);
275
276 /* for now just set margins at 10% of width. This is not very good. */
277 *x1 = g->xdim / 10.0;
278 *x2 = 9.0 * *x1;
279 *y1 = g->ydim / 10.0;
280 *y2 = 9.0 * *y1;
281
282 cairo_move_to(cr, *x1, *y1);
283 cairo_line_to(cr, *x1, *y2);
284 cairo_line_to(cr, *x2, *y2);
285 cairo_line_to(cr, *x2, *y1);
286 cairo_line_to(cr, *x1, *y1);
287 cairo_stroke(cr);
288
289 draw_centered_text(g, cr, g->xdim / 2, g->ydim / 20, 20.0, g->title);
290 draw_centered_text(g, cr, g->xdim / 2, g->ydim * 0.97, 14.0, g->xtitle);
291 draw_vertical_centered_text(g, cr, g->xdim * 0.02, g->ydim / 2, 14.0, g->ytitle);
292 cairo_stroke(cr);
293}
294
295static void graph_draw_x_ticks(struct graph *g, cairo_t *cr,
296 double x1, double y1, double x2, double y2,
297 double minx, double maxx, int nticks)
298{
299 struct tickmark *tm;
300 double tx;
301 int i;
302 static double dash[] = { 1.0, 2.0 };
303
304 nticks = calc_tickmarks(minx, maxx, nticks, &tm);
305
306 for (i = 0; i < nticks; i++) {
307 tx = (((tm[i].value) - minx) / (maxx - minx)) * (x2 - x1) + x1;
308 if (tx < x1 || tx > x2)
309 continue;
310
311 /* Draw tick mark */
312 cairo_set_line_width(cr, 0.8);
313 cairo_move_to(cr, tx, y2);
314 cairo_line_to(cr, tx, y2 + (y2 - y1) * 0.03);
315 cairo_stroke(cr);
316
317 /* draw grid lines */
318 cairo_save(cr);
319 cairo_set_dash(cr, dash, 2, 2.0);
320 cairo_set_line_width(cr, 0.5);
321 cairo_move_to(cr, tx, y1);
322 cairo_line_to(cr, tx, y2);
323 cairo_stroke(cr);
324 cairo_restore(cr);
325
326 /* draw tickmark label */
327 draw_centered_text(g, cr, tx, y2 * 1.04, 12.0, tm[i].string);
328 cairo_stroke(cr);
329
330 }
331}
332
333static void graph_draw_y_ticks(struct graph *g, cairo_t *cr,
334 double x1, double y1, double x2, double y2,
335 double miny, double maxy, int nticks)
336{
337 struct tickmark *tm;
338 double ty;
339 int i;
340 static double dash[] = { 2.0, 2.0 };
341
342 nticks = calc_tickmarks(miny, maxy, nticks, &tm);
343
344 for (i = 0; i < nticks; i++) {
345 ty = y2 - (((tm[i].value) - miny) / (maxy - miny)) * (y2 - y1);
346 if (ty < y1 || ty > y2)
347 continue;
348 /* draw tick mark */
349 cairo_move_to(cr, x1, ty);
350 cairo_line_to(cr, x1 - (x2 - x1) * 0.02, ty);
351 cairo_stroke(cr);
352
353 /* draw grid lines */
354 cairo_save(cr);
355 cairo_set_dash(cr, dash, 2, 2.0);
356 cairo_set_line_width(cr, 0.5);
357 cairo_move_to(cr, x1, ty);
358 cairo_line_to(cr, x2, ty);
359 cairo_stroke(cr);
360 cairo_restore(cr);
361
362 /* draw tickmark label */
363 draw_right_justified_text(g, cr, x1 - (x2 - x1) * 0.025, ty, 12.0, tm[i].string);
364 cairo_stroke(cr);
365 }
366}
367
368void bar_graph_draw(struct graph *bg, cairo_t *cr)
369{
370 double x1, y1, x2, y2;
371 double space_per_label, bar_width;
372 double label_offset, mindata, maxdata;
373 int i, nlabels;
374 struct graph_label *lb;
375
376 cairo_save(cr);
377 graph_draw_common(bg, cr, &x1, &y1, &x2, &y2);
378
379 nlabels = count_labels(bg->labels);
380 space_per_label = (x2 - x1) / (double) nlabels;
381
382 mindata = find_min_data(bg->labels);
383 maxdata = find_max_data(bg->labels);
384
385 if (fabs(maxdata - mindata) < 1e-20) {
386 draw_centered_text(bg, cr,
387 x1 + (x2 - x1) / 2.0,
388 y1 + (y2 - y1) / 2.0, 20.0, "No good data");
389 return;
390 }
391
392 graph_draw_y_ticks(bg, cr, x1, y1, x2, y2, mindata, maxdata, 10);
393
394 i = 0;
395 for (lb = bg->labels; lb; lb = lb->next) {
396 int nvalues;
397 nvalues = count_values(lb->values);
398 bar_width = (space_per_label - space_per_label * 0.2) / (double) nvalues;
399 label_offset = bg->xdim * 0.1 + space_per_label * (double) i + space_per_label * 0.1;
400 draw_bars(bg, cr, lb, label_offset, bar_width, mindata, maxdata);
401 // draw_centered_text(cr, label_offset + (bar_width / 2.0 + bar_width * 0.1), bg->ydim * 0.93,
402 draw_centered_text(bg, cr, x1 + space_per_label * (i + 0.5), bg->ydim * 0.93,
403 12.0, lb->label);
404 i++;
405 }
406 cairo_stroke(cr);
407 cairo_restore(cr);
408}
409
410typedef double (*xy_value_extractor)(struct graph_value *v);
411
412static double getx(struct graph_value *v)
413{
414 struct xyvalue *xy = v->value;
415 return xy->x;
416}
417
418static double gety(struct graph_value *v)
419{
420 struct xyvalue *xy = v->value;
421 return xy->y;
422}
423
424static double find_xy_value(struct graph *g, xy_value_extractor getvalue, double_comparator cmp)
425{
426 double tmp, answer = 0.0;
427 struct graph_label *i;
428 struct graph_value *j;
429 int first = 1;
430
431 for (i = g->labels; i; i = i->next)
432 for (j = i->values; j; j = j->next) {
433 tmp = getvalue(j);
434 if (first) {
435 first = 0;
436 answer = tmp;
437 }
438 answer = cmp(tmp, answer);
439 }
440 return answer;
441}
442
443void line_graph_draw(struct graph *g, cairo_t *cr)
444{
445 double x1, y1, x2, y2;
446 double minx, miny, maxx, maxy;
447 double tx, ty;
448 struct graph_label *i;
449 struct graph_value *j;
450 int good_data = 1, first = 1;
451
452 cairo_save(cr);
453 graph_draw_common(g, cr, &x1, &y1, &x2, &y2);
454
455 minx = find_xy_value(g, getx, mindouble);
456 maxx = find_xy_value(g, getx, maxdouble);
457 miny = find_xy_value(g, gety, mindouble);
458 maxy = find_xy_value(g, gety, maxdouble);
459
460 if (fabs(maxx - minx) < 1e-20 || fabs(maxy - miny) < 1e-20) {
461 good_data = 0;
462 minx = 0.0;
463 miny = 0.0;
464 maxx = 10.0;
465 maxy = 100.0;
466 }
467
468 graph_draw_x_ticks(g, cr, x1, y1, x2, y2, minx, maxx, 10);
469 graph_draw_y_ticks(g, cr, x1, y1, x2, y2, miny, maxy, 10);
470
471 if (!good_data)
472 goto skip_data;
473
474 cairo_set_line_width(cr, 1.5);
475 for (i = g->labels; i; i = i->next) {
476 first = 1;
477 if (i->r < 0) /* invisible data */
478 continue;
479 cairo_set_source_rgb(cr, i->r, i->g, i->b);
480 for (j = i->values; j; j = j->next) {
481 tx = ((getx(j) - minx) / (maxx - minx)) * (x2 - x1) + x1;
482 ty = y2 - ((gety(j) - miny) / (maxy - miny)) * (y2 - y1);
483 if (first) {
484 cairo_move_to(cr, tx, ty);
485 first = 0;
486 } else {
487 cairo_line_to(cr, tx, ty);
488 }
489 }
490 cairo_stroke(cr);
491 }
492
493skip_data:
494 cairo_restore(cr);
495
496}
497
498static void gfree(void *f)
499{
500 if (f)
501 free(f);
502}
503
504static void setstring(char **str, const char *value)
505{
506 gfree(*str);
507 *str = strdup(value);
508}
509
510void graph_title(struct graph *bg, const char *title)
511{
512 setstring(&bg->title, title);
513}
514
515void graph_x_title(struct graph *bg, const char *title)
516{
517 setstring(&bg->xtitle, title);
518}
519
520void graph_y_title(struct graph *bg, const char *title)
521{
522 setstring(&bg->ytitle, title);
523}
524
525static struct graph_label *graph_find_label(struct graph *bg,
526 const char *label)
527{
528 struct graph_label *i;
529
530 for (i = bg->labels; i; i = i->next)
531 if (strcmp(label, i->label) == 0)
532 return i;
533 return NULL;
534}
535
536void graph_add_label(struct graph *bg, const char *label)
537{
538 struct graph_label *i;
539
540 i = graph_find_label(bg, label);
541 if (i)
542 return; /* already present. */
543 i = calloc(1, sizeof(*i));
544 i->parent = bg;
545 setstring(&i->label, label);
546 i->next = NULL;
547 if (!bg->tail)
548 bg->labels = i;
549 else
550 bg->tail->next = i;
551 bg->tail = i;
552}
553
554static void graph_label_add_value(struct graph_label *i, void *value)
555{
556 struct graph_value *x;
557
558 x = malloc(sizeof(*x));
559 x->value = value;
560 x->next = NULL;
561 if (!i->tail) {
562 i->values = x;
563 } else {
564 i->tail->next = x;
565 }
566 i->tail = x;
567 i->value_count++;
568
569 if (i->parent->per_label_limit != -1 &&
570 i->value_count > i->parent->per_label_limit) {
571 x = i->values;
572 i->values = i->values->next;
573 free(x->value);
574 free(x);
575 i->value_count--;
576 }
577}
578
579int graph_add_data(struct graph *bg, const char *label, const double value)
580{
581 struct graph_label *i;
582 double *d;
583
584 d = malloc(sizeof(*d));
585 *d = value;
586
587 i = graph_find_label(bg, label);
588 if (!i)
589 return -1;
590 graph_label_add_value(i, d);
591 return 0;
592}
593
594int graph_add_xy_data(struct graph *bg, const char *label,
595 const double x, const double y)
596{
597 struct graph_label *i;
598 struct xyvalue *xy;
599
600 xy = malloc(sizeof(*xy));
601 xy->x = x;
602 xy->y = y;
603
604 i = graph_find_label(bg, label);
605 if (!i)
606 return -1;
607 graph_label_add_value(i, xy);
608 return 0;
609}
610
611static void graph_free_values(struct graph_value *values)
612{
613 struct graph_value *i, *next;
614
615 for (i = values; i; i = next) {
616 next = i->next;
617 gfree(i->value);
618 gfree(i);
619 }
620}
621
622static void graph_free_labels(struct graph_label *labels)
623{
624 struct graph_label *i, *next;
625
626 for (i = labels; i; i = next) {
627 next = i->next;
628 graph_free_values(i->values);
629 gfree(i);
630 }
631}
632
633void graph_set_color(struct graph *gr, const char *label,
634 double red, double green, double blue)
635{
636 struct graph_label *i;
637 double r, g, b;
638
639 if (red < 0.0) { /* invisible color */
640 r = -1.0;
641 g = -1.0;
642 b = -1.0;
643 } else {
644 r = fabs(red);
645 g = fabs(green);
646 b = fabs(blue);
647
648 if (r > 1.0)
649 r = 1.0;
650 if (g > 1.0)
651 g = 1.0;
652 if (b > 1.0)
653 b =1.0;
654 }
655
656 for (i = gr->labels; i; i = i->next)
657 if (strcmp(i->label, label) == 0) {
658 i->r = r;
659 i->g = g;
660 i->b = b;
661 break;
662 }
663}
664
665void graph_free(struct graph *bg)
666{
667 gfree(bg->title);
668 gfree(bg->xtitle);
669 gfree(bg->ytitle);
670 graph_free_labels(bg->labels);
671}
672
673/* For each line in the line graph, up to per_label_limit segments may
674 * be added. After that, adding more data to the end of the line
675 * causes data to drop off of the front of the line.
676 */
677void line_graph_set_data_count_limit(struct graph *g, int per_label_limit)
678{
679 g->per_label_limit = per_label_limit;
680}
681