#include <stdarg.h>
#include <math.h>
+#include <lcms.h>
+
typedef struct _acre_data_point_2d {
double x;
double y;
struct _acre_data {
char *name;
+ acre_data_point_2d_t min;
+ acre_data_point_2d_t max;
+
acre_data_point_2d_t *points;
unsigned int points_size;
unsigned int num_points;
typedef struct _acre_axis {
char *label;
- double min;
- double max;
+ double data_min;
+ double data_max;
+ double view_min;
+ double view_max;
} 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;
- struct { double r; double g; double b;} colors[3];
+ acre_color_t *colors;
+ int colors_size;
int num_colors;
/* Total size including labels. */
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->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->data = NULL;
acre->data_size = 0;
acre->num_data = 0;
+ acre->cr = NULL;
+ acre->font = NULL;
+ acre->colors = NULL;
+ acre->num_colors = 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->data[acre->num_data] = data;
+
+ 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++;
}
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)
{
- unsigned int d, i;
- acre_data_t *data;
double x_adjust, y_adjust;
cairo_t *cr = acre->cr;
- 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, simply set view ranges to data ranges. */
+ acre->x_axis.view_min = acre->x_axis.data_min;
+ acre->x_axis.view_max = acre->x_axis.data_max;
- /* 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;
- }
- }
+ acre->y_axis.view_min = acre->y_axis.data_min;
+ acre->y_axis.view_max = acre->y_axis.data_max;
/* Next, we want to ensure that the data never collides with the
* ticks. So we expand each axis on its minimum side as needed. */
y = ACRE_TICK_MAJOR_SIZE + 2.0;
cairo_device_to_user_distance (cr, &x, &y);
- acre->x_axis.min -= x;
- acre->y_axis.min += y;
+ acre->x_axis.view_min -= x;
+ acre->y_axis.view_min += y;
}
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
+_acre_color_from_hsv (acre_color_t *color,
+ double hue,
+ double saturation,
+ double value)
+{
+ double f, p, q, t;
+ int hmod6;
+
+ hmod6 = (int) floor (hue / 60) % 6;
+ f = hue / 60 - floor (hue / 60);
+ p = value * (1 - saturation);
+ q = value * (1 - f * saturation);
+ t = value * (1 - (1 - f) * saturation);
+
+ switch (hmod6) {
+ case 0:
+ color->red = value;
+ color->green = t;
+ color->blue = p;
+ break;
+ case 1:
+ color->red = q;
+ color->green = value;
+ color->blue = p;
+ break;
+ case 2:
+ color->red = p;
+ color->green = value;
+ color->blue = t;
+ break;
+ case 3:
+ color->red = p;
+ color->green = q;
+ color->blue = value;
+ break;
+ case 4:
+ color->red = t;
+ color->green = p;
+ color->blue = value;
+ break;
+ case 5:
+ color->red = value;
+ color->green = p;
+ color->blue = q;
+ break;
+ }
+}
+
static void
_choose_colors (acre_t *acre)
{
- /* XXX: Should choose N, equally-spaced colors from a perceptually
- * linear space here. */
- acre->num_colors = 3;
+ cmsHPROFILE lab_profile, srgb_profile;
+ cmsHTRANSFORM lab_to_srgb;
+ int i;
+ double theta, radius, srgb[3];
+ cmsCIELab lab;
- acre->colors[0].r = 1;
- acre->colors[0].g = 0;
- acre->colors[0].b = 0;
+ 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));
+ }
- acre->colors[1].r = 0;
- acre->colors[1].g = 1;
- acre->colors[1].b = 0;
+ 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);
- acre->colors[2].r = 0;
- acre->colors[2].g = 0;
- acre->colors[2].b = 1;
+ 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);
}
static void
for (d = 0; d < acre->num_data; d++) {
int color = d % acre->num_colors;
cairo_set_source_rgb (cr,
- acre->colors[color].r,
- acre->colors[color].g,
- acre->colors[color].b);
+ acre->colors[color].red,
+ acre->colors[color].green,
+ acre->colors[color].blue);
data = acre->data[d];
cairo_new_path (cr);
for (i = 0; i < data->num_points; i++) {
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);
layout = _create_layout_printf (acre, "%g", t);
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_translate (cr, ACRE_LEGEND_PAD, ACRE_LEGEND_PAD);
for (i = 0; i < acre->num_data; i++) {
- cairo_move_to (cr, 0, ACRE_FONT_SIZE / 2 + 0.5);
- cairo_rel_line_to (cr, ACRE_LEGEND_LINE_SIZE, 0);
+ 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].r,
- acre->colors[i % acre->num_colors].g,
- acre->colors[i % acre->num_colors].b);
- cairo_stroke (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); /* 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,
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++;
}