gfio: Add mini library to draw bar graphs and line graphs
authorStephen M. Cameron <stephenmcameron@gmail.com>
Wed, 7 Mar 2012 06:56:16 +0000 (07:56 +0100)
committerJens Axboe <axboe@kernel.dk>
Wed, 7 Mar 2012 06:56:16 +0000 (07:56 +0100)
Signed-off-by: Stephen M. Cameron <stephenmcameron@gmail.com>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
Makefile
graph.c [new file with mode: 0644]
graph.h [new file with mode: 0644]
tickmarks.c [new file with mode: 0644]
tickmarks.h [new file with mode: 0644]

index 8bd61863bf9807b4f9df12c3513c540c54efb182..94617688d4ecf2845a6d172b36856bec9ce2ecc4 100644 (file)
--- 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 (file)
index 0000000..3e76fa9
--- /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 <stephenmcameron@gmail.com> 
+ *
+ * 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 <string.h>
+#include <malloc.h>
+#include <math.h>
+#include <assert.h>
+
+#include <cairo.h>
+#include <gtk/gtk.h>
+
+#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 (file)
index 0000000..57c5bd3
--- /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 (file)
index 0000000..7766acd
--- /dev/null
@@ -0,0 +1,96 @@
+#include <stdio.h>
+#include <math.h>
+#include <malloc.h>
+
+/* 
+ * 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 (file)
index 0000000..ab817a7
--- /dev/null
@@ -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