gfio: add extra space to the graphs at the edges
[fio.git] / graph.c
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 #include "graph.h"
33
34 struct xyvalue {
35         double x, y;
36 };
37
38 struct graph_value {
39         struct graph_value *next;
40         void *value;
41 };
42
43 struct graph_label {
44         char *label;
45         struct graph_value *tail;
46         struct graph_value *values;
47         struct graph_label *next;
48         double r, g, b;
49         int value_count;
50         struct graph *parent;
51 };
52
53 struct graph {
54         char *title;
55         char *xtitle;
56         char *ytitle;
57         unsigned int xdim, ydim;
58         double xoffset, yoffset;
59         struct graph_label *labels;
60         struct graph_label *tail;
61         int per_label_limit;
62         const char *font;
63         graph_axis_unit_change_callback x_axis_unit_change_callback;
64         graph_axis_unit_change_callback y_axis_unit_change_callback;
65         double left_extra;      
66         double right_extra;     
67         double top_extra;       
68         double bottom_extra;    
69 };
70
71 void graph_set_size(struct graph *g, unsigned int xdim, unsigned int ydim)
72 {
73         g->xdim = xdim;
74         g->ydim = ydim;
75 }
76
77 void graph_set_position(struct graph *g, double xoffset, double yoffset)
78 {
79         g->xoffset = xoffset;
80         g->yoffset = yoffset;
81 }
82
83 struct graph *graph_new(unsigned int xdim, unsigned int ydim, const char *font)
84 {
85         struct graph *g;
86
87         g = calloc(1, sizeof(*g));
88         graph_set_size(g, xdim, ydim);
89         g->per_label_limit = -1;
90         g->font = font;
91         if (!g->font)
92                 g->font = "Sans";
93         return g;
94 }
95
96 void graph_x_axis_unit_change_notify(struct graph *g, graph_axis_unit_change_callback f)
97 {
98         g->x_axis_unit_change_callback = f;
99 }
100
101 void graph_y_axis_unit_change_notify(struct graph *g, graph_axis_unit_change_callback f)
102 {
103         g->y_axis_unit_change_callback = f;
104 }
105
106 static int count_labels(struct graph_label *labels)
107 {
108         int count = 0;
109         struct graph_label *i;
110
111         for (i = labels; i; i = i->next)
112                 count++;
113         return count;
114 }
115
116 static int count_values(struct graph_value *values)
117 {
118         int count = 0;
119         struct graph_value *i;
120
121         for (i = values; i; i = i->next)
122                 count++;
123         return count;
124 }
125
126 typedef double (*double_comparator)(double a, double b);
127
128 static double mindouble(double a, double b)
129 {
130         return a < b ? a : b;
131 }
132
133 static double maxdouble(double a, double b)
134 {
135         return a < b ? b : a;
136 }
137
138 static double find_double_values(struct graph_value *values, double_comparator cmp)
139 {
140         struct graph_value *i;
141         int first = 1;
142         double answer, tmp;
143
144         assert(values != NULL);
145         answer = 0.0; /* shut the compiler up, might need to think harder though. */
146         for (i = values; i; i = i->next) {
147                 tmp = *(double *) i->value; 
148                 if (first) {
149                         answer = tmp;
150                         first = 0;
151                 } else {
152                         answer = cmp(answer, tmp);
153                 }
154         }
155         return answer;
156 }
157
158 static double find_double_data(struct graph_label *labels, double_comparator cmp)
159 {
160         struct graph_label *i;
161         int first = 1;
162         double answer, tmp;
163
164         assert(labels != NULL);
165         answer = 0.0; /* shut the compiler up, might need to think harder though. */
166         for (i = labels; i; i = i->next) {
167                 tmp = find_double_values(i->values, cmp);
168                 if (first) {
169                         answer = tmp;
170                         first = 0;
171                 } else {
172                         answer = cmp(tmp, answer);
173                 }
174         }
175         return answer;
176 }
177
178 static double find_min_data(struct graph_label *labels)
179 {
180         return find_double_data(labels, mindouble);
181 }
182
183 static double find_max_data(struct graph_label *labels)
184 {
185         return find_double_data(labels, maxdouble);
186 }
187
188 static void draw_bars(struct graph *bg, cairo_t *cr, struct graph_label *lb,
189                         double label_offset, double bar_width,
190                         double mindata, double maxdata)
191 {
192         struct graph_value *i;
193         double x1, y1, x2, y2;
194         int bar_num = 0;
195         double domain, range, v;
196
197         domain = (maxdata - mindata);
198         range = (double) bg->ydim * 0.80; /* FIXME */
199         cairo_stroke(cr);
200         for (i = lb->values; i; i = i->next) {
201
202                 x1 = label_offset + (double) bar_num * bar_width + (bar_width * 0.05);
203                 x2 = x1 + bar_width * 0.90;
204                 y2 = bg->ydim * 0.90;
205                 v = *(double *) i->value;
206                 y1 = y2 - (((v - mindata) / domain) * range);
207                 cairo_move_to(cr, x1, y1);
208                 cairo_line_to(cr, x1, y2);
209                 cairo_line_to(cr, x2, y2);
210                 cairo_line_to(cr, x2, y1);
211                 cairo_close_path(cr);
212                 cairo_fill(cr);
213                 cairo_stroke(cr);
214                 bar_num++;      
215         }
216 }
217
218 static void draw_aligned_text(struct graph *g, cairo_t *cr, double x, double y,
219                                double fontsize, const char *text, int alignment)
220 {
221 #define CENTERED 0
222 #define LEFT_JUSTIFIED 1
223 #define RIGHT_JUSTIFIED 2
224
225         double factor, direction;
226         cairo_text_extents_t extents;
227
228         switch(alignment) {
229                 case CENTERED:
230                         direction = -1.0;
231                         factor = 0.5;
232                         break;
233                 case RIGHT_JUSTIFIED:
234                         direction = -1.0;
235                         factor = 1.0;
236                         break;
237                 case LEFT_JUSTIFIED:
238                 default:
239                         direction = 1.0;
240                         factor = 1.0;
241                         break;
242         }
243         cairo_select_font_face (cr, g->font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
244
245         cairo_set_font_size(cr, fontsize);
246         cairo_text_extents(cr, text, &extents);
247         x = x + direction * (factor * extents.width  + extents.x_bearing);
248         y = y - (extents.height / 2 + extents.y_bearing);
249
250         cairo_move_to(cr, x, y);
251         cairo_show_text(cr, text);
252 }
253
254 static inline void draw_centered_text(struct graph *g, cairo_t *cr, double x, double y,
255                                double fontsize, const char *text)
256 {
257         draw_aligned_text(g, cr, x, y, fontsize, text, CENTERED);
258 }
259
260 static inline void draw_right_justified_text(struct graph *g, cairo_t *cr,
261                                 double x, double y,
262                                 double fontsize, const char *text)
263 {
264         draw_aligned_text(g, cr, x, y, fontsize, text, RIGHT_JUSTIFIED);
265 }
266
267 static inline void draw_left_justified_text(struct graph *g, cairo_t *cr,
268                                 double x, double y,
269                                 double fontsize, const char *text)
270 {
271         draw_aligned_text(g, cr, x, y, fontsize, text, LEFT_JUSTIFIED);
272 }
273
274 static void draw_vertical_centered_text(struct graph *g, cairo_t *cr, double x,
275                                         double y, double fontsize,
276                                         const char *text)
277 {
278         double sx, sy;
279         cairo_text_extents_t extents;
280
281         cairo_select_font_face(cr, g->font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
282
283         cairo_set_font_size(cr, fontsize);
284         cairo_text_extents(cr, text, &extents);
285         sx = x;
286         sy = y;
287         y = y + (extents.width / 2.0 + extents.x_bearing);
288         x = x - (extents.height / 2.0 + extents.y_bearing);
289
290         cairo_move_to(cr, x, y);
291         cairo_save(cr);
292         cairo_translate(cr, -sx, -sy);
293         cairo_rotate(cr, -90.0 * M_PI / 180.0);
294         cairo_translate(cr, sx, sy);
295         cairo_show_text(cr, text);
296         cairo_restore(cr);
297 }
298
299 static void graph_draw_common(struct graph *g, cairo_t *cr,
300         double *x1, double *y1, double *x2, double *y2)
301 {
302         cairo_set_source_rgb(cr, 0, 0, 0);
303         cairo_set_line_width (cr, 0.8);
304
305         *x1 = 0.15 * g->xdim;   
306         *x2 = 0.95 * g->xdim;
307         *y1 = 0.10 * g->ydim;   
308         *y2 = 0.90 * g->ydim;
309
310         cairo_move_to(cr, *x1, *y1);
311         cairo_line_to(cr, *x1, *y2);
312         cairo_line_to(cr, *x2, *y2);
313         cairo_line_to(cr, *x2, *y1);
314         cairo_line_to(cr, *x1, *y1);
315         cairo_stroke(cr);
316
317         draw_centered_text(g, cr, g->xdim / 2, g->ydim / 20, 20.0, g->title);
318         draw_centered_text(g, cr, g->xdim / 2, g->ydim * 0.97, 14.0, g->xtitle);
319         draw_vertical_centered_text(g, cr, g->xdim * 0.02, g->ydim / 2, 14.0, g->ytitle);
320         cairo_stroke(cr);
321 }
322
323 static void graph_draw_x_ticks(struct graph *g, cairo_t *cr,
324         double x1, double y1, double x2, double y2,
325         double minx, double maxx, int nticks)
326 {
327         struct tickmark *tm;
328         double tx;
329         int i, power_of_ten;
330         static double dash[] = { 1.0, 2.0 };
331
332         nticks = calc_tickmarks(minx, maxx, nticks, &tm, &power_of_ten,
333                 g->x_axis_unit_change_callback == NULL);
334         if (g->x_axis_unit_change_callback)
335                 g->x_axis_unit_change_callback(g, power_of_ten);
336
337         for (i = 0; i < nticks; i++) {
338                 tx = (((tm[i].value) - minx) / (maxx - minx)) * (x2 - x1) + x1;
339                 if (tx < x1 || tx > x2)
340                         continue;
341
342                 /* Draw tick mark */
343                 cairo_set_line_width(cr, 0.8);
344                 cairo_move_to(cr, tx, y2);
345                 cairo_line_to(cr, tx, y2 + (y2 - y1) * 0.03);
346                 cairo_stroke(cr);
347
348                 /* draw grid lines */
349                 cairo_save(cr);
350                 cairo_set_dash(cr, dash, 2, 2.0);
351                 cairo_set_line_width(cr, 0.5);
352                 cairo_move_to(cr, tx, y1);
353                 cairo_line_to(cr, tx, y2);
354                 cairo_stroke(cr);
355                 cairo_restore(cr);
356
357                 /* draw tickmark label */
358                 draw_centered_text(g, cr, tx, y2 * 1.04, 12.0, tm[i].string);
359                 cairo_stroke(cr);
360                 
361         }
362 }
363
364 static void graph_draw_y_ticks(struct graph *g, cairo_t *cr,
365         double x1, double y1, double x2, double y2,
366         double miny, double maxy, int nticks)
367 {
368         struct tickmark *tm;
369         double ty;
370         int i, power_of_ten;
371         static double dash[] = { 2.0, 2.0 };
372
373         nticks = calc_tickmarks(miny, maxy, nticks, &tm, &power_of_ten,
374                 g->y_axis_unit_change_callback == NULL);
375         if (g->y_axis_unit_change_callback)
376                 g->y_axis_unit_change_callback(g, power_of_ten);
377
378         for (i = 0; i < nticks; i++) {
379                 ty = y2 - (((tm[i].value) - miny) / (maxy - miny)) * (y2 - y1);
380                 if (ty < y1 || ty > y2)
381                         continue;
382                 /* draw tick mark */
383                 cairo_move_to(cr, x1, ty);
384                 cairo_line_to(cr, x1 - (x2 - x1) * 0.02, ty);
385                 cairo_stroke(cr);
386
387                 /* draw grid lines */
388                 cairo_save(cr);
389                 cairo_set_dash(cr, dash, 2, 2.0);
390                 cairo_set_line_width(cr, 0.5);
391                 cairo_move_to(cr, x1, ty);
392                 cairo_line_to(cr, x2, ty);
393                 cairo_stroke(cr);
394                 cairo_restore(cr);
395
396                 /* draw tickmark label */
397                 draw_right_justified_text(g, cr, x1 - (x2 - x1) * 0.025, ty, 12.0, tm[i].string);
398                 cairo_stroke(cr);
399         }
400 }
401
402 void bar_graph_draw(struct graph *bg, cairo_t *cr)
403 {
404         double x1, y1, x2, y2;
405         double space_per_label, bar_width;
406         double label_offset, mindata, maxdata;
407         int i, nlabels;
408         struct graph_label *lb;
409
410         cairo_save(cr);
411         cairo_translate(cr, bg->xoffset, bg->yoffset);
412         graph_draw_common(bg, cr, &x1, &y1, &x2, &y2);
413
414         nlabels = count_labels(bg->labels);
415         space_per_label = (x2 - x1) / (double) nlabels; 
416
417         mindata = find_min_data(bg->labels);
418         maxdata = find_max_data(bg->labels);
419
420         if (fabs(maxdata - mindata) < 1e-20) {
421                 draw_centered_text(bg, cr,
422                         x1 + (x2 - x1) / 2.0,
423                         y1 + (y2 - y1) / 2.0, 20.0, "No good data");
424                 return;
425         }
426
427         graph_draw_y_ticks(bg, cr, x1, y1, x2, y2, mindata, maxdata, 10);
428
429         i = 0;
430         for (lb = bg->labels; lb; lb = lb->next) {
431                 int nvalues;
432                 nvalues = count_values(lb->values);
433                 bar_width = (space_per_label - space_per_label * 0.2) / (double) nvalues;
434                 label_offset = bg->xdim * 0.1 + space_per_label * (double) i + space_per_label * 0.1;
435                 draw_bars(bg, cr, lb, label_offset, bar_width, mindata, maxdata);
436                 // draw_centered_text(cr, label_offset + (bar_width / 2.0 + bar_width * 0.1), bg->ydim * 0.93,
437                 draw_centered_text(bg, cr, x1 + space_per_label * (i + 0.5), bg->ydim * 0.93,
438                         12.0, lb->label); 
439                 i++;
440         }
441         cairo_stroke(cr);
442         cairo_restore(cr);
443 }
444
445 typedef double (*xy_value_extractor)(struct graph_value *v);
446
447 static double getx(struct graph_value *v)
448 {
449         struct xyvalue *xy = v->value;
450         return xy->x;
451 }
452
453 static double gety(struct graph_value *v)
454 {
455         struct xyvalue *xy = v->value;
456         return xy->y;
457 }
458
459 static double find_xy_value(struct graph *g, xy_value_extractor getvalue, double_comparator cmp)
460 {
461         double tmp, answer = 0.0;
462         struct graph_label *i;
463         struct graph_value *j;
464         int first = 1;
465
466         for (i = g->labels; i; i = i->next)
467                 for (j = i->values; j; j = j->next) {
468                         tmp = getvalue(j);
469                         if (first) {
470                                 first = 0;
471                                 answer = tmp;
472                         }
473                         answer = cmp(tmp, answer);      
474                 }
475         return answer;
476
477
478 void line_graph_draw(struct graph *g, cairo_t *cr)
479 {
480         double x1, y1, x2, y2;
481         double minx, miny, maxx, maxy, gminx, gminy, gmaxx, gmaxy;
482         double tx, ty, top_extra, bottom_extra, left_extra, right_extra;
483         struct graph_label *i;
484         struct graph_value *j;
485         int good_data = 1, first = 1;
486
487         cairo_save(cr);
488         cairo_translate(cr, g->xoffset, g->yoffset);
489         graph_draw_common(g, cr, &x1, &y1, &x2, &y2);
490
491         minx = find_xy_value(g, getx, mindouble);
492         maxx = find_xy_value(g, getx, maxdouble);
493         miny = find_xy_value(g, gety, mindouble);
494         maxy = find_xy_value(g, gety, maxdouble);
495
496         if (fabs(maxx - minx) < 1e-20 || fabs(maxy - miny) < 1e-20) {
497                 good_data = 0;
498                 minx = 0.0;
499                 miny = 0.0;
500                 maxx = 10.0;
501                 maxy = 100.0;
502         }
503
504         top_extra = 0.0;
505         bottom_extra = 0.0;
506         left_extra = 0.0;
507         right_extra = 0.0;
508
509         if (g->top_extra > 0.001)
510                 top_extra = fabs(maxy - miny) * g->top_extra;
511         if (g->bottom_extra > 0.001)
512                 bottom_extra = fabs(maxy - miny) * g->bottom_extra;
513         if (g->left_extra > 0.001)
514                 left_extra = fabs(maxx - minx) * g->left_extra;
515         if (g->right_extra > 0.001)
516                 right_extra = fabs(maxx - minx) * g->right_extra;
517
518         gminx = minx - left_extra;
519         gmaxx = maxx + right_extra;
520         gminy = miny - bottom_extra;
521         gmaxy = maxy + top_extra;
522
523         graph_draw_x_ticks(g, cr, x1, y1, x2, y2, gminx, gmaxx, 10);
524         graph_draw_y_ticks(g, cr, x1, y1, x2, y2, gminy, gmaxy, 10);
525
526         if (!good_data)
527                 goto skip_data;
528
529         cairo_set_line_width(cr, 1.5);
530         for (i = g->labels; i; i = i->next) {
531                 first = 1;
532                 if (i->r < 0) /* invisible data */
533                         continue;
534                 cairo_set_source_rgb(cr, i->r, i->g, i->b);
535                 for (j = i->values; j; j = j->next) {
536                         tx = ((getx(j) - gminx) / (gmaxx - gminx)) * (x2 - x1) + x1;
537                         ty = y2 - ((gety(j) - gminy) / (gmaxy - gminy)) * (y2 - y1);
538                         if (first) {
539                                 cairo_move_to(cr, tx, ty);
540                                 first = 0;
541                         } else {
542                                 cairo_line_to(cr, tx, ty);
543                         }
544                 }
545                 cairo_stroke(cr);
546         }
547
548 skip_data:
549         cairo_restore(cr);
550
551 }
552
553 static void gfree(void *f)
554 {
555         if (f)
556                 free(f);
557 }
558
559 static void setstring(char **str, const char *value)
560 {
561         gfree(*str);
562         *str = strdup(value);
563 }
564
565 void graph_title(struct graph *bg, const char *title)
566 {
567         setstring(&bg->title, title);
568 }
569
570 void graph_x_title(struct graph *bg, const char *title)
571 {
572         setstring(&bg->xtitle, title);
573 }
574
575 void graph_y_title(struct graph *bg, const char *title)
576 {
577         setstring(&bg->ytitle, title);
578 }
579
580 static struct graph_label *graph_find_label(struct graph *bg,
581                                 const char *label)
582 {
583         struct graph_label *i;
584         
585         for (i = bg->labels; i; i = i->next)
586                 if (strcmp(label, i->label) == 0)
587                         return i;
588         return NULL;
589 }
590
591 void graph_add_label(struct graph *bg, const char *label)
592 {
593         struct graph_label *i;
594         
595         i = graph_find_label(bg, label);
596         if (i)
597                 return; /* already present. */
598         i = calloc(1, sizeof(*i));
599         i->parent = bg;
600         setstring(&i->label, label);
601         i->next = NULL;
602         if (!bg->tail)
603                 bg->labels = i;
604         else
605                 bg->tail->next = i;
606         bg->tail = i;
607 }
608
609 static void graph_label_add_value(struct graph_label *i, void *value)
610 {
611         struct graph_value *x;
612
613         x = malloc(sizeof(*x));
614         x->value = value;
615         x->next = NULL;
616         if (!i->tail) {
617                 i->values = x;
618         } else {
619                 i->tail->next = x;
620         }
621         i->tail = x;
622         i->value_count++;
623
624         if (i->parent->per_label_limit != -1 &&
625                 i->value_count > i->parent->per_label_limit) {
626                 int to_drop = 1;
627
628                 /*
629                  * If the limit was dynamically reduced, making us more
630                  * than 1 entry ahead after adding this one, drop two
631                  * entries. This will make us (eventually) reach the
632                  * specified limit.
633                  */
634                 if (i->value_count - i->parent->per_label_limit >= 2)
635                         to_drop = 2;
636
637                 while (to_drop--) {
638                         x = i->values;
639                         i->values = i->values->next;
640                         free(x->value);
641                         free(x);
642                         i->value_count--;
643                 }
644         }
645 }
646
647 int graph_add_data(struct graph *bg, const char *label, const double value)
648 {
649         struct graph_label *i;
650         double *d;
651
652         d = malloc(sizeof(*d));
653         *d = value;
654
655         i = graph_find_label(bg, label);
656         if (!i)
657                 return -1;
658         graph_label_add_value(i, d);
659         return 0;
660 }
661
662 int graph_add_xy_data(struct graph *bg, const char *label,
663                 const double x, const double y)
664 {
665         struct graph_label *i;
666         struct xyvalue *xy;
667
668         xy = malloc(sizeof(*xy));
669         xy->x = x;
670         xy->y = y;
671
672         i = graph_find_label(bg, label);
673         if (!i)
674                 return -1;
675         graph_label_add_value(i, xy);
676         return 0;
677 }
678
679 static void graph_free_values(struct graph_value *values)
680 {
681         struct graph_value *i, *next;
682
683         for (i = values; i; i = next) {
684                 next = i->next;
685                 gfree(i->value);
686                 gfree(i);
687         }       
688 }
689
690 static void graph_free_labels(struct graph_label *labels)
691 {
692         struct graph_label *i, *next;
693
694         for (i = labels; i; i = next) {
695                 next = i->next;
696                 graph_free_values(i->values);
697                 gfree(i);
698         }       
699 }
700
701 void graph_set_color(struct graph *gr, const char *label,
702         double red, double green, double blue)
703 {
704         struct graph_label *i;
705         double r, g, b;
706
707         if (red < 0.0) { /* invisible color */
708                 r = -1.0;
709                 g = -1.0;
710                 b = -1.0;
711         } else {
712                 r = fabs(red);
713                 g = fabs(green);
714                 b = fabs(blue);
715
716                 if (r > 1.0)
717                         r = 1.0;
718                 if (g > 1.0)
719                         g = 1.0;
720                 if (b > 1.0)
721                         b =1.0;
722         }
723
724         for (i = gr->labels; i; i = i->next)
725                 if (strcmp(i->label, label) == 0) {
726                         i->r = r;       
727                         i->g = g;       
728                         i->b = b;       
729                         break;
730                 }
731 }
732
733 void graph_free(struct graph *bg)
734 {
735         gfree(bg->title);
736         gfree(bg->xtitle);
737         gfree(bg->ytitle);
738         graph_free_labels(bg->labels);
739 }
740
741 /* For each line in the line graph, up to per_label_limit segments may
742  * be added.  After that, adding more data to the end of the line
743  * causes data to drop off of the front of the line.
744  */
745 void line_graph_set_data_count_limit(struct graph *g, int per_label_limit)
746 {
747         g->per_label_limit = per_label_limit;
748 }
749
750 void graph_add_extra_space(struct graph *g, double left_percent, double right_percent,
751                                 double top_percent, double bottom_percent)
752 {
753         g->left_extra = left_percent;   
754         g->right_extra = right_percent; 
755         g->top_extra = top_percent;     
756         g->bottom_extra = bottom_percent;       
757 }
758
759