#include <string.h>
#include <stdarg.h>
+#include <stdbool.h>
#include <math.h>
+#include <lcms.h>
+
typedef struct _acre_data_point_2d {
double x;
double y;
} acre_data_point_2d_t;
struct _acre_data {
+ /* The name of this data set. */
char *name;
+ /* The style for rendering (line, bar, etc.) */
+ acre_style_t style;
+
+ /* Minimum and mximum extents of data. */
+ acre_data_point_2d_t min;
+ acre_data_point_2d_t max;
+
+ /* The data itself. */
acre_data_point_2d_t *points;
unsigned int points_size;
unsigned int num_points;
+
+ /* The names of data points (if any).
+ *
+ * This array is indexed with the same index as the 'points' array
+ * to provide names for points. It is legal for this array to be
+ * NULL or smaller than 'points', (in which case, the
+ * corresponding points simply have no names).
+ */
+ char **names;
+ unsigned int names_size;
+ unsigned int num_names;
};
typedef struct _acre_axis {
char *label;
- double min;
- double max;
+
+ /* Range of data */
+ double data_min;
+ double data_max;
+
+ /* Range of data to be viewed. */
+ double view_min;
+ double view_max;
+
+ /* Has the view range been set? */
+ bool view_range_set;
} acre_axis_t;
+typedef struct _acre_color {
+ double red;
+ double green;
+ double blue;
+} acre_color_t;
+
struct _acre {
char *title;
acre_axis_t x_axis;
/* Data for drawing. */
cairo_t *cr;
PangoFontDescription *font;
+ acre_color_t *colors;
+ int colors_size;
+ int num_colors;
/* Total size including labels. */
int width;
PangoRectangle chart;
};
+static void
+_find_x_range_given_y_range (acre_t *acre,
+ double *x_min, double *x_max,
+ double y_min, double y_max);
+
+static void
+_find_y_range_given_x_range (acre_t *acre,
+ double *y_min, double *y_max,
+ double x_min, double x_max);
+
/* Create a new, empty plot. */
acre_t *
acre_create (void)
acre->title = NULL;
acre->x_axis.label = NULL;
- acre->x_axis.min = 0.0;
- acre->x_axis.max = 0.0;
+ acre->x_axis.data_min = 0.0;
+ acre->x_axis.data_max = 0.0;
+ acre->x_axis.view_min = 0.0;
+ acre->x_axis.view_max = 0.0;
+ acre->x_axis.view_range_set = false;
acre->y_axis.label = NULL;
- acre->y_axis.min = 0.0;
- acre->y_axis.max = 0.0;
+ acre->y_axis.data_min = 0.0;
+ acre->y_axis.data_max = 0.0;
+ acre->y_axis.view_min = 0.0;
+ acre->y_axis.view_max = 0.0;
+ acre->y_axis.view_range_set = false;
acre->data = NULL;
acre->data_size = 0;
acre->num_data = 0;
+ acre->cr = NULL;
+ acre->font = NULL;
+ acre->colors = NULL;
+ acre->num_colors = 0;
+ acre->colors_size = 0;
+
+ acre->width = 0;
+ acre->height = 0;
+
acre->chart.x = 0;
acre->chart.y = 0;
acre->chart.width = 0;
free (acre->data);
+ free (acre->colors);
+
free (acre);
}
acre->y_axis.label = strdup (label);
}
+void
+acre_get_x_axis_data_range (acre_t *acre, double *x_min, double *x_max)
+{
+ if (x_min)
+ *x_min = acre->x_axis.data_min;
+
+ if (x_max)
+ *x_max = acre->x_axis.data_max;
+}
+
+void
+acre_get_x_axis_range (acre_t *acre, double *x_min, double *x_max)
+{
+ /* If an X range has been set, return that. */
+ if (acre->x_axis.view_range_set) {
+ if (x_min)
+ *x_min = acre->x_axis.view_min;
+
+ if (x_max)
+ *x_max = acre->x_axis.view_max;
+
+ return;
+ }
+
+ /* Otherwise, if a Y range has been set, use that to compute X. */
+ if (acre->y_axis.view_range_set) {
+ _find_x_range_given_y_range (acre, x_min, x_max,
+ acre->y_axis.view_min,
+ acre->y_axis.view_max);
+
+ return;
+ }
+
+ /* Neither view range set. Return full, data-based X range. */
+ acre_get_x_axis_data_range (acre, x_min, x_max);
+}
+
+void
+acre_set_x_axis_range (acre_t *acre, double x_min, double x_max)
+{
+ acre->x_axis.view_min = x_min;
+ acre->x_axis.view_max = x_max;
+
+ acre->x_axis.view_range_set = true;
+}
+
+void
+acre_set_x_axis_range_auto (acre_t *acre)
+{
+ acre->x_axis.view_range_set = false;
+}
+
+void
+acre_get_y_axis_data_range (acre_t *acre, double *y_min, double *y_max)
+{
+ if (y_min)
+ *y_min = acre->y_axis.data_min;
+
+ if (y_max)
+ *y_max = acre->y_axis.data_max;
+}
+
+void
+acre_get_y_axis_range (acre_t *acre, double *y_min, double *y_max)
+{
+ /* If a Y range has been set, return that. */
+ if (acre->y_axis.view_range_set) {
+ if (y_min)
+ *y_min = acre->y_axis.view_min;
+
+ if (y_max)
+ *y_max = acre->y_axis.view_max;
+
+ return;
+ }
+
+ /* Otherwise, if an X range has been set, use that to compute Y. */
+ if (acre->x_axis.view_range_set) {
+ _find_y_range_given_x_range (acre, y_min, y_max,
+ acre->x_axis.view_min,
+ acre->x_axis.view_max);
+
+ return;
+ }
+
+ /* Neither view range set. Return full data-based Y range. */
+ acre_get_y_axis_data_range (acre, y_min, y_max);
+}
+
+void
+acre_set_y_axis_range (acre_t *acre, double y_min, double y_max)
+{
+ acre->y_axis.view_min = y_min;
+ acre->y_axis.view_max = y_max;
+
+ acre->y_axis.view_range_set = true;
+}
+
+void
+acre_set_y_axis_range_auto (acre_t *acre)
+{
+ acre->y_axis.view_range_set = false;
+}
+
/* Add a dataset to the plot. The plot assumes ownership of the
* dataset so it is not necessary to call acre_data_destroy on it. */
void
}
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;
+
+ acre->x_axis.data_max = data->max.x;
+ acre->y_axis.data_max = data->max.y;
+ } else {
+ if (data->min.x < acre->x_axis.data_min)
+ acre->x_axis.data_min = data->min.x;
+ if (data->min.y < acre->y_axis.data_min)
+ acre->y_axis.data_min = data->min.y;
+
+ if (data->max.x > acre->x_axis.data_max)
+ acre->x_axis.data_max = data->max.x;
+ if (data->max.y > acre->y_axis.data_max)
+ acre->y_axis.data_max = data->max.y;
+ }
+
acre->num_data++;
}
#define ACRE_TICK_MINOR_SIZE 3
#define ACRE_X_TICK_VALUE_PAD 2
#define ACRE_Y_TICK_VALUE_PAD 4
+#define ACRE_LEGEND_PAD 4
+#define ACRE_LEGEND_LINE_SIZE 10
static PangoLayout *
_create_layout (acre_t *acre, const char *text)
{
PangoLayout *layout;
+ if (text == NULL)
+ text = "";
+
+ cairo_save (acre->cr);
+ cairo_identity_matrix (acre->cr);
+
layout = pango_cairo_create_layout (acre->cr);
pango_layout_set_font_description (layout, acre->font);
pango_layout_set_text (layout, text, -1);
pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
+ cairo_restore (acre->cr);
+
return layout;
}
title_layout = _create_layout (acre, acre->title);
pango_layout_set_font_description (title_layout, title_font);
+ pango_font_description_free (title_font);
x_axis_layout = _create_layout (acre, acre->x_axis.label);
y_axis_layout = _create_layout (acre, acre->y_axis.label);
min_y = _create_layout_printf (acre, "%g",
- round (acre->y_axis.min));
+ round (acre->y_axis.view_min));
max_y = _create_layout_printf (acre, "%g",
- round (acre->y_axis.max));
+ round (acre->y_axis.view_max));
pango_layout_get_pixel_size (min_y, &min_y_width, NULL);
pango_layout_get_pixel_size (max_y, &max_y_width, NULL);
acre->chart.x,
acre->chart.y + acre->chart.height);
cairo_scale (cr,
- acre->chart.width / (acre->x_axis.max - acre->x_axis.min),
- - acre->chart.height /(acre->y_axis.max - acre->y_axis.min));
- cairo_translate (cr, -acre->x_axis.min, -acre->y_axis.min);
+ acre->chart.width / (acre->x_axis.view_max - acre->x_axis.view_min),
+ - acre->chart.height /(acre->y_axis.view_max - acre->y_axis.view_min));
+ cairo_translate (cr, -acre->x_axis.view_min, -acre->y_axis.view_min);
}
static void
-_compute_axis_ranges (acre_t *acre)
+_find_x_range_given_y_range (acre_t *acre,
+ double *x_min, double *x_max,
+ double y_min, double y_max)
{
- unsigned int d, i;
acre_data_t *data;
- double x_adjust, y_adjust;
- cairo_t *cr = acre->cr;
+ unsigned d, i;
+ bool first;
- acre->x_axis.min = acre->data[0]->points[0].x;
- acre->x_axis.max = acre->data[0]->points[0].x;
- acre->y_axis.min = acre->data[0]->points[0].y;
- acre->y_axis.min = acre->data[0]->points[0].y;
+ first = true;
- /* First, simply find the extrema of the data. */
for (d = 0; d < acre->num_data; d++) {
data = acre->data[d];
for (i = 0; i < data->num_points; i++) {
- if (data->points[i].x < acre->x_axis.min)
- acre->x_axis.min = data->points[i].x;
- if (data->points[i].x > acre->x_axis.max)
- acre->x_axis.max = data->points[i].x;
-
- if (data->points[i].y < acre->y_axis.min)
- acre->y_axis.min = data->points[i].y;
- if (data->points[i].y > acre->y_axis.max)
- acre->y_axis.max = data->points[i].y;
+ if (data->points[i].y >= y_min &&
+ data->points[i].y <= y_max)
+ {
+ if (first) {
+ *x_min = data->points[i].x;
+ *x_max = data->points[i].x;
+ first = false;
+ } else {
+ if (data->points[i].x < *x_min)
+ *x_min = data->points[i].x;
+ if (data->points[i].x > *x_max)
+ *x_max = data->points[i].x;
+ }
+ }
}
}
- /* Next, we want to ensure that the data never collides with the
- * ticks. So we expand each axis on its minimum side as needed. */
- cairo_save (cr);
- {
- double x, y;
+ /* If nothing is visible, punt to full X data range. */
+ if (first) {
+ *x_min = acre->x_axis.data_min;
+ *x_max = acre->x_axis.data_max;
+ }
+}
- _set_transform_to_data_space (acre);
+static void
+_find_y_range_given_x_range (acre_t *acre,
+ double *y_min, double *y_max,
+ double x_min, double x_max)
+{
+ acre_data_t *data;
+ unsigned d, i;
+ bool first;
+
+ first = true;
+
+ for (d = 0; d < acre->num_data; d++) {
+ data = acre->data[d];
- x = ACRE_TICK_MAJOR_SIZE + 2.0;
- y = ACRE_TICK_MAJOR_SIZE + 2.0;
- cairo_device_to_user_distance (cr, &x, &y);
+ /* Never mess with the Y range for timeline data. */
+ if (data->style == ACRE_STYLE_TIMELINE)
+ continue;
- acre->x_axis.min -= x;
- acre->y_axis.min += y;
+ for (i = 0; i < data->num_points; i++) {
+ if (data->points[i].x >= x_min &&
+ data->points[i].x <= x_max)
+ {
+ if (first) {
+ *y_min = data->points[i].y;
+ *y_max = data->points[i].y;
+ first = false;
+ } else {
+ if (data->points[i].y < *y_min)
+ *y_min = data->points[i].y;
+ if (data->points[i].y > *y_max)
+ *y_max = data->points[i].y;
+ }
+ }
+ }
+ }
+
+ /* If nothing is visible, punt to full Y data range. */
+ if (first) {
+ *y_min = acre->y_axis.data_min;
+ *y_max = acre->y_axis.data_max;
+ }
+}
+
+static void
+_compute_axis_ranges (acre_t *acre)
+{
+ double x_adjust, y_adjust;
+ cairo_t *cr = acre->cr;
+
+ /* If neither view range is set, set both to data ranges. */
+ if (! acre->x_axis.view_range_set && ! acre->y_axis.view_range_set)
+ {
+ acre->x_axis.view_min = acre->x_axis.data_min;
+ acre->x_axis.view_max = acre->x_axis.data_max;
+
+ acre->y_axis.view_min = acre->y_axis.data_min;
+ acre->y_axis.view_max = acre->y_axis.data_max;
+ } else {
+ /* Otherwise, auto-fit unset range based on data. */
+ if (acre->x_axis.view_range_set && ! acre->y_axis.view_range_set) {
+ _find_y_range_given_x_range (acre,
+ &acre->y_axis.view_min,
+ &acre->y_axis.view_max,
+ acre->x_axis.view_min,
+ acre->x_axis.view_max);
+ }
+ else if (acre->y_axis.view_range_set && ! acre->x_axis.view_range_set) {
+ _find_x_range_given_y_range (acre,
+ &acre->x_axis.view_min,
+ &acre->x_axis.view_max,
+ acre->y_axis.view_min,
+ acre->y_axis.view_max);
+ }
}
- cairo_restore (cr);
/* Then, increase the axis ranges just enough so that the step
* sizes for the ticks will be integers.
*/
- _expand_range_for_width (&acre->x_axis.min,
- &acre->x_axis.max,
+ _expand_range_for_width (&acre->x_axis.view_min,
+ &acre->x_axis.view_max,
acre->chart.width);
- _expand_range_for_width (&acre->y_axis.min,
- &acre->y_axis.max,
+ _expand_range_for_width (&acre->y_axis.view_min,
+ &acre->y_axis.view_max,
acre->chart.height);
/* Finally, we also translate the axis ranges slightly so that the
y_adjust = (round (y_adjust + 0.5) - 0.5) - y_adjust;
cairo_device_to_user_distance (cr, &x_adjust, &y_adjust);
- acre->x_axis.min -= x_adjust;
- acre->x_axis.max -= x_adjust;
+ acre->x_axis.view_min -= x_adjust;
+ acre->x_axis.view_max -= x_adjust;
- acre->y_axis.min -= y_adjust;
- acre->y_axis.max -= y_adjust;
+ acre->y_axis.view_min -= y_adjust;
+ acre->y_axis.view_max -= y_adjust;
}
cairo_restore (cr);
}
+static void
+_choose_colors (acre_t *acre)
+{
+ cmsHPROFILE lab_profile, srgb_profile;
+ cmsHTRANSFORM lab_to_srgb;
+ int i;
+ double theta, radius, srgb[3];
+ cmsCIELab lab;
+
+ lab_profile = cmsCreateLabProfile (NULL); /* D50 */
+ srgb_profile = cmsCreate_sRGBProfile ();
+
+ lab_to_srgb = cmsCreateTransform (lab_profile, TYPE_Lab_DBL,
+ srgb_profile, TYPE_RGB_DBL,
+ INTENT_PERCEPTUAL, 0);
+
+ acre->num_colors = acre->num_data;
+
+ if (acre->num_colors > acre->colors_size) {
+ acre->colors_size = acre->num_colors;
+ acre->colors = xrealloc (acre->colors,
+ acre->colors_size * sizeof (acre_color_t));
+ }
+
+ lab.L = 36;
+ radius = 130;
+ for (i = 0; i < acre->num_colors; i++) {
+ theta = 0.713 + 2 * M_PI * (double) i / acre->num_colors;
+ lab.a = radius * cos (theta);
+ lab.b = radius * sin (theta);
+
+ cmsDoTransform (lab_to_srgb, &lab, srgb, 1);
+
+ acre->colors[i].red = srgb[0];
+ acre->colors[i].green = srgb[1];
+ acre->colors[i].blue = srgb[2];
+ }
+
+ cmsDeleteTransform (lab_to_srgb);
+ cmsCloseProfile (lab_profile);
+ cmsCloseProfile (srgb_profile);
+}
+
+/* Draw the given dataset as a line. */
+static void
+_draw_data_line (acre_t *acre, acre_data_t *data)
+{
+ unsigned i;
+ cairo_t *cr = acre->cr;
+
+ cairo_save (cr);
+
+ cairo_new_path (cr);
+
+ for (i = 0; i < data->num_points; i++) {
+ cairo_line_to (cr,
+ data->points[i].x,
+ data->points[i].y);
+ }
+
+ cairo_identity_matrix (cr);
+ cairo_set_line_width (cr, 1.0);
+ cairo_stroke (cr);
+
+ cairo_restore (cr);
+}
+
+#define BARS_WIDTH 0.8
+
+/* Draw the given dataset as bars if there is room for that.
+ *
+ * Or, if the bars would run into each other, use a line instead.
+ */
+static void
+_draw_data_bars_or_line (acre_t *acre, acre_data_t *data)
+{
+ unsigned i;
+ cairo_t *cr = acre->cr;
+ double ignored, width;
+
+ /* Check device-space width available for inter-bad padding. */
+ width = 1.0 - BARS_WIDTH;
+ ignored = 0.0;
+ cairo_user_to_device_distance (cr, &width, &ignored);
+
+ /* If padding is less than two pixels, draw with a line instead. */
+ if (width < 2.0) {
+ _draw_data_line (acre, data);
+
+ return;
+ }
+
+ cairo_save (cr);
+
+ cairo_new_path (cr);
+
+ for (i = 0; i < data->num_points; i++) {
+ cairo_rectangle (cr,
+ data->points[i].x - BARS_WIDTH / 2.0, 0.0,
+ BARS_WIDTH, data->points[i].y);
+ }
+
+ cairo_identity_matrix (cr);
+ cairo_set_line_width (cr, 1.0);
+ cairo_stroke (cr);
+
+ 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)
{
cairo_t *cr = acre->cr;
- unsigned int d, i;
+ unsigned int i;
acre_data_t *data;
cairo_save (cr);
+ cairo_rectangle (cr,
+ acre->chart.x, acre->chart.y,
+ acre->chart.width, acre->chart.height);
+ cairo_clip (cr);
+
cairo_set_source_rgb (cr, 0, 0, 0);
_set_transform_to_data_space (acre);
- for (d = 0; d < acre->num_data; d++) {
- data = acre->data[d];
- cairo_new_path (cr);
- for (i = 0; i < data->num_points; i++) {
- cairo_line_to (cr,
- data->points[i].x,
- data->points[i].y);
- }
- cairo_save (cr);
- {
- cairo_identity_matrix (cr);
- cairo_set_line_width (cr, 1.0);
- cairo_stroke (cr);
+ for (i = 0; i < acre->num_data; i++) {
+ int color = i % acre->num_colors;
+ cairo_set_source_rgb (cr,
+ acre->colors[color].red,
+ acre->colors[color].green,
+ acre->colors[color].blue);
+ data = acre->data[i];
+
+ switch (data->style) {
+ case ACRE_STYLE_LINE:
+ _draw_data_line (acre, data);
+ break;
+ case ACRE_STYLE_BARS_OR_LINE:
+ _draw_data_bars_or_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;
}
- cairo_restore (cr);
}
cairo_restore (cr);
cairo_save (cr);
{
if (ticks == ACRE_TICKS_X)
- cairo_move_to (cr, t, acre->y_axis.min);
+ cairo_move_to (cr, t, acre->y_axis.view_min);
else
- cairo_move_to (cr, acre->x_axis.min, t);
+ cairo_move_to (cr, acre->x_axis.view_min, t);
cairo_identity_matrix (cr);
{
PangoLayout *layout;
int width, height;
+ double label_value;
cairo_save (cr);
- layout = _create_layout_printf (acre, "%g", t);
+ label_value = t;
+ if (fabs (label_value) < (sub_step / 1000.))
+ label_value = 0.0;
+ layout = _create_layout_printf (acre, "%g", label_value);
if (ticks == ACRE_TICKS_X)
- cairo_move_to (cr, t, acre->y_axis.min);
+ cairo_move_to (cr, t, acre->y_axis.view_min);
else
- cairo_move_to (cr, acre->x_axis.min, t);
+ cairo_move_to (cr, acre->x_axis.view_min, t);
cairo_identity_matrix (cr);
pango_layout_get_pixel_size (layout, &width, &height);
cairo_restore (cr);
}
+static void
+_draw_legend (acre_t *acre)
+{
+ PangoLayout *layout;
+ int label_width, max_label_width = 0;
+ int width, height;
+ unsigned int i;
+ cairo_t *cr = acre->cr;
+
+ cairo_save (cr);
+
+ for (i = 0; i < acre->num_data; i++) {
+ layout = _create_layout (acre, acre->data[i]->name);
+ pango_layout_get_pixel_size (layout, &label_width, NULL);
+ _destroy_layout (layout);
+ if (label_width > max_label_width)
+ max_label_width = label_width;
+ }
+
+ width = ACRE_LEGEND_PAD + ACRE_LEGEND_LINE_SIZE + ACRE_LEGEND_PAD +
+ max_label_width + ACRE_LEGEND_PAD;
+ height = ACRE_LEGEND_PAD +
+ acre->num_data * (ACRE_FONT_SIZE + ACRE_LEGEND_PAD);
+
+ cairo_translate (cr, acre->chart.x, acre->chart.y);
+
+ cairo_translate (cr,
+ acre->chart.width - ACRE_LEGEND_PAD - width,
+ ACRE_LEGEND_PAD);
+
+ cairo_rectangle (cr, -0.5, -0.5, width + 1.0, height + 1.0);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_set_line_width (cr, 1.0);
+ cairo_stroke (cr);
+
+ cairo_translate (cr, ACRE_LEGEND_PAD, ACRE_LEGEND_PAD);
+
+ for (i = 0; i < acre->num_data; i++) {
+ cairo_rectangle (cr,
+ 0, ACRE_LEGEND_LINE_SIZE / 2,
+ ACRE_LEGEND_LINE_SIZE, ACRE_LEGEND_LINE_SIZE / 2);
+ cairo_set_source_rgb (cr,
+ acre->colors[i % acre->num_colors].red,
+ acre->colors[i % acre->num_colors].green,
+ acre->colors[i % acre->num_colors].blue);
+ cairo_fill (cr);
+
+ layout = _create_layout (acre, acre->data[i]->name);
+ cairo_move_to (cr, ACRE_LEGEND_LINE_SIZE + ACRE_LEGEND_PAD, 0);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ _show_layout (cr, layout);
+
+ cairo_translate (cr, 0, ACRE_LEGEND_PAD + ACRE_FONT_SIZE);
+ }
+
+ cairo_restore (cr);
+}
+
static void
_draw_frame_and_ticks (acre_t *acre)
{
cairo_set_source_rgb (cr, 0, 0, 0); /* black */
/* ticks */
- _draw_ticks (acre, acre->x_axis.min, acre->x_axis.max, ACRE_TICKS_X);
- _draw_ticks (acre, acre->y_axis.min, acre->y_axis.max, ACRE_TICKS_Y);
+ _draw_ticks (acre, acre->x_axis.view_min, acre->x_axis.view_max, ACRE_TICKS_X);
+ _draw_ticks (acre, acre->y_axis.view_min, acre->y_axis.view_max, ACRE_TICKS_Y);
/* frame */
cairo_rectangle (cr,
cairo_set_source_rgb (cr, 1, 1, 1);
+ _choose_colors (acre);
+
/* We compute the axis ranges before doing label layout so that we
* can account for the width of the y-axis value labels. */
_compute_axis_ranges (acre);
_draw_data (acre);
+ if (acre->num_data > 1)
+ _draw_legend (acre);
+
_draw_frame_and_ticks (acre);
}
data->name = NULL;
+ data->style = ACRE_STYLE_LINE;
+
data->points = NULL;
data->points_size = 0;
data->num_points = 0;
void
acre_data_destroy (acre_data_t *data)
{
+ unsigned i;
+
+ for (i = 0; i < data->num_names; i++) {
+ if (data->names[i])
+ free (data->names[i]);
+ }
+ free (data->names);
+
+ free (data->name);
+
free (data->points);
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)
data->points[data->num_points].x = x;
data->points[data->num_points].y = y;
+
+ if (data->num_points == 0) {
+ data->min.x = x;
+ data->min.y = y;
+
+ data->max.x = x;
+ data->max.y = y;
+ } else {
+ if (x < data->min.x)
+ data->min.x = x;
+ if (y < data->min.y)
+ data->min.y = y;
+
+ if (x > data->max.x)
+ data->max.x = x;
+ if (y > data->max.y)
+ data->max.y = y;
+ }
+
data->num_points++;
}
+
+/* Add a datapoint with a name to the given dataset. */
+void
+acre_data_add_point_2d_named (acre_data_t *data, double x, double y, const char *name)
+{
+ unsigned i;
+
+ acre_data_add_point_2d (data, x, y);
+
+ if (data->names_size < data->points_size) {
+ data->names_size = data->points_size;
+ data->names = xrealloc_ab (data->names,
+ data->names_size,
+ sizeof (char *));
+ }
+
+ /* Initialize any newly-created holes in the array to NULL. */
+ for (i = data->num_names; i < data->num_points - 1; i++)
+ data->names[i] = NULL;
+
+ data->num_names = data->num_points;
+
+ data->names[data->num_names - 1] = xstrdup (name);
+}