graph: switch tooltip lookups to being range based in a prio tree
[fio.git] / graph.c
CommitLineData
af58ef32
SC
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>
93e2db2b 27#include <stdlib.h>
af58ef32
SC
28
29#include <cairo.h>
30#include <gtk/gtk.h>
31
32#include "tickmarks.h"
09ad20ff 33#include "graph.h"
b65c7ec4
JA
34#include "flist.h"
35#include "lib/prio_tree.h"
36#include "gettime.h"
37struct thread_data;
38#include "time.h"
39
40/*
41 * Allowable difference to show tooltip
42 */
43#define TOOLTIP_DELTA 1.02
af58ef32
SC
44
45struct xyvalue {
46 double x, y;
47};
48
49struct graph_value {
50 struct graph_value *next;
b65c7ec4 51 struct prio_tree_node node;
93e2db2b 52 char *tooltip;
af58ef32
SC
53 void *value;
54};
55
56struct graph_label {
57 char *label;
58 struct graph_value *tail;
59 struct graph_value *values;
60 struct graph_label *next;
b65c7ec4 61 struct prio_tree_root prio_tree;
af58ef32
SC
62 double r, g, b;
63 int value_count;
93e2db2b 64 unsigned int tooltip_count;
af58ef32
SC
65 struct graph *parent;
66};
67
b65c7ec4
JA
68struct tick_value {
69 unsigned int offset;
70 double value;
71};
72
af58ef32
SC
73struct graph {
74 char *title;
75 char *xtitle;
76 char *ytitle;
87d5f276 77 unsigned int xdim, ydim;
57f9d28e 78 double xoffset, yoffset;
af58ef32
SC
79 struct graph_label *labels;
80 struct graph_label *tail;
81 int per_label_limit;
f3e8440f 82 const char *font;
7175d91d
SC
83 graph_axis_unit_change_callback x_axis_unit_change_callback;
84 graph_axis_unit_change_callback y_axis_unit_change_callback;
d8fbeefb 85 unsigned int base_offset;
def0ac29
SC
86 double left_extra;
87 double right_extra;
88 double top_extra;
89 double bottom_extra;
b65c7ec4
JA
90
91 double xtick_zero;
92 double xtick_delta;
93 double xtick_zero_val;
94 double ytick_zero;
95 double ytick_delta;
96 double ytick_zero_val;
af58ef32
SC
97};
98
3ea48b88
SC
99void graph_set_size(struct graph *g, unsigned int xdim, unsigned int ydim)
100{
101 g->xdim = xdim;
102 g->ydim = ydim;
103}
104
57f9d28e
SC
105void graph_set_position(struct graph *g, double xoffset, double yoffset)
106{
107 g->xoffset = xoffset;
108 g->yoffset = yoffset;
109}
110
f3e8440f 111struct graph *graph_new(unsigned int xdim, unsigned int ydim, const char *font)
af58ef32
SC
112{
113 struct graph *g;
114
115 g = calloc(1, sizeof(*g));
3ea48b88 116 graph_set_size(g, xdim, ydim);
af58ef32 117 g->per_label_limit = -1;
f3e8440f
JA
118 g->font = font;
119 if (!g->font)
120 g->font = "Sans";
af58ef32
SC
121 return g;
122}
123
7175d91d
SC
124void graph_x_axis_unit_change_notify(struct graph *g, graph_axis_unit_change_callback f)
125{
126 g->x_axis_unit_change_callback = f;
127}
128
129void graph_y_axis_unit_change_notify(struct graph *g, graph_axis_unit_change_callback f)
130{
131 g->y_axis_unit_change_callback = f;
132}
133
af58ef32
SC
134static int count_labels(struct graph_label *labels)
135{
136 int count = 0;
137 struct graph_label *i;
138
139 for (i = labels; i; i = i->next)
140 count++;
141 return count;
142}
143
144static int count_values(struct graph_value *values)
145{
146 int count = 0;
147 struct graph_value *i;
148
149 for (i = values; i; i = i->next)
150 count++;
151 return count;
152}
153
154typedef double (*double_comparator)(double a, double b);
155
156static double mindouble(double a, double b)
157{
158 return a < b ? a : b;
159}
160
161static double maxdouble(double a, double b)
162{
163 return a < b ? b : a;
164}
165
166static double find_double_values(struct graph_value *values, double_comparator cmp)
167{
168 struct graph_value *i;
169 int first = 1;
170 double answer, tmp;
171
172 assert(values != NULL);
173 answer = 0.0; /* shut the compiler up, might need to think harder though. */
174 for (i = values; i; i = i->next) {
175 tmp = *(double *) i->value;
176 if (first) {
177 answer = tmp;
178 first = 0;
179 } else {
180 answer = cmp(answer, tmp);
181 }
182 }
183 return answer;
184}
185
186static double find_double_data(struct graph_label *labels, double_comparator cmp)
187{
188 struct graph_label *i;
189 int first = 1;
190 double answer, tmp;
191
192 assert(labels != NULL);
193 answer = 0.0; /* shut the compiler up, might need to think harder though. */
194 for (i = labels; i; i = i->next) {
195 tmp = find_double_values(i->values, cmp);
196 if (first) {
197 answer = tmp;
198 first = 0;
199 } else {
200 answer = cmp(tmp, answer);
201 }
202 }
203 return answer;
204}
205
206static double find_min_data(struct graph_label *labels)
207{
208 return find_double_data(labels, mindouble);
209}
210
211static double find_max_data(struct graph_label *labels)
212{
213 return find_double_data(labels, maxdouble);
214}
215
216static void draw_bars(struct graph *bg, cairo_t *cr, struct graph_label *lb,
217 double label_offset, double bar_width,
218 double mindata, double maxdata)
219{
220 struct graph_value *i;
221 double x1, y1, x2, y2;
222 int bar_num = 0;
223 double domain, range, v;
224
225 domain = (maxdata - mindata);
226 range = (double) bg->ydim * 0.80; /* FIXME */
227 cairo_stroke(cr);
228 for (i = lb->values; i; i = i->next) {
229
230 x1 = label_offset + (double) bar_num * bar_width + (bar_width * 0.05);
231 x2 = x1 + bar_width * 0.90;
232 y2 = bg->ydim * 0.90;
233 v = *(double *) i->value;
234 y1 = y2 - (((v - mindata) / domain) * range);
235 cairo_move_to(cr, x1, y1);
236 cairo_line_to(cr, x1, y2);
237 cairo_line_to(cr, x2, y2);
238 cairo_line_to(cr, x2, y1);
239 cairo_close_path(cr);
240 cairo_fill(cr);
241 cairo_stroke(cr);
242 bar_num++;
243 }
244}
245
10e54cdc
SC
246static void draw_aligned_text(struct graph *g, cairo_t *cr, double x, double y,
247 double fontsize, const char *text, int alignment)
af58ef32 248{
10e54cdc
SC
249#define CENTERED 0
250#define LEFT_JUSTIFIED 1
251#define RIGHT_JUSTIFIED 2
252
253 double factor, direction;
af58ef32
SC
254 cairo_text_extents_t extents;
255
10e54cdc
SC
256 switch(alignment) {
257 case CENTERED:
258 direction = -1.0;
259 factor = 0.5;
260 break;
261 case RIGHT_JUSTIFIED:
262 direction = -1.0;
263 factor = 1.0;
264 break;
265 case LEFT_JUSTIFIED:
266 default:
267 direction = 1.0;
268 factor = 1.0;
269 break;
270 }
f3e8440f 271 cairo_select_font_face (cr, g->font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
af58ef32
SC
272
273 cairo_set_font_size(cr, fontsize);
274 cairo_text_extents(cr, text, &extents);
10e54cdc 275 x = x + direction * (factor * extents.width + extents.x_bearing);
af58ef32
SC
276 y = y - (extents.height / 2 + extents.y_bearing);
277
278 cairo_move_to(cr, x, y);
279 cairo_show_text(cr, text);
280}
281
10e54cdc
SC
282static inline void draw_centered_text(struct graph *g, cairo_t *cr, double x, double y,
283 double fontsize, const char *text)
284{
285 draw_aligned_text(g, cr, x, y, fontsize, text, CENTERED);
286}
287
288static inline void draw_right_justified_text(struct graph *g, cairo_t *cr,
289 double x, double y,
290 double fontsize, const char *text)
291{
292 draw_aligned_text(g, cr, x, y, fontsize, text, RIGHT_JUSTIFIED);
293}
294
295static inline void draw_left_justified_text(struct graph *g, cairo_t *cr,
296 double x, double y,
297 double fontsize, const char *text)
298{
299 draw_aligned_text(g, cr, x, y, fontsize, text, LEFT_JUSTIFIED);
300}
301
f3e8440f
JA
302static void draw_vertical_centered_text(struct graph *g, cairo_t *cr, double x,
303 double y, double fontsize,
304 const char *text)
af58ef32
SC
305{
306 double sx, sy;
307 cairo_text_extents_t extents;
308
f3e8440f 309 cairo_select_font_face(cr, g->font, CAIRO_FONT_SLANT_NORMAL, CAIRO_FONT_WEIGHT_NORMAL);
af58ef32
SC
310
311 cairo_set_font_size(cr, fontsize);
312 cairo_text_extents(cr, text, &extents);
313 sx = x;
314 sy = y;
315 y = y + (extents.width / 2.0 + extents.x_bearing);
316 x = x - (extents.height / 2.0 + extents.y_bearing);
317
318 cairo_move_to(cr, x, y);
319 cairo_save(cr);
320 cairo_translate(cr, -sx, -sy);
321 cairo_rotate(cr, -90.0 * M_PI / 180.0);
322 cairo_translate(cr, sx, sy);
323 cairo_show_text(cr, text);
324 cairo_restore(cr);
325}
326
327static void graph_draw_common(struct graph *g, cairo_t *cr,
328 double *x1, double *y1, double *x2, double *y2)
329{
330 cairo_set_source_rgb(cr, 0, 0, 0);
f3e8440f 331 cairo_set_line_width (cr, 0.8);
af58ef32 332
9ede9c9c 333 *x1 = 0.10 * g->xdim;
6bf86008
SC
334 *x2 = 0.95 * g->xdim;
335 *y1 = 0.10 * g->ydim;
336 *y2 = 0.90 * g->ydim;
af58ef32
SC
337
338 cairo_move_to(cr, *x1, *y1);
339 cairo_line_to(cr, *x1, *y2);
340 cairo_line_to(cr, *x2, *y2);
341 cairo_line_to(cr, *x2, *y1);
342 cairo_line_to(cr, *x1, *y1);
343 cairo_stroke(cr);
344
f3e8440f
JA
345 draw_centered_text(g, cr, g->xdim / 2, g->ydim / 20, 20.0, g->title);
346 draw_centered_text(g, cr, g->xdim / 2, g->ydim * 0.97, 14.0, g->xtitle);
347 draw_vertical_centered_text(g, cr, g->xdim * 0.02, g->ydim / 2, 14.0, g->ytitle);
af58ef32
SC
348 cairo_stroke(cr);
349}
350
351static void graph_draw_x_ticks(struct graph *g, cairo_t *cr,
352 double x1, double y1, double x2, double y2,
d8fbeefb 353 double minx, double maxx, int nticks, int add_tm_text)
af58ef32
SC
354{
355 struct tickmark *tm;
356 double tx;
7175d91d 357 int i, power_of_ten;
af58ef32
SC
358 static double dash[] = { 1.0, 2.0 };
359
7175d91d 360 nticks = calc_tickmarks(minx, maxx, nticks, &tm, &power_of_ten,
d8fbeefb 361 g->x_axis_unit_change_callback == NULL, g->base_offset);
7175d91d
SC
362 if (g->x_axis_unit_change_callback)
363 g->x_axis_unit_change_callback(g, power_of_ten);
af58ef32
SC
364
365 for (i = 0; i < nticks; i++) {
366 tx = (((tm[i].value) - minx) / (maxx - minx)) * (x2 - x1) + x1;
d8fbeefb 367
b65c7ec4
JA
368 /*
369 * Update tick delta
370 */
371 if (!i) {
372 g->xtick_zero = tx;
373 g->xtick_zero_val = tm[0].value;
374 } else if (i == 1)
375 g->xtick_delta = (tm[1].value - tm[0].value) / (tx - g->xtick_zero);
376
d8fbeefb
JA
377 /* really tx < yx || tx > x2, but protect against rounding */
378 if (x1 - tx > 0.01 || tx - x2 > 0.01)
af58ef32
SC
379 continue;
380
381 /* Draw tick mark */
f3e8440f 382 cairo_set_line_width(cr, 0.8);
af58ef32
SC
383 cairo_move_to(cr, tx, y2);
384 cairo_line_to(cr, tx, y2 + (y2 - y1) * 0.03);
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, tx, y1);
392 cairo_line_to(cr, tx, y2);
393 cairo_stroke(cr);
394 cairo_restore(cr);
395
d8fbeefb
JA
396 if (!add_tm_text)
397 continue;
398
af58ef32 399 /* draw tickmark label */
f3e8440f 400 draw_centered_text(g, cr, tx, y2 * 1.04, 12.0, tm[i].string);
af58ef32 401 cairo_stroke(cr);
af58ef32
SC
402 }
403}
404
d8fbeefb 405static double graph_draw_y_ticks(struct graph *g, cairo_t *cr,
af58ef32 406 double x1, double y1, double x2, double y2,
d8fbeefb 407 double miny, double maxy, int nticks, int add_tm_text)
af58ef32
SC
408{
409 struct tickmark *tm;
410 double ty;
7175d91d 411 int i, power_of_ten;
af58ef32
SC
412 static double dash[] = { 2.0, 2.0 };
413
7175d91d 414 nticks = calc_tickmarks(miny, maxy, nticks, &tm, &power_of_ten,
d8fbeefb 415 g->y_axis_unit_change_callback == NULL, g->base_offset);
7175d91d
SC
416 if (g->y_axis_unit_change_callback)
417 g->y_axis_unit_change_callback(g, power_of_ten);
af58ef32 418
d8fbeefb
JA
419 /*
420 * Use highest tickmark as top of graph, not highest value. Otherwise
421 * it's impossible to see what the max value is, if the graph is
422 * fairly flat.
423 */
424 maxy = tm[nticks - 1].value;
425
af58ef32
SC
426 for (i = 0; i < nticks; i++) {
427 ty = y2 - (((tm[i].value) - miny) / (maxy - miny)) * (y2 - y1);
d8fbeefb 428
b65c7ec4
JA
429 /*
430 * Update tick delta
431 */
432 if (!i) {
433 g->ytick_zero = ty;
434 g->ytick_zero_val = tm[0].value;
435 } else if (i == 1)
436 g->ytick_delta = (tm[1].value - tm[0].value) / (ty - g->ytick_zero);
437
d8fbeefb
JA
438 /* really ty < y1 || ty > y2, but protect against rounding */
439 if (y1 - ty > 0.01 || ty - y2 > 0.01)
af58ef32 440 continue;
d8fbeefb 441
af58ef32
SC
442 /* draw tick mark */
443 cairo_move_to(cr, x1, ty);
444 cairo_line_to(cr, x1 - (x2 - x1) * 0.02, ty);
445 cairo_stroke(cr);
446
447 /* draw grid lines */
448 cairo_save(cr);
449 cairo_set_dash(cr, dash, 2, 2.0);
450 cairo_set_line_width(cr, 0.5);
451 cairo_move_to(cr, x1, ty);
452 cairo_line_to(cr, x2, ty);
453 cairo_stroke(cr);
454 cairo_restore(cr);
455
d8fbeefb
JA
456 if (!add_tm_text)
457 continue;
458
af58ef32 459 /* draw tickmark label */
10e54cdc 460 draw_right_justified_text(g, cr, x1 - (x2 - x1) * 0.025, ty, 12.0, tm[i].string);
af58ef32
SC
461 cairo_stroke(cr);
462 }
d8fbeefb
JA
463
464 /*
465 * Return new max to use
466 */
467 return maxy;
af58ef32
SC
468}
469
470void bar_graph_draw(struct graph *bg, cairo_t *cr)
471{
472 double x1, y1, x2, y2;
473 double space_per_label, bar_width;
474 double label_offset, mindata, maxdata;
475 int i, nlabels;
476 struct graph_label *lb;
477
478 cairo_save(cr);
57f9d28e 479 cairo_translate(cr, bg->xoffset, bg->yoffset);
af58ef32
SC
480 graph_draw_common(bg, cr, &x1, &y1, &x2, &y2);
481
482 nlabels = count_labels(bg->labels);
d8fbeefb 483 space_per_label = (x2 - x1) / (double) nlabels;
af58ef32 484
bb39379f
JA
485 /*
486 * Start bars at 0 unless we have negative values, otherwise we
487 * present a skewed picture comparing label X and X+1.
488 */
af58ef32 489 mindata = find_min_data(bg->labels);
bb39379f
JA
490 if (mindata > 0)
491 mindata = 0;
492
af58ef32
SC
493 maxdata = find_max_data(bg->labels);
494
495 if (fabs(maxdata - mindata) < 1e-20) {
f3e8440f 496 draw_centered_text(bg, cr,
af58ef32
SC
497 x1 + (x2 - x1) / 2.0,
498 y1 + (y2 - y1) / 2.0, 20.0, "No good data");
499 return;
500 }
501
bb39379f 502 maxdata = graph_draw_y_ticks(bg, cr, x1, y1, x2, y2, mindata, maxdata, 10, 1);
af58ef32
SC
503 i = 0;
504 for (lb = bg->labels; lb; lb = lb->next) {
505 int nvalues;
506 nvalues = count_values(lb->values);
507 bar_width = (space_per_label - space_per_label * 0.2) / (double) nvalues;
508 label_offset = bg->xdim * 0.1 + space_per_label * (double) i + space_per_label * 0.1;
509 draw_bars(bg, cr, lb, label_offset, bar_width, mindata, maxdata);
510 // draw_centered_text(cr, label_offset + (bar_width / 2.0 + bar_width * 0.1), bg->ydim * 0.93,
f3e8440f 511 draw_centered_text(bg, cr, x1 + space_per_label * (i + 0.5), bg->ydim * 0.93,
af58ef32
SC
512 12.0, lb->label);
513 i++;
514 }
515 cairo_stroke(cr);
516 cairo_restore(cr);
517}
518
519typedef double (*xy_value_extractor)(struct graph_value *v);
520
521static double getx(struct graph_value *v)
522{
523 struct xyvalue *xy = v->value;
524 return xy->x;
525}
526
527static double gety(struct graph_value *v)
528{
529 struct xyvalue *xy = v->value;
530 return xy->y;
531}
532
533static double find_xy_value(struct graph *g, xy_value_extractor getvalue, double_comparator cmp)
534{
10e54cdc 535 double tmp, answer = 0.0;
af58ef32
SC
536 struct graph_label *i;
537 struct graph_value *j;
d582bf70 538 int first = 1;
af58ef32
SC
539
540 for (i = g->labels; i; i = i->next)
541 for (j = i->values; j; j = j->next) {
542 tmp = getvalue(j);
d582bf70
SC
543 if (first) {
544 first = 0;
545 answer = tmp;
546 }
af58ef32
SC
547 answer = cmp(tmp, answer);
548 }
549 return answer;
550}
551
552void line_graph_draw(struct graph *g, cairo_t *cr)
553{
554 double x1, y1, x2, y2;
def0ac29
SC
555 double minx, miny, maxx, maxy, gminx, gminy, gmaxx, gmaxy;
556 double tx, ty, top_extra, bottom_extra, left_extra, right_extra;
af58ef32
SC
557 struct graph_label *i;
558 struct graph_value *j;
9ce9cfbd 559 int good_data = 1, first = 1;
af58ef32
SC
560
561 cairo_save(cr);
57f9d28e 562 cairo_translate(cr, g->xoffset, g->yoffset);
af58ef32
SC
563 graph_draw_common(g, cr, &x1, &y1, &x2, &y2);
564
565 minx = find_xy_value(g, getx, mindouble);
566 maxx = find_xy_value(g, getx, maxdouble);
567 miny = find_xy_value(g, gety, mindouble);
5aec6680
JA
568
569 /*
570 * Start graphs at zero, unless we have a value below. Otherwise
571 * it's hard to visually compare the read and write graph, since
572 * the lowest valued one will be the floor of the graph view.
573 */
574 if (miny > 0)
575 miny = 0;
576
af58ef32
SC
577 maxy = find_xy_value(g, gety, maxdouble);
578
579 if (fabs(maxx - minx) < 1e-20 || fabs(maxy - miny) < 1e-20) {
9ce9cfbd
SC
580 good_data = 0;
581 minx = 0.0;
582 miny = 0.0;
583 maxx = 10.0;
584 maxy = 100.0;
af58ef32
SC
585 }
586
def0ac29
SC
587 top_extra = 0.0;
588 bottom_extra = 0.0;
589 left_extra = 0.0;
590 right_extra = 0.0;
591
592 if (g->top_extra > 0.001)
593 top_extra = fabs(maxy - miny) * g->top_extra;
594 if (g->bottom_extra > 0.001)
595 bottom_extra = fabs(maxy - miny) * g->bottom_extra;
596 if (g->left_extra > 0.001)
597 left_extra = fabs(maxx - minx) * g->left_extra;
598 if (g->right_extra > 0.001)
599 right_extra = fabs(maxx - minx) * g->right_extra;
600
601 gminx = minx - left_extra;
602 gmaxx = maxx + right_extra;
603 gminy = miny - bottom_extra;
604 gmaxy = maxy + top_extra;
605
d8fbeefb
JA
606 graph_draw_x_ticks(g, cr, x1, y1, x2, y2, gminx, gmaxx, 10, good_data);
607 gmaxy = graph_draw_y_ticks(g, cr, x1, y1, x2, y2, gminy, gmaxy, 10, good_data);
af58ef32 608
9ce9cfbd
SC
609 if (!good_data)
610 goto skip_data;
611
f3e8440f 612 cairo_set_line_width(cr, 1.5);
af58ef32
SC
613 for (i = g->labels; i; i = i->next) {
614 first = 1;
cae08727
SC
615 if (i->r < 0) /* invisible data */
616 continue;
b65c7ec4 617
af58ef32
SC
618 cairo_set_source_rgb(cr, i->r, i->g, i->b);
619 for (j = i->values; j; j = j->next) {
def0ac29
SC
620 tx = ((getx(j) - gminx) / (gmaxx - gminx)) * (x2 - x1) + x1;
621 ty = y2 - ((gety(j) - gminy) / (gmaxy - gminy)) * (y2 - y1);
af58ef32
SC
622 if (first) {
623 cairo_move_to(cr, tx, ty);
624 first = 0;
625 } else {
626 cairo_line_to(cr, tx, ty);
627 }
628 }
629 cairo_stroke(cr);
630 }
9ce9cfbd
SC
631
632skip_data:
af58ef32 633 cairo_restore(cr);
9ce9cfbd 634
af58ef32
SC
635}
636
af58ef32
SC
637static void setstring(char **str, const char *value)
638{
b65c7ec4 639 free(*str);
af58ef32
SC
640 *str = strdup(value);
641}
642
643void graph_title(struct graph *bg, const char *title)
644{
645 setstring(&bg->title, title);
646}
647
648void graph_x_title(struct graph *bg, const char *title)
649{
650 setstring(&bg->xtitle, title);
651}
652
653void graph_y_title(struct graph *bg, const char *title)
654{
655 setstring(&bg->ytitle, title);
656}
657
658static struct graph_label *graph_find_label(struct graph *bg,
659 const char *label)
660{
661 struct graph_label *i;
662
663 for (i = bg->labels; i; i = i->next)
664 if (strcmp(label, i->label) == 0)
665 return i;
666 return NULL;
667}
668
669void graph_add_label(struct graph *bg, const char *label)
670{
671 struct graph_label *i;
672
673 i = graph_find_label(bg, label);
674 if (i)
675 return; /* already present. */
676 i = calloc(1, sizeof(*i));
677 i->parent = bg;
678 setstring(&i->label, label);
679 i->next = NULL;
680 if (!bg->tail)
681 bg->labels = i;
682 else
683 bg->tail->next = i;
684 bg->tail = i;
b65c7ec4 685 INIT_PRIO_TREE_ROOT(&i->prio_tree);
af58ef32
SC
686}
687
93e2db2b
JA
688static void graph_label_add_value(struct graph_label *i, void *value,
689 const char *tooltip)
af58ef32
SC
690{
691 struct graph_value *x;
692
693 x = malloc(sizeof(*x));
694 x->value = value;
93e2db2b
JA
695 if (tooltip)
696 x->tooltip = strdup(tooltip);
697 else
698 x->tooltip = NULL;
af58ef32
SC
699 x->next = NULL;
700 if (!i->tail) {
701 i->values = x;
702 } else {
703 i->tail->next = x;
704 }
705 i->tail = x;
706 i->value_count++;
b65c7ec4
JA
707
708 if (x->tooltip) {
709 double yval = gety(x);
710 double miny = yval / TOOLTIP_DELTA;
711 double maxy = yval * TOOLTIP_DELTA;
712
713 x->node.start = miny;
714 x->node.last = maxy;
715 if (x->node.last == x->node.start)
716 x->node.last++;
717
718 prio_tree_insert(&i->prio_tree, &x->node);
719 printf("insert (x=%u,y=%u) range %lu-%lu (%s)\n", (int)getx(x), (int)gety(x), x->node.start, x->node.last, x->tooltip);
93e2db2b 720 i->tooltip_count++;
b65c7ec4 721 }
af58ef32
SC
722
723 if (i->parent->per_label_limit != -1 &&
724 i->value_count > i->parent->per_label_limit) {
c148daed
JA
725 int to_drop = 1;
726
727 /*
728 * If the limit was dynamically reduced, making us more
729 * than 1 entry ahead after adding this one, drop two
730 * entries. This will make us (eventually) reach the
731 * specified limit.
732 */
733 if (i->value_count - i->parent->per_label_limit >= 2)
734 to_drop = 2;
735
736 while (to_drop--) {
737 x = i->values;
738 i->values = i->values->next;
93e2db2b
JA
739 if (x->tooltip) {
740 free(x->tooltip);
b65c7ec4 741 prio_tree_remove(&i->prio_tree, &x->node);
93e2db2b
JA
742 i->tooltip_count--;
743 }
c148daed
JA
744 free(x->value);
745 free(x);
746 i->value_count--;
747 }
af58ef32
SC
748 }
749}
750
751int graph_add_data(struct graph *bg, const char *label, const double value)
752{
753 struct graph_label *i;
754 double *d;
755
756 d = malloc(sizeof(*d));
757 *d = value;
758
759 i = graph_find_label(bg, label);
760 if (!i)
761 return -1;
93e2db2b 762 graph_label_add_value(i, d, NULL);
af58ef32
SC
763 return 0;
764}
765
766int graph_add_xy_data(struct graph *bg, const char *label,
93e2db2b 767 const double x, const double y, const char *tooltip)
af58ef32
SC
768{
769 struct graph_label *i;
770 struct xyvalue *xy;
771
772 xy = malloc(sizeof(*xy));
773 xy->x = x;
774 xy->y = y;
775
776 i = graph_find_label(bg, label);
777 if (!i)
778 return -1;
93e2db2b
JA
779
780 graph_label_add_value(i, xy, tooltip);
af58ef32
SC
781 return 0;
782}
783
784static void graph_free_values(struct graph_value *values)
785{
786 struct graph_value *i, *next;
787
788 for (i = values; i; i = next) {
789 next = i->next;
b65c7ec4
JA
790 free(i->value);
791 free(i);
af58ef32
SC
792 }
793}
794
795static void graph_free_labels(struct graph_label *labels)
796{
797 struct graph_label *i, *next;
798
799 for (i = labels; i; i = next) {
800 next = i->next;
801 graph_free_values(i->values);
b65c7ec4 802 free(i);
af58ef32
SC
803 }
804}
805
806void graph_set_color(struct graph *gr, const char *label,
807 double red, double green, double blue)
808{
809 struct graph_label *i;
810 double r, g, b;
811
cae08727
SC
812 if (red < 0.0) { /* invisible color */
813 r = -1.0;
814 g = -1.0;
815 b = -1.0;
816 } else {
817 r = fabs(red);
818 g = fabs(green);
819 b = fabs(blue);
820
821 if (r > 1.0)
822 r = 1.0;
823 if (g > 1.0)
824 g = 1.0;
825 if (b > 1.0)
b65c7ec4 826 b = 1.0;
cae08727 827 }
af58ef32
SC
828
829 for (i = gr->labels; i; i = i->next)
830 if (strcmp(i->label, label) == 0) {
831 i->r = r;
832 i->g = g;
833 i->b = b;
834 break;
835 }
836}
837
838void graph_free(struct graph *bg)
839{
b65c7ec4
JA
840 free(bg->title);
841 free(bg->xtitle);
842 free(bg->ytitle);
af58ef32
SC
843 graph_free_labels(bg->labels);
844}
845
846/* For each line in the line graph, up to per_label_limit segments may
847 * be added. After that, adding more data to the end of the line
848 * causes data to drop off of the front of the line.
849 */
850void line_graph_set_data_count_limit(struct graph *g, int per_label_limit)
851{
852 g->per_label_limit = per_label_limit;
853}
854
def0ac29
SC
855void graph_add_extra_space(struct graph *g, double left_percent, double right_percent,
856 double top_percent, double bottom_percent)
857{
858 g->left_extra = left_percent;
859 g->right_extra = right_percent;
860 g->top_extra = top_percent;
861 g->bottom_extra = bottom_percent;
862}
863
d8fbeefb
JA
864/*
865 * Normally values are logged in a base unit of 0, but for other purposes
866 * it makes more sense to log in higher unit. For instance for bandwidth
867 * purposes, you may want to log in KB/sec (or MB/sec) rather than bytes/sec.
868 */
869void graph_set_base_offset(struct graph *g, unsigned int base_offset)
870{
871 g->base_offset = base_offset;
872}
873
93e2db2b
JA
874int graph_has_tooltips(struct graph *g)
875{
876 struct graph_label *i;
877
878 for (i = g->labels; i; i = i->next)
879 if (i->tooltip_count)
880 return 1;
881
882 return 0;
883}
884
885int graph_contains_xy(struct graph *g, int x, int y)
886{
887 int first_x = g->xoffset;
888 int last_x = g->xoffset + g->xdim;
889 int first_y = g->yoffset;
890 int last_y = g->yoffset + g->ydim;
891
892 return (x >= first_x && x <= last_x) && (y >= first_y && y <= last_y);
893}
def0ac29 894
b65c7ec4 895const char *graph_find_tooltip(struct graph *g, int ix, int iy)
93e2db2b 896{
b65c7ec4
JA
897 double x = ix, y = iy;
898 struct prio_tree_iter iter;
899 struct prio_tree_node *n;
900 struct graph_label *i;
901 struct graph_value *best = NULL;
902 double best_delta;
903 double maxx, minx;
93e2db2b 904
b65c7ec4
JA
905 x -= g->xoffset;
906 y -= g->yoffset;
93e2db2b 907
b65c7ec4
JA
908 x = g->xtick_zero_val + ((x - g->xtick_zero) * g->xtick_delta);
909 y = g->ytick_zero_val + ((y - g->ytick_zero) * g->ytick_delta);
93e2db2b 910
b65c7ec4
JA
911 maxx = x * TOOLTIP_DELTA;
912 minx = x / TOOLTIP_DELTA;
913 best_delta = UINT_MAX;
914 i = g->labels;
915 do {
916 prio_tree_iter_init(&iter, &i->prio_tree, y, y);
917
918 n = prio_tree_next(&iter);
919 if (!n)
920 continue;
921
922 do {
923 struct graph_value *v;
924 double xval, xdiff;
925
926 v = container_of(n, struct graph_value, node);
927 xval = getx(v);
928
929 if (xval > x)
930 xdiff = xval - x;
931 else
932 xdiff = x - xval;
0deba507
JA
933
934 /*
b65c7ec4 935 * zero delta, or within or match critera, break
0deba507 936 */
b65c7ec4
JA
937 if (xdiff < best_delta) {
938 best_delta = xdiff;
939 if (!best_delta ||
940 (xval >= minx && xval <= maxx)) {
941 best = v;
942 break;
943 }
944 }
945 } while ((n = prio_tree_next(&iter)) != NULL);
946
947 /*
948 * If we got matches in one label, don't check others.
949 */
950 break;
951 } while ((i = i->next) != NULL);
952
953 if (best)
954 return best->tooltip;
93e2db2b
JA
955
956 return NULL;
957}