/* * gtk-valuegraph.c * * A widget to display a graph of a sequence of values * * (c) 2006-2007 Thomas White * * synth2d - two-dimensional Fourier synthesis * */ #include #include #include "gtk-valuegraph.h" static GtkObjectClass *parent_class = NULL; static void gtk_value_graph_destroy(GtkObject *gtk_value_graph) { parent_class->destroy(gtk_value_graph); } GtkWidget *gtk_value_graph_new() { GtkValueGraph *gtk_value_graph; gtk_value_graph = GTK_VALUE_GRAPH(gtk_type_new(gtk_value_graph_get_type())); gtk_value_graph->data = NULL; gtk_value_graph->n = 0; return GTK_WIDGET(gtk_value_graph); } static GObject *gtk_value_graph_constructor(GType type, guint n_construct_properties, GObjectConstructParam *construct_properties) { GtkValueGraphClass *class; GObjectClass *p_class; GObject *obj; class = GTK_VALUE_GRAPH_CLASS(g_type_class_peek(gtk_value_graph_get_type())); p_class = G_OBJECT_CLASS(g_type_class_peek_parent(class)); obj = p_class->constructor(type, n_construct_properties, construct_properties); return obj; } static void gtk_value_graph_class_init(GtkValueGraphClass *class) { GtkObjectClass *object_class; GObjectClass *g_object_class; object_class = (GtkObjectClass *) class; g_object_class = G_OBJECT_CLASS(class); object_class->destroy = gtk_value_graph_destroy; g_object_class->constructor = gtk_value_graph_constructor; parent_class = gtk_type_class(gtk_drawing_area_get_type()); } static gint gtk_value_graph_draw(GtkWidget *graph, GdkEventExpose *event, gpointer data) { GtkValueGraph *vg; unsigned int bw_left, bw_right, bw_top, bw_bottom; PangoLayout *y0_layout; PangoLayout *y1_layout; PangoLayout *x0_layout; PangoLayout *x1_layout; PangoRectangle y0_extent, y1_extent, x0_extent, x1_extent; unsigned int width, height; char tmp[32]; unsigned int i; vg = GTK_VALUE_GRAPH(graph); /* Blank white background */ gdk_draw_rectangle(graph->window, graph->style->white_gc, TRUE, 0, 0, graph->allocation.width, graph->allocation.height); /* Create PangoLayouts for labels */ y0_layout = gtk_widget_create_pango_layout(graph, "0"); pango_layout_get_pixel_extents(y0_layout, NULL, &y0_extent); if ( fabs(log(vg->ymax)/log(10)) < 3 ) { snprintf(tmp, 31, "%.4f", vg->ymax); } else { snprintf(tmp, 31, "%1.1e", vg->ymax); } y1_layout = gtk_widget_create_pango_layout(graph, tmp); pango_layout_get_pixel_extents(y1_layout, NULL, &y1_extent); x0_layout = gtk_widget_create_pango_layout(graph, "0"); pango_layout_get_pixel_extents(x0_layout, NULL, &x0_extent); if ( vg->xmax < 1000 ) { snprintf(tmp, 31, "%i", vg->xmax); } else { snprintf(tmp, 31, "%1.1e", (double)vg->xmax); } x1_layout = gtk_widget_create_pango_layout(graph, tmp); pango_layout_get_pixel_extents(x1_layout, NULL, &x1_extent); /* Determine border widths */ bw_left = 1+((y1_extent.width > y0_extent.width) ? y1_extent.width : y0_extent.width); bw_right = 1+x1_extent.width/2; bw_top = 1+y1_extent.height/2; bw_bottom = 1+((x1_extent.height > x0_extent.height) ? x1_extent.height : x0_extent.height); width = graph->allocation.width; height = graph->allocation.height; /* Draw axis lines */ gdk_draw_line(graph->window, graph->style->black_gc, bw_left, height-1-bw_bottom, bw_left, bw_top); gdk_draw_line(graph->window, graph->style->black_gc, bw_left, height-1-bw_bottom, width-1-bw_right, height-1-bw_bottom); /* Label axes */ gdk_draw_layout(graph->window, graph->style->black_gc, 1+bw_left-x0_extent.width/2, height-1-bw_bottom, x0_layout); gdk_draw_layout(graph->window, graph->style->black_gc, width-bw_right-x1_extent.width/2, height-1-bw_bottom, x1_layout); gdk_draw_layout(graph->window, graph->style->black_gc, bw_left-y0_extent.width-1, height-1-bw_bottom-y0_extent.height/2, y0_layout); gdk_draw_layout(graph->window, graph->style->black_gc, bw_left-y1_extent.width-1, 1, y1_layout); /* Plot data */ for ( i=0; in; i++ ) { unsigned int x, y; double xd, yd; xd = (((double)width-bw_left-bw_right)/(double)vg->xmax)*(double)(i+1); /* Graph axes go from 1 */ x = bw_left + xd; yd = (((double)height-bw_top-bw_bottom)/(double)vg->ymax)*(double)vg->data[i]; y = height-bw_bottom - yd; gdk_draw_point(graph->window, graph->style->black_gc, x, y); } return 0; } static void gtk_value_graph_init(GtkValueGraph *gtk_value_graph) { gtk_widget_set_size_request(GTK_WIDGET(gtk_value_graph), 100, 200); g_signal_connect(G_OBJECT(gtk_value_graph), "expose_event", G_CALLBACK(gtk_value_graph_draw), NULL); } guint gtk_value_graph_get_type(void) { static guint gtk_value_graph_type = 0; if ( !gtk_value_graph_type ) { GtkTypeInfo gtk_value_graph_info = { "GtkValueGraph", sizeof(GtkValueGraph), sizeof(GtkValueGraphClass), (GtkClassInitFunc) gtk_value_graph_class_init, (GtkObjectInitFunc) gtk_value_graph_init, NULL, NULL, (GtkClassInitFunc) NULL, }; gtk_value_graph_type = gtk_type_unique(gtk_drawing_area_get_type(), >k_value_graph_info); } return gtk_value_graph_type; } static double gtk_value_graph_peak(double *data, unsigned int n) { unsigned int i; double max; if ( n == 0 ) return 1; max = 0; for ( i=0; i max ) max = data[i]; } return max; } /* Calculate the best range for a axis with maximum value n */ static double gtk_value_graph_axis_max(double n) { double mantissa, exponent, test; return n; if ( n == 0 ) return 1; /* Convert to standard form */ exponent = rint(log(n)/log(10)); mantissa = n / pow(10, exponent); /* Check if the value can be exactly represented */ test = mantissa * 10; test = rint(test); test /= 10; if ( fabs(test - mantissa) > 0.001 ) { /* Round the mantissa upwards */ mantissa += 0.1; mantissa *= 10; mantissa = rint(mantissa); mantissa /= 10; } /* Else don't touch it */ return mantissa*pow(10, exponent); } void gtk_value_graph_set_data(GtkValueGraph *vg, double *data, unsigned int n) { double dmax; /* Recalculate axes */ dmax = gtk_value_graph_peak(data, n); vg->data = data; vg->n = n; vg->xmax = gtk_value_graph_axis_max(n); vg->ymax = gtk_value_graph_axis_max(dmax); //printf("n=%i, dmax=%f => xmax=%i, ymax=%f\n", n, dmax, vg->xmax, vg->ymax); /* Schedule redraw */ gtk_widget_queue_draw_area(GTK_WIDGET(vg), 0, 0, GTK_WIDGET(vg)->allocation.width, GTK_WIDGET(vg)->allocation.height); }