From af58ef32b7398d791168af54aa4aab0b23192f90 Mon Sep 17 00:00:00 2001 From: "Stephen M. Cameron" Date: Wed, 7 Mar 2012 07:56:16 +0100 Subject: [PATCH] gfio: Add mini library to draw bar graphs and line graphs Signed-off-by: Stephen M. Cameron Signed-off-by: Jens Axboe --- Makefile | 5 +- graph.c | 626 ++++++++++++++++++++++++++++++++++++++++++++++++++++ graph.h | 23 ++ tickmarks.c | 96 ++++++++ tickmarks.h | 11 + 5 files changed, 760 insertions(+), 1 deletion(-) create mode 100644 graph.c create mode 100644 graph.h create mode 100644 tickmarks.c create mode 100644 tickmarks.h diff --git a/Makefile b/Makefile index 8bd61863..94617688 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ endif OBJS = $(SOURCE:.c=.o) FIO_OBJS = $(OBJS) fio.o -GFIO_OBJS = $(OBJS) gfio.o +GFIO_OBJS = $(OBJS) gfio.o graph.o tickmarks.o T_SMALLOC_OBJS = t/stest.o T_SMALLOC_OBJS += mutex.o smalloc.o t/log.o @@ -106,6 +106,9 @@ all: .depend $(PROGS) $(SCRIPTS) gfio.o: gfio.c $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c gfio.c +graph.o: graph.c graph.h + $(QUIET_CC)$(CC) $(CFLAGS) $(GTK_CFLAGS) $(CPPFLAGS) -c graph.c + t/stest: $(T_SMALLOC_OBJS) $(QUIET_CC)$(CC) $(LDFLAGS) $(CFLAGS) -o $@ $(T_SMALLOC_OBJS) $(LIBS) $(LDFLAGS) diff --git a/graph.c b/graph.c new file mode 100644 index 00000000..3e76fa91 --- /dev/null +++ b/graph.c @@ -0,0 +1,626 @@ +/* + * gfio - gui front end for fio - the flexible io tester + * + * Copyright (C) 2012 Stephen M. Cameron + * + * The license below covers all files distributed with fio unless otherwise + * noted in the file itself. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include +#include + +#include +#include + +#include "tickmarks.h" + +struct xyvalue { + double x, y; +}; + +struct graph_value { + struct graph_value *next; + void *value; +}; + +struct graph_label { + char *label; + struct graph_value *tail; + struct graph_value *values; + struct graph_label *next; + double r, g, b; + int value_count; + struct graph *parent; +}; + +struct graph { + char *title; + char *xtitle; + char *ytitle; + int xdim, ydim; + struct graph_label *labels; + struct graph_label *tail; + int per_label_limit; +}; + +struct graph *graph_new(int xdim, int ydim) +{ + struct graph *g; + + g = calloc(1, sizeof(*g)); + g->xdim = xdim; + g->ydim = ydim; + g->per_label_limit = -1; + return g; +} + +static int count_labels(struct graph_label *labels) +{ + int count = 0; + struct graph_label *i; + + for (i = labels; i; i = i->next) + count++; + return count; +} + +static int count_values(struct graph_value *values) +{ + int count = 0; + struct graph_value *i; + + for (i = values; i; i = i->next) + count++; + return count; +} + +typedef double (*double_comparator)(double a, double b); + +static double mindouble(double a, double b) +{ + return a < b ? a : b; +} + +static double maxdouble(double a, double b) +{ + return a < b ? b : a; +} + +static double find_double_values(struct graph_value *values, double_comparator cmp) +{ + struct graph_value *i; + int first = 1; + double answer, tmp; + + assert(values != NULL); + answer = 0.0; /* shut the compiler up, might need to think harder though. */ + for (i = values; i; i = i->next) { + tmp = *(double *) i->value; + if (first) { + answer = tmp; + first = 0; + } else { + answer = cmp(answer, tmp); + } + } + return answer; +} + +static double find_double_data(struct graph_label *labels, double_comparator cmp) +{ + struct graph_label *i; + int first = 1; + double answer, tmp; + + assert(labels != NULL); + answer = 0.0; /* shut the compiler up, might need to think harder though. */ + for (i = labels; i; i = i->next) { + tmp = find_double_values(i->values, cmp); + if (first) { + answer = tmp; + first = 0; + } else { + answer = cmp(tmp, answer); + } + } + return answer; +} + +static double find_min_data(struct graph_label *labels) +{ + return find_double_data(labels, mindouble); +} + +static double find_max_data(struct graph_label *labels) +{ + return find_double_data(labels, maxdouble); +} + +static void draw_bars(struct graph *bg, cairo_t *cr, struct graph_label *lb, + double label_offset, double bar_width, + double mindata, double maxdata) +{ + struct graph_value *i; + double x1, y1, x2, y2; + int bar_num = 0; + double domain, range, v; + + domain = (maxdata - mindata); + range = (double) bg->ydim * 0.80; /* FIXME */ + cairo_stroke(cr); + for (i = lb->values; i; i = i->next) { + + x1 = label_offset + (double) bar_num * bar_width + (bar_width * 0.05); + x2 = x1 + bar_width * 0.90; + y2 = bg->ydim * 0.90; + v = *(double *) i->value; + y1 = y2 - (((v - mindata) / domain) * range); + cairo_move_to(cr, x1, y1); + cairo_line_to(cr, x1, y2); + cairo_line_to(cr, x2, y2); + cairo_line_to(cr, x2, y1); + cairo_close_path(cr); + cairo_fill(cr); + cairo_stroke(cr); + bar_num++; + } +} + +static void draw_centered_text(cairo_t *cr, double x, double y, + double fontsize, const char *text) +{ + cairo_text_extents_t extents; + + cairo_select_font_face (cr, "Sans", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + + cairo_set_font_size(cr, fontsize); + cairo_text_extents(cr, text, &extents); + x = x - (extents.width / 2 + extents.x_bearing); + y = y - (extents.height / 2 + extents.y_bearing); + + cairo_move_to(cr, x, y); + cairo_show_text(cr, text); +} + +static void draw_vertical_centered_text(cairo_t *cr, double x, double y, + double fontsize, const char *text) +{ + double sx, sy; + cairo_text_extents_t extents; + + cairo_select_font_face (cr, "Sans", + CAIRO_FONT_SLANT_NORMAL, + CAIRO_FONT_WEIGHT_NORMAL); + + cairo_set_font_size(cr, fontsize); + cairo_text_extents(cr, text, &extents); + sx = x; + sy = y; + y = y + (extents.width / 2.0 + extents.x_bearing); + x = x - (extents.height / 2.0 + extents.y_bearing); + + cairo_move_to(cr, x, y); + cairo_save(cr); + cairo_translate(cr, -sx, -sy); + cairo_rotate(cr, -90.0 * M_PI / 180.0); + cairo_translate(cr, sx, sy); + cairo_show_text(cr, text); + cairo_restore(cr); +} + +static void graph_draw_common(struct graph *g, cairo_t *cr, + double *x1, double *y1, double *x2, double *y2) +{ + cairo_set_source_rgb(cr, 0, 0, 0); + cairo_set_line_width (cr, 1.0); + + cairo_move_to(cr, 0, 0); + cairo_line_to(cr, 0, g->ydim); + cairo_line_to(cr, g->xdim, g->ydim); + cairo_line_to(cr, g->xdim, 0); + cairo_line_to(cr, 0, 0); + + /* for now just set margins at 10% of width. This is not very good. */ + *x1 = g->xdim / 10.0; + *x2 = 9.0 * *x1; + *y1 = g->ydim / 10.0; + *y2 = 9.0 * *y1; + + cairo_move_to(cr, *x1, *y1); + cairo_line_to(cr, *x1, *y2); + cairo_line_to(cr, *x2, *y2); + cairo_line_to(cr, *x2, *y1); + cairo_line_to(cr, *x1, *y1); + cairo_stroke(cr); + + draw_centered_text(cr, g->xdim / 2, g->ydim / 20, 20.0, g->title); + draw_centered_text(cr, g->xdim / 2, g->ydim * 0.97, 14.0, g->xtitle); + draw_vertical_centered_text(cr, g->xdim * 0.02, g->ydim / 2, 14.0, g->ytitle); + cairo_stroke(cr); +} + +static void graph_draw_x_ticks(struct graph *g, cairo_t *cr, + double x1, double y1, double x2, double y2, + double minx, double maxx, int nticks) +{ + struct tickmark *tm; + double tx; + int i; + static double dash[] = { 1.0, 2.0 }; + + nticks = calc_tickmarks(minx, maxx, nticks, &tm); + + for (i = 0; i < nticks; i++) { + tx = (((tm[i].value) - minx) / (maxx - minx)) * (x2 - x1) + x1; + if (tx < x1 || tx > x2) + continue; + + /* Draw tick mark */ + cairo_set_line_width(cr, 1.0); + cairo_move_to(cr, tx, y2); + cairo_line_to(cr, tx, y2 + (y2 - y1) * 0.03); + cairo_stroke(cr); + + /* draw grid lines */ + cairo_save(cr); + cairo_set_dash(cr, dash, 2, 2.0); + cairo_set_line_width(cr, 0.5); + cairo_move_to(cr, tx, y1); + cairo_line_to(cr, tx, y2); + cairo_stroke(cr); + cairo_restore(cr); + + /* draw tickmark label */ + draw_centered_text(cr, tx, y2 * 1.04, 12.0, tm[i].string); + cairo_stroke(cr); + + } +} + +static void graph_draw_y_ticks(struct graph *g, cairo_t *cr, + double x1, double y1, double x2, double y2, + double miny, double maxy, int nticks) +{ + struct tickmark *tm; + double ty; + int i; + static double dash[] = { 2.0, 2.0 }; + + nticks = calc_tickmarks(miny, maxy, nticks, &tm); + + for (i = 0; i < nticks; i++) { + ty = y2 - (((tm[i].value) - miny) / (maxy - miny)) * (y2 - y1); + if (ty < y1 || ty > y2) + continue; + /* draw tick mark */ + cairo_move_to(cr, x1, ty); + cairo_line_to(cr, x1 - (x2 - x1) * 0.02, ty); + cairo_stroke(cr); + + /* draw grid lines */ + cairo_save(cr); + cairo_set_dash(cr, dash, 2, 2.0); + cairo_set_line_width(cr, 0.5); + cairo_move_to(cr, x1, ty); + cairo_line_to(cr, x2, ty); + cairo_stroke(cr); + cairo_restore(cr); + + /* draw tickmark label */ + draw_centered_text(cr, x1 - (x2 - x1) * 0.04, ty, 12.0, tm[i].string); + cairo_stroke(cr); + } +} + +void bar_graph_draw(struct graph *bg, cairo_t *cr) +{ + double x1, y1, x2, y2; + double space_per_label, bar_width; + double label_offset, mindata, maxdata; + int i, nlabels; + struct graph_label *lb; + + cairo_save(cr); + graph_draw_common(bg, cr, &x1, &y1, &x2, &y2); + + nlabels = count_labels(bg->labels); + space_per_label = (x2 - x1) / (double) nlabels; + + mindata = find_min_data(bg->labels); + maxdata = find_max_data(bg->labels); + + if (fabs(maxdata - mindata) < 1e-20) { + draw_centered_text(cr, + x1 + (x2 - x1) / 2.0, + y1 + (y2 - y1) / 2.0, 20.0, "No good data"); + return; + } + + graph_draw_y_ticks(bg, cr, x1, y1, x2, y2, mindata, maxdata, 10); + + i = 0; + for (lb = bg->labels; lb; lb = lb->next) { + int nvalues; + nvalues = count_values(lb->values); + bar_width = (space_per_label - space_per_label * 0.2) / (double) nvalues; + label_offset = bg->xdim * 0.1 + space_per_label * (double) i + space_per_label * 0.1; + draw_bars(bg, cr, lb, label_offset, bar_width, mindata, maxdata); + // draw_centered_text(cr, label_offset + (bar_width / 2.0 + bar_width * 0.1), bg->ydim * 0.93, + draw_centered_text(cr, x1 + space_per_label * (i + 0.5), bg->ydim * 0.93, + 12.0, lb->label); + i++; + } + cairo_stroke(cr); + cairo_restore(cr); +} + +typedef double (*xy_value_extractor)(struct graph_value *v); + +static double getx(struct graph_value *v) +{ + struct xyvalue *xy = v->value; + return xy->x; +} + +static double gety(struct graph_value *v) +{ + struct xyvalue *xy = v->value; + return xy->y; +} + +static double find_xy_value(struct graph *g, xy_value_extractor getvalue, double_comparator cmp) +{ + double tmp, answer = 0.0; + struct graph_label *i; + struct graph_value *j; + + for (i = g->labels; i; i = i->next) + for (j = i->values; j; j = j->next) { + tmp = getvalue(j); + answer = cmp(tmp, answer); + } + return answer; +} + +void line_graph_draw(struct graph *g, cairo_t *cr) +{ + double x1, y1, x2, y2; + double minx, miny, maxx, maxy; + double tx, ty; + struct graph_label *i; + struct graph_value *j; + int first = 1; + + cairo_save(cr); + graph_draw_common(g, cr, &x1, &y1, &x2, &y2); + + minx = find_xy_value(g, getx, mindouble); + maxx = find_xy_value(g, getx, maxdouble); + miny = find_xy_value(g, gety, mindouble); + maxy = find_xy_value(g, gety, maxdouble); + + if (fabs(maxx - minx) < 1e-20 || fabs(maxy - miny) < 1e-20) { + draw_centered_text(cr, + x1 + (x2 - x1) / 2.0, + y1 + (y2 - y1) / 2.0, 20.0, "No good Data"); + return; + } + + graph_draw_x_ticks(g, cr, x1, y1, x2, y2, minx, maxx, 10); + graph_draw_y_ticks(g, cr, x1, y1, x2, y2, miny, maxy, 10); + + cairo_set_line_width(cr, 2.5); + for (i = g->labels; i; i = i->next) { + first = 1; + cairo_set_source_rgb(cr, i->r, i->g, i->b); + for (j = i->values; j; j = j->next) { + tx = ((getx(j) - minx) / (maxx - minx)) * (x2 - x1) + x1; + ty = y2 - ((gety(j) - miny) / (maxy - miny)) * (y2 - y1); + if (first) { + cairo_move_to(cr, tx, ty); + first = 0; + } else { + cairo_line_to(cr, tx, ty); + } + } + cairo_stroke(cr); + } + cairo_restore(cr); +} + +static void gfree(void *f) +{ + if (f) + free(f); +} + +static void setstring(char **str, const char *value) +{ + gfree(*str); + *str = strdup(value); +} + +void graph_title(struct graph *bg, const char *title) +{ + setstring(&bg->title, title); +} + +void graph_x_title(struct graph *bg, const char *title) +{ + setstring(&bg->xtitle, title); +} + +void graph_y_title(struct graph *bg, const char *title) +{ + setstring(&bg->ytitle, title); +} + +static struct graph_label *graph_find_label(struct graph *bg, + const char *label) +{ + struct graph_label *i; + + for (i = bg->labels; i; i = i->next) + if (strcmp(label, i->label) == 0) + return i; + return NULL; +} + +void graph_add_label(struct graph *bg, const char *label) +{ + struct graph_label *i; + + i = graph_find_label(bg, label); + if (i) + return; /* already present. */ + i = calloc(1, sizeof(*i)); + i->parent = bg; + setstring(&i->label, label); + i->next = NULL; + if (!bg->tail) + bg->labels = i; + else + bg->tail->next = i; + bg->tail = i; +} + +static void graph_label_add_value(struct graph_label *i, void *value) +{ + struct graph_value *x; + + x = malloc(sizeof(*x)); + x->value = value; + x->next = NULL; + if (!i->tail) { + i->values = x; + } else { + i->tail->next = x; + } + i->tail = x; + i->value_count++; + + if (i->parent->per_label_limit != -1 && + i->value_count > i->parent->per_label_limit) { + x = i->values; + i->values = i->values->next; + free(x->value); + free(x); + i->value_count--; + } +} + +int graph_add_data(struct graph *bg, const char *label, const double value) +{ + struct graph_label *i; + double *d; + + d = malloc(sizeof(*d)); + *d = value; + + i = graph_find_label(bg, label); + if (!i) + return -1; + graph_label_add_value(i, d); + return 0; +} + +int graph_add_xy_data(struct graph *bg, const char *label, + const double x, const double y) +{ + struct graph_label *i; + struct xyvalue *xy; + + xy = malloc(sizeof(*xy)); + xy->x = x; + xy->y = y; + + i = graph_find_label(bg, label); + if (!i) + return -1; + graph_label_add_value(i, xy); + return 0; +} + +static void graph_free_values(struct graph_value *values) +{ + struct graph_value *i, *next; + + for (i = values; i; i = next) { + next = i->next; + gfree(i->value); + gfree(i); + } +} + +static void graph_free_labels(struct graph_label *labels) +{ + struct graph_label *i, *next; + + for (i = labels; i; i = next) { + next = i->next; + graph_free_values(i->values); + gfree(i); + } +} + +void graph_set_color(struct graph *gr, const char *label, + double red, double green, double blue) +{ + struct graph_label *i; + double r, g, b; + + r = fabs(red); + g = fabs(green); + b = fabs(blue); + + if (r > 1.0) + r = 1.0; + if (g > 1.0) + g = 1.0; + if (b > 1.0) + b =1.0; + + for (i = gr->labels; i; i = i->next) + if (strcmp(i->label, label) == 0) { + i->r = r; + i->g = g; + i->b = b; + break; + } +} + +void graph_free(struct graph *bg) +{ + gfree(bg->title); + gfree(bg->xtitle); + gfree(bg->ytitle); + graph_free_labels(bg->labels); +} + +/* For each line in the line graph, up to per_label_limit segments may + * be added. After that, adding more data to the end of the line + * causes data to drop off of the front of the line. + */ +void line_graph_set_data_count_limit(struct graph *g, int per_label_limit) +{ + g->per_label_limit = per_label_limit; +} + diff --git a/graph.h b/graph.h new file mode 100644 index 00000000..57c5bd32 --- /dev/null +++ b/graph.h @@ -0,0 +1,23 @@ +#ifndef GRAPH_H +#define GRAPH_H + +struct graph; + +struct graph *graph_new(int xdim, int ydim); +void bar_graph_draw(struct graph *g, cairo_t *cr); +void line_graph_draw(struct graph *g, cairo_t *cr); +void line_graph_set_data_count_limit(struct graph *g, int per_label_limit); +void graph_title(struct graph *g, const char *title); +void graph_x_title(struct graph *g, const char *title); +void graph_y_title(struct graph *g, const char *title); +void graph_add_label(struct graph *g, const char *label); +void graph_add_data(struct graph *g, const char *label, const double value); +void graph_add_xy_data(struct graph *g, const char *label, + const double x, const double y); +void graph_set_color(struct graph *g, const char *label, + double red, double green, double blue); +void graph_free(struct graph *bg); + + +#endif + diff --git a/tickmarks.c b/tickmarks.c new file mode 100644 index 00000000..7766acd2 --- /dev/null +++ b/tickmarks.c @@ -0,0 +1,96 @@ +#include +#include +#include + +/* + * adapted from Paul Heckbert's algorithm on p 657-659 of + * Andrew S. Glassner's book, "Graphics Gems" + * ISBN 0-12-286166-3 + * + */ + +#include "tickmarks.h" + +#define MAX(a, b) (((a) < (b)) ? (b) : (a)) + +static double nicenum(double x, int round) +{ + int exp; /* exponent of x */ + double f; /* fractional part of x */ + + exp = floor(log10(x)); + f = x / pow(10.0, exp); + if (round) { + if (f < 1.5) + return 1.0 * pow(10.0, exp); + if (f < 3.0) + return 2.0 * pow(10.0, exp); + if (f < 7.0) + return 5.0 * pow(10.0, exp); + return 10.0 * pow(10.0, exp); + } + if (f <= 1.0) + return 1.0 * pow(10.0, exp); + if (f <= 2.0) + return 2.0 * pow(10.0, exp); + if (f <= 5.0) + return 5.0 * pow(10.0, exp); + return 10.0 * pow(10.0, exp); +} + +int calc_tickmarks(double min, double max, int nticks, struct tickmark **tm) +{ + char str[100]; + int nfrac; + double d; /* tick mark spacing */ + double graphmin, graphmax; /* graph range min and max */ + double range, x; + int count, i; + + /* we expect min != max */ + range = nicenum(max - min, 0); + d = nicenum(range / (nticks - 1), 1); + graphmin = floor(min / d) * d; + graphmax = ceil(max / d) * d; + nfrac = MAX(-floor(log10(d)), 0); + snprintf(str, sizeof(str)-1, "%%.%df", nfrac); + + count = ((graphmax + 0.5 * d) - graphmin) / d + 1; + *tm = malloc(sizeof(**tm) * count); + + i = 0; + for (x = graphmin; x < graphmax + 0.5 * d; x += d) { + (*tm)[i].value = x; + sprintf((*tm)[i].string, str, x); + i++; + } + return i; +} + +#if 0 + +static void test_range(double x, double y) +{ + int nticks, i; + + struct tickmark *tm = NULL; + printf("Testing range %g - %g\n", x, y); + nticks = calc_tickmarks(x, y, 10, &tm); + + for (i = 0; i < nticks; i++) { + printf(" (%s) %g\n", tm[i].string, tm[i].value); + } + printf("\n\n"); + free(tm); +} + +int main(int argc, char *argv[]) +{ + test_range(0.0005, 0.008); + test_range(0.5, 0.8); + test_range(5.5, 8.8); + test_range(50.5, 80.8); + test_range(-20, 20.8); + test_range(-30, 700.8); +} +#endif diff --git a/tickmarks.h b/tickmarks.h new file mode 100644 index 00000000..ab817a76 --- /dev/null +++ b/tickmarks.h @@ -0,0 +1,11 @@ +#ifndef TICKMARKS_H +#define TICKMARKS_H + +struct tickmark { + double value; + char string[20]; +}; + +int calc_tickmarks(double min, double max, int nticks, struct tickmark **tm); + +#endif -- 2.25.1