From f38364b3004fb77dd8ebd6c6a37c66873f37e2b2 Mon Sep 17 00:00:00 2001 From: Carl Worth Date: Thu, 7 Nov 2013 13:24:30 -0800 Subject: [PATCH] Add support for a new TIMELINE style for drawing a data-set. For the TIMELINE style, each (X,Y) pair represents (start,stop) time in the X dimension for a timeline bar to be drawn. The recently added "_named" variant of add_data is also useful for supplying a name to be drawn on the bar. With multiple timelines within a chart, each one is positioned at a different integer position along the Y axis, (and the Y axis range computation is changed to preserve this). There's only minimal cleverness to avoid collision of timeline label, (the vertical position is cycled unconditionally). More clever approaches would not change the vertical position if there is no possibility of conflict, and would also reset to the top position as soon as possible. Other potential improvements to the TIMELINE style include: * Ensuring that a timeline label is visible whenever any portion of a timeline bar is visible. * Eliminating the rendering of numbers and ticks on the Y axis. --- acre.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ acre.h | 13 +++++++- 2 files changed, 106 insertions(+), 1 deletion(-) diff --git a/acre.c b/acre.c index 8e7f1b1..4e00bee 100644 --- a/acre.c +++ b/acre.c @@ -328,6 +328,20 @@ acre_add_data (acre_t *acre, acre_data_t *data) acre->data[acre->num_data] = data; + /* For timeline datasets, the X and Y ranges need to be + * adjusted. The desired X range is the min/max of the X and Y + * ranges, and the desired Y range has a size of 1.0 (centered + * around the dataset's index) */ + if (data->style == ACRE_STYLE_TIMELINE) { + if (data->min.y < data->min.x) + data->min.x = data->min.y; + if (data->max.y > data->max.x) + data->max.x = data->max.y; + + data->min.y = acre->num_data -0.5; + data->max.y = acre->num_data + 0.5; + } + if (acre->num_data == 0) { acre->x_axis.data_min = data->min.x; acre->y_axis.data_min = data->min.y; @@ -669,6 +683,11 @@ _find_y_range_given_x_range (acre_t *acre, for (d = 0; d < acre->num_data; d++) { data = acre->data[d]; + + /* Never mess with the Y range for timeline data. */ + if (data->style == ACRE_STYLE_TIMELINE) + continue; + for (i = 0; i < data->num_points; i++) { if (data->points[i].x >= x_min && data->points[i].x <= x_max) @@ -827,6 +846,71 @@ _draw_data_line (acre_t *acre, acre_data_t *data) cairo_restore (cr); } +#define TIMELINE_BAR_HEIGHT 0.6 + +/* Draw the given dataset as a timeline. Each (X,Y) point (potentially + * with a name) specifies the (start,stop) of a single timeline bar. + * + * Each independent timeline dataset in the chart is given its own + * vertical position, as specified by 'y_position'. + */ +static void +_draw_data_timeline (acre_t *acre, acre_data_t *data, int y_position) +{ + unsigned i; + cairo_t *cr = acre->cr; + PangoLayout *timeline_label_layout; + double ignored, label_offset; + int labels_within_bar; + + cairo_save (cr); + + timeline_label_layout = _create_layout (acre, "Timeline"); + pango_layout_set_font_description (timeline_label_layout, acre->font); + + ignored = 0.0; + label_offset = ACRE_FONT_SIZE; + cairo_device_to_user_distance (cr, &ignored, &label_offset); + + labels_within_bar = TIMELINE_BAR_HEIGHT / fabs (label_offset); + + for (i = 0; i < data->num_points; i++) { + cairo_rectangle (cr, + data->points[i].x, + y_position - TIMELINE_BAR_HEIGHT / 2.0, + data->points[i].y - data->points[i].x, + TIMELINE_BAR_HEIGHT); + + cairo_save (cr); + cairo_identity_matrix (cr); + cairo_set_line_width (cr, 1.0); + cairo_stroke_preserve (cr); + cairo_restore (cr); + + cairo_new_path (cr); + + if (i <= data->num_names && data->names[i]) { + cairo_save (cr); + + cairo_move_to (cr, data->points[i].x, + y_position + TIMELINE_BAR_HEIGHT / 2.0 + + (i % labels_within_bar) * label_offset); + pango_layout_set_text (timeline_label_layout, data->names[i], -1); + cairo_identity_matrix (cr); + pango_cairo_show_layout (cr, timeline_label_layout); + + cairo_restore (cr); + } else { + cairo_new_path (cr); + } + + } + + _destroy_layout (timeline_label_layout); + + cairo_restore (cr); +} + /* Draw all the datasets of the chart. */ static void _draw_data (acre_t *acre) @@ -858,6 +942,10 @@ _draw_data (acre_t *acre) case ACRE_STYLE_LINE: _draw_data_line (acre, data); break; + case ACRE_STYLE_TIMELINE: + /* Position the timeline bars top-down */ + _draw_data_timeline (acre, data, acre->num_data - 1 - i); + break; } } @@ -1113,6 +1201,12 @@ acre_data_destroy (acre_data_t *data) free (data); } +void +acre_data_set_style (acre_data_t *data, acre_style_t style) +{ + data->style = style; +} + /* Set the label for this dataset (to appear in the plot's key). */ void acre_data_set_name (acre_data_t *data, const char *name) diff --git a/acre.h b/acre.h index 6bad89a..9611da3 100644 --- a/acre.h +++ b/acre.h @@ -115,7 +115,18 @@ acre_data_destroy (acre_data_t *data); /* The style to be used for rendering data sets. */ typedef enum { - ACRE_STYLE_LINE + /* A simple line graph connection each (X,Y) pair in order. */ + ACRE_STYLE_LINE, + + /* A timeline. + * + * Each (X,Y) pair specifies the (start,stop) time of a + * timeline bar. + * + * The acre_data_add_point_2d_named function is particularly + * useful to provide a name for each timeline bar. + */ + ACRE_STYLE_TIMELINE } acre_style_t; /* Set the rendering style for this dataset. */ -- 2.43.0