]> git.cworth.org Git - acre/blob - acre.c
Select N colors from HSV color space.
[acre] / acre.c
1 /* acre - A cairo-based library for creating plots and charts.
2  *
3  * Copyright © 2009 Carl Worth <cworth@cworth.org>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
18  */
19
20 #define _ISOC99_SOURCE /* for round() */
21 #define _XOPEN_SOURCE 500
22 #define _GNU_SOURCE /* for asprintf() */
23
24 #include "acre.h"
25 #include "xmalloc.h"
26
27 #include <string.h>
28 #include <stdarg.h>
29 #include <math.h>
30
31 typedef struct _acre_data_point_2d {
32     double x;
33     double y;
34 } acre_data_point_2d_t;
35
36 struct _acre_data {
37     char *name;
38
39     acre_data_point_2d_t *points;
40     unsigned int points_size;
41     unsigned int num_points;
42 };
43
44 typedef struct _acre_axis {
45     char *label;
46     double min;
47     double max;
48 } acre_axis_t;
49
50 typedef struct _acre_color {
51     double red;
52     double green;
53     double blue;
54 } acre_color_t;
55
56 struct _acre {
57     char *title;
58     acre_axis_t x_axis;
59     acre_axis_t y_axis;
60
61     acre_data_t **data;
62     unsigned int data_size;
63     unsigned int num_data;
64
65     /* Data for drawing. */
66     cairo_t *cr;
67     PangoFontDescription *font;
68     acre_color_t *colors;
69     int colors_size;
70     int num_colors;
71
72     /* Total size including labels. */
73     int width;
74     int height;
75
76     /* Position and size of chart alone. */
77     PangoRectangle chart;
78 };
79
80 /* Create a new, empty plot. */
81 acre_t *
82 acre_create (void)
83 {
84     acre_t *acre;
85
86     acre = xmalloc (sizeof (acre_t));
87
88     acre->title = NULL;
89
90     acre->x_axis.label = NULL;
91     acre->x_axis.min = 0.0;
92     acre->x_axis.max = 0.0;
93
94     acre->y_axis.label = NULL;
95     acre->y_axis.min = 0.0;
96     acre->y_axis.max = 0.0;
97
98     acre->data = NULL;
99     acre->data_size = 0;
100     acre->num_data = 0;
101
102     acre->cr = NULL;
103     acre->font = NULL;
104     acre->colors = NULL;
105     acre->num_colors = 0;
106
107     acre->width = 0;
108     acre->height = 0;
109
110     acre->chart.x = 0;
111     acre->chart.y = 0;
112     acre->chart.width = 0;
113     acre->chart.height = 0;
114
115     return acre;
116 }
117
118 /* Destroy a plot. */
119 void
120 acre_destroy (acre_t *acre)
121 {
122     unsigned int i;
123
124     free (acre->title);
125     free (acre->x_axis.label);
126     free (acre->y_axis.label);
127
128     for (i = 0; i < acre->num_data; i++)
129         acre_data_destroy (acre->data[i]);
130
131     free (acre->data);
132
133     free (acre->colors);
134
135     free (acre);
136 }
137
138 void
139 acre_set_title (acre_t *acre, const char *title)
140 {
141     free (acre->title);
142
143     acre->title = strdup (title);
144 }
145
146 void
147 acre_set_x_axis_label (acre_t *acre, const char *label)
148 {
149     free (acre->x_axis.label);
150
151     acre->x_axis.label = strdup (label);
152 }
153
154 void
155 acre_set_y_axis_label (acre_t *acre, const char *label)
156 {
157     free (acre->y_axis.label);
158
159     acre->y_axis.label = strdup (label);
160 }
161
162 /* Add a dataset to the plot. The plot assumes ownership of the
163  * dataset so it is not necessary to call acre_data_destroy on it. */
164 void
165 acre_add_data (acre_t *acre, acre_data_t *data)
166 {
167     if (acre->num_data >= acre->data_size) {
168         acre->data_size *= 2;
169         if (acre->data_size == 0)
170             acre->data_size = 1;
171         acre->data = xrealloc_ab (acre->data,
172                                   acre->data_size,
173                                   sizeof (acre_data_t *));
174     }
175
176     acre->data[acre->num_data] = data;
177     acre->num_data++;
178 }
179
180 #define ACRE_FONT_FAMILY "sans"
181 #define ACRE_FONT_SIZE 12
182 #define ACRE_TITLE_FONT_SIZE 20
183 #define ACRE_PAD (ACRE_FONT_SIZE)
184 #define ACRE_TICK_MAJOR_SIZE 6
185 #define ACRE_TICK_MINOR_SIZE 3
186 #define ACRE_X_TICK_VALUE_PAD 2
187 #define ACRE_Y_TICK_VALUE_PAD 4
188 #define ACRE_LEGEND_PAD 4
189 #define ACRE_LEGEND_LINE_SIZE 10
190
191 static PangoLayout *
192 _create_layout (acre_t *acre, const char *text)
193 {
194     PangoLayout *layout;
195
196     layout = pango_cairo_create_layout (acre->cr);
197     pango_layout_set_font_description (layout, acre->font);
198     pango_layout_set_text (layout, text, -1);
199     pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
200
201     return layout;
202 }
203
204 #define PRINTF_FORMAT(fmt_index, va_index) __attribute__ ((__format__(__printf__, fmt_index, va_index)))
205
206 static PangoLayout *
207 _create_layout_vprintf (acre_t *acre, const char *fmt, va_list ap)
208 {
209     PangoLayout *layout;
210     char *text;
211
212     vasprintf (&text, fmt, ap);
213
214     layout = _create_layout (acre, text);
215
216     free (text);
217
218     return layout;
219 }
220
221 static PangoLayout *
222 _create_layout_printf (acre_t *acre, const char *fmt, ...)
223     PRINTF_FORMAT (2, 3);
224
225 static PangoLayout *
226 _create_layout_printf (acre_t *acre, const char *fmt, ...)
227 {
228     va_list ap;
229     PangoLayout *layout;
230
231     va_start (ap, fmt);
232
233     layout = _create_layout_vprintf (acre, fmt, ap);
234
235     va_end (ap);
236
237     return layout;
238 }
239
240 static void
241 _destroy_layout (PangoLayout *layout)
242 {
243     g_object_unref (layout);
244 }
245
246 static void
247 _show_layout (cairo_t *cr, PangoLayout *layout)
248 {
249     pango_cairo_show_layout (cr, layout);
250
251     _destroy_layout (layout);
252 }
253
254 static void
255 _draw_title_and_labels (acre_t *acre)
256 {
257     cairo_t *cr = acre->cr;
258     PangoFontDescription *title_font;
259     PangoLayout *title_layout, *x_axis_layout, *y_axis_layout;
260     PangoLayout *min_y, *max_y;
261     int min_y_width, max_y_width, y_axis_value_width;
262     int title_width, title_height;
263     int x_axis_width, x_axis_height;
264     int y_axis_width, y_axis_height;
265     PangoRectangle new_chart;
266
267     cairo_save (cr);
268
269     acre->font = pango_font_description_new ();
270     pango_font_description_set_family (acre->font, ACRE_FONT_FAMILY);
271     pango_font_description_set_absolute_size (acre->font,
272                                               ACRE_FONT_SIZE * PANGO_SCALE);
273
274     title_font = pango_font_description_new ();
275     pango_font_description_set_family (title_font, ACRE_FONT_FAMILY);
276     pango_font_description_set_absolute_size (title_font,
277                                               ACRE_TITLE_FONT_SIZE * PANGO_SCALE);
278
279     title_layout = _create_layout (acre, acre->title);
280     pango_layout_set_font_description (title_layout, title_font);
281
282     x_axis_layout = _create_layout (acre, acre->x_axis.label);
283     y_axis_layout = _create_layout (acre, acre->y_axis.label);
284
285     min_y = _create_layout_printf (acre, "%g",
286                                    round (acre->y_axis.min));
287     max_y = _create_layout_printf (acre, "%g",
288                                    round (acre->y_axis.max));
289
290     pango_layout_get_pixel_size (min_y, &min_y_width, NULL);
291     pango_layout_get_pixel_size (max_y, &max_y_width, NULL);
292     y_axis_value_width = MAX (min_y_width, max_y_width);
293
294     _destroy_layout (min_y);
295     _destroy_layout (max_y);
296
297     /* Iterate with the layout of the title and axis labels until they
298      * are stable, (this requires iteration since we don't know what
299      * to set their widths to in advance due to the wrapping of the
300      * other elements). */
301     while (1) {
302         pango_layout_set_width (title_layout, acre->chart.width * PANGO_SCALE);
303         pango_layout_set_width (x_axis_layout, acre->chart.width * PANGO_SCALE);
304         pango_layout_set_width (y_axis_layout, acre->chart.height * PANGO_SCALE);
305
306         pango_layout_get_pixel_size (title_layout, &title_width, &title_height);
307         pango_layout_get_pixel_size (x_axis_layout, &x_axis_width, &x_axis_height);
308         pango_layout_get_pixel_size (y_axis_layout, &y_axis_width, &y_axis_height);
309
310         new_chart.x = ACRE_PAD + y_axis_height +
311             ACRE_PAD + y_axis_value_width + ACRE_Y_TICK_VALUE_PAD;
312         new_chart.width = acre->width - acre->chart.x - ACRE_PAD;
313
314         new_chart.y = ACRE_PAD + title_height + ACRE_PAD;
315         new_chart.height = acre->height - acre->chart.y - 
316             (ACRE_X_TICK_VALUE_PAD + ACRE_FONT_SIZE +
317              ACRE_PAD + x_axis_height + ACRE_PAD);
318
319         if (new_chart.x == acre->chart.x &&
320             new_chart.y == acre->chart.y &&
321             new_chart.width == acre->chart.width &&
322             new_chart.height == acre->chart.height)
323         {
324             break;
325         }
326
327         acre->chart.x = new_chart.x;
328         acre->chart.y = new_chart.y;
329         acre->chart.width = new_chart.width;
330         acre->chart.height = new_chart.height;
331     }
332
333     cairo_set_source_rgb (cr, 0, 0, 0);
334
335     cairo_move_to (cr, acre->chart.x, ACRE_PAD);
336     _show_layout (cr, title_layout);
337
338     cairo_save (cr);
339     {
340         cairo_translate (cr, ACRE_PAD, acre->chart.y + acre->chart.height);
341         cairo_rotate (cr, - M_PI / 2.0);
342         cairo_move_to (cr, 0, 0);
343         _show_layout (cr, y_axis_layout);
344     }
345     cairo_restore (cr);
346
347     cairo_move_to (cr, acre->chart.x,
348                    acre->chart.y + acre->chart.height +
349                    ACRE_FONT_SIZE + ACRE_PAD);
350     _show_layout (cr, x_axis_layout);
351
352     cairo_restore (cr);
353 }
354
355 /* For a given axis range, compute a step size (in data space) to
356  * generate a suitable number of ticks (5 or so). */
357 static double
358 _step_for_range (double range, int *minor_divisions)
359 {
360     double step, scale_factor;
361
362     /* We want roughly 5 major ticks for the chart. */
363     step = range / 5;
364
365     /* Normalize the step so we can easily snap it to a desirable
366      * value. */
367     scale_factor = pow (10.0, floor (log10 (step)));
368     step /= scale_factor;
369
370     /* We want increments of 1, 2.5, 5, or 10 (times some power of
371      * 10). The threshold values between these are computed
372      * logarithmically. */
373     if (step < 3.535533905932738) {
374         if (step < 1.58113883008419) {
375             step = 1.0;
376             *minor_divisions = 4;
377         } else {
378             step = 2.5;
379             *minor_divisions = 5;
380         }
381     } else {
382         if (step < 7.071067811865475) {
383             step = 5.0;
384             *minor_divisions = 5;
385         } else {
386             step = 10.0;
387             *minor_divisions = 4;
388         }
389     }
390
391     /* Un-normalize and we now have the data value that we want to
392      * step at. */
393     return step * scale_factor;
394 }
395
396 /* Given an axis range, we can compute a desired data-space step
397  * amount for the major ticks (see _step_for_range). To get
398  * nice-looking pixel-snapped ticks we want to expand the range
399  * slightly. */
400 static void
401 _expand_range_for_width (double *axis_min, double *axis_max, int pixel_range)
402 {
403     double range, new_range, step, step_minor, pixel_step;
404     int minor_divisions;
405
406     range = *axis_max - *axis_min;
407
408     step = _step_for_range (range, &minor_divisions);
409     step_minor = step / minor_divisions;
410
411     pixel_step = step_minor * (pixel_range / range);
412
413     /* We expand the range by the ratio of the pixel step to the floor
414      * of the pixel_step.
415      */
416     new_range = range * pixel_step / floor (pixel_step);
417
418     /* And spread the increase out on either side of the range. */
419     *axis_min -= (new_range - range) / 2.0;
420     *axis_max += (new_range - range) / 2.0;
421 }
422
423 /* Setup a transformation in acre->cr such that data values plotted
424  * will appear where they should within the chart.
425  */
426 static void
427 _set_transform_to_data_space (acre_t *acre)
428 {
429     cairo_t *cr = acre->cr;
430
431     cairo_translate (cr,
432                      acre->chart.x,
433                      acre->chart.y + acre->chart.height);
434     cairo_scale (cr,
435                  acre->chart.width / (acre->x_axis.max - acre->x_axis.min),
436                  - acre->chart.height /(acre->y_axis.max - acre->y_axis.min));
437     cairo_translate (cr, -acre->x_axis.min, -acre->y_axis.min);
438 }
439
440 static void
441 _compute_axis_ranges (acre_t *acre)
442 {
443     unsigned int d, i;
444     acre_data_t *data;
445     double x_adjust, y_adjust;
446     cairo_t *cr = acre->cr;
447
448     acre->x_axis.min = acre->data[0]->points[0].x;
449     acre->x_axis.max = acre->data[0]->points[0].x;
450     acre->y_axis.min = acre->data[0]->points[0].y;
451     acre->y_axis.min = acre->data[0]->points[0].y;
452
453     /* First, simply find the extrema of the data. */
454     for (d = 0; d < acre->num_data; d++) {
455         data = acre->data[d];
456         for (i = 0; i < data->num_points; i++) {
457             if (data->points[i].x < acre->x_axis.min)
458                 acre->x_axis.min = data->points[i].x;
459             if (data->points[i].x > acre->x_axis.max)
460                 acre->x_axis.max = data->points[i].x;
461
462             if (data->points[i].y < acre->y_axis.min)
463                 acre->y_axis.min = data->points[i].y;
464             if (data->points[i].y > acre->y_axis.max)
465                 acre->y_axis.max = data->points[i].y;
466         }
467     }
468
469     /* Next, we want to ensure that the data never collides with the
470      * ticks. So we expand each axis on its minimum side as needed. */
471     cairo_save (cr);
472     {
473         double x, y;
474
475         _set_transform_to_data_space (acre);
476
477         x = ACRE_TICK_MAJOR_SIZE + 2.0;
478         y = ACRE_TICK_MAJOR_SIZE + 2.0;
479         cairo_device_to_user_distance (cr, &x, &y);
480
481         acre->x_axis.min -= x;
482         acre->y_axis.min += y;
483     }
484     cairo_restore (cr);
485
486     /* Then, increase the axis ranges just enough so that the step
487      * sizes for the ticks will be integers.
488      */
489     _expand_range_for_width (&acre->x_axis.min,
490                              &acre->x_axis.max,
491                              acre->chart.width);
492
493     _expand_range_for_width (&acre->y_axis.min,
494                              &acre->y_axis.max,
495                              acre->chart.height);
496
497     /* Finally, we also translate the axis ranges slightly so that the
498      * ticks land on half-integer device-pixel positions.
499      */
500     cairo_save (cr);
501     {
502         _set_transform_to_data_space (acre);
503
504         x_adjust = 0.0;
505         y_adjust = 0.0;
506         cairo_user_to_device (cr, &x_adjust, &y_adjust);
507         x_adjust = (round (x_adjust + 0.5) - 0.5) - x_adjust;
508         y_adjust = (round (y_adjust + 0.5) - 0.5) - y_adjust;
509         cairo_device_to_user_distance (cr, &x_adjust, &y_adjust);
510
511         acre->x_axis.min -= x_adjust;
512         acre->x_axis.max -= x_adjust;
513
514         acre->y_axis.min -= y_adjust;
515         acre->y_axis.max -= y_adjust;
516     }
517     cairo_restore (cr);
518 }
519
520 static void
521 _acre_color_from_hsv (acre_color_t *color,
522                       double hue,
523                       double saturation,
524                       double value)
525 {
526     double f, p, q, t;
527     int hmod6;
528
529     hmod6 = (int) floor (hue / 60) % 6;
530     f = hue / 60 - floor (hue / 60);
531     p = value * (1 - saturation);
532     q = value * (1 - f * saturation);
533     t = value * (1 - (1 - f) * saturation);
534     
535     switch (hmod6) {
536       case 0:
537         color->red = value;
538         color->green = t;
539         color->blue = p;
540         break;
541       case 1:
542         color->red = q;
543         color->green = value;
544         color->blue = p;
545         break;
546       case 2:
547         color->red = p;
548         color->green = value;
549         color->blue = t;
550         break;
551       case 3:
552         color->red = p;
553         color->green = q;
554         color->blue = value;
555         break;
556       case 4:
557         color->red = t;
558         color->green = p;
559         color->blue = value;
560         break;
561       case 5:
562         color->red = value;
563         color->green = p;
564         color->blue = q;
565         break;
566     }
567 }
568
569 static void
570 _choose_colors (acre_t *acre)
571 {
572     double hue, saturation, value;
573     int i;
574
575     acre->num_colors = acre->num_data;
576
577     if (acre->num_colors > acre->colors_size) {
578         acre->colors_size = acre->num_colors;
579         acre->colors = xrealloc (acre->colors,
580                                  acre->colors_size * sizeof (acre_color_t));
581     }
582
583     saturation = 1.0;
584     value = 0.7;
585     for (i = 0; i < acre->num_colors; i++) {
586         hue = 360 * (double) i / acre->num_colors;
587         _acre_color_from_hsv (&acre->colors[i],
588                               hue, saturation, value);
589     }
590 }
591
592 static void
593 _draw_data (acre_t *acre)
594 {
595     cairo_t *cr = acre->cr;
596     unsigned int d, i;
597     acre_data_t *data;
598
599     cairo_save (cr);
600
601     cairo_set_source_rgb (cr, 0, 0, 0);
602
603     _set_transform_to_data_space (acre);
604
605     for (d = 0; d < acre->num_data; d++) {
606         int color = d % acre->num_colors;
607         cairo_set_source_rgb (cr,
608                               acre->colors[color].red,
609                               acre->colors[color].green,
610                               acre->colors[color].blue);
611         data = acre->data[d];
612         cairo_new_path (cr);
613         for (i = 0; i < data->num_points; i++) {
614             cairo_line_to (cr,
615                            data->points[i].x,
616                            data->points[i].y);
617         }
618         cairo_save (cr);
619         {
620             cairo_identity_matrix (cr);
621             cairo_set_line_width (cr, 1.0);
622             cairo_stroke (cr);
623         }
624         cairo_restore (cr);
625     }
626
627     cairo_restore (cr);
628 }
629
630 typedef enum _ticks { ACRE_TICKS_X, ACRE_TICKS_Y } acre_ticks_t;
631
632 static void
633 _draw_ticks (acre_t *acre,
634              double axis_min, double axis_max,
635              acre_ticks_t ticks)
636 {
637     cairo_t *cr = acre->cr;
638     double t, step, sub_step;
639     int minor_divisions;
640
641     cairo_save (cr);
642
643     _set_transform_to_data_space (acre);
644
645     step = _step_for_range (axis_max - axis_min, &minor_divisions);
646     sub_step = step / minor_divisions;
647
648     for (t = (floor (axis_min / sub_step) + 1) * sub_step;
649          t <= axis_max;
650          t += sub_step)
651     {
652         int tick_size;
653         if (fabs((t / step) - round (t / step)) < 0.5 * (sub_step / step))
654             tick_size = ACRE_TICK_MAJOR_SIZE;
655         else
656             tick_size = ACRE_TICK_MINOR_SIZE;
657
658         /* tick */
659         cairo_save (cr);
660         {
661             if (ticks == ACRE_TICKS_X)
662                 cairo_move_to (cr, t, acre->y_axis.min);
663             else
664                 cairo_move_to (cr, acre->x_axis.min, t);
665
666             cairo_identity_matrix (cr);
667
668             if (ticks == ACRE_TICKS_X) {
669                 cairo_rel_line_to (cr, 0, 0.5);
670                 cairo_rel_line_to (cr, 0, -tick_size - 0.5);
671             } else {
672                 cairo_rel_line_to (cr, -0.5, 0);
673                 cairo_rel_line_to (cr, tick_size + 0.5, 0);
674             }
675
676             cairo_set_line_width (cr, 1.0);
677             cairo_stroke (cr);
678         }
679         cairo_restore (cr);
680
681         /* label */
682         if (tick_size == ACRE_TICK_MAJOR_SIZE)
683         {
684             PangoLayout *layout;
685             int width, height;
686
687             cairo_save (cr);
688
689             layout = _create_layout_printf (acre, "%g", t);
690
691             if (ticks == ACRE_TICKS_X)
692                 cairo_move_to (cr, t, acre->y_axis.min);
693             else
694                 cairo_move_to (cr, acre->x_axis.min, t);
695
696             cairo_identity_matrix (cr);
697             pango_layout_get_pixel_size (layout, &width, &height);
698
699             if (ticks == ACRE_TICKS_X)
700                 cairo_rel_move_to (cr, -width / 2, ACRE_X_TICK_VALUE_PAD);
701             else
702                 cairo_rel_move_to (cr, -width - ACRE_Y_TICK_VALUE_PAD,
703                                    -height/2);
704
705             _show_layout (cr, layout);
706
707             cairo_restore (cr);
708         }
709     }
710
711     cairo_restore (cr);
712 }
713
714 static void
715 _draw_legend (acre_t *acre)
716 {
717     PangoLayout *layout;
718     int label_width, max_label_width = 0;
719     int width, height;
720     unsigned int i;
721     cairo_t *cr = acre->cr;
722
723     cairo_save (cr);
724
725     for (i = 0; i < acre->num_data; i++) {
726         layout = _create_layout (acre, acre->data[i]->name);
727         pango_layout_get_pixel_size (layout, &label_width, NULL);
728         _destroy_layout (layout);
729         if (label_width > max_label_width)
730             max_label_width = label_width;
731     }
732
733     width = ACRE_LEGEND_PAD + ACRE_LEGEND_LINE_SIZE + ACRE_LEGEND_PAD + 
734         max_label_width + ACRE_LEGEND_PAD;
735     height = ACRE_LEGEND_PAD +
736         acre->num_data * (ACRE_FONT_SIZE + ACRE_LEGEND_PAD);
737
738     cairo_translate (cr, acre->chart.x, acre->chart.y);
739
740     cairo_translate (cr,
741                      acre->chart.width - ACRE_LEGEND_PAD - width,
742                      ACRE_LEGEND_PAD);
743
744     cairo_rectangle (cr, -0.5, -0.5, width + 1.0, height + 1.0);
745     cairo_set_source_rgb (cr, 0, 0, 0);
746     cairo_set_line_width (cr, 1.0);
747     cairo_stroke (cr);
748
749     cairo_translate (cr, ACRE_LEGEND_PAD, ACRE_LEGEND_PAD);
750
751     for (i = 0; i < acre->num_data; i++) {
752         cairo_rectangle (cr,
753                          0, ACRE_LEGEND_LINE_SIZE / 2,
754                          ACRE_LEGEND_LINE_SIZE, ACRE_LEGEND_LINE_SIZE / 2);
755         cairo_set_source_rgb (cr,
756                               acre->colors[i % acre->num_colors].red,
757                               acre->colors[i % acre->num_colors].green,
758                               acre->colors[i % acre->num_colors].blue);
759         cairo_fill (cr);
760
761         layout = _create_layout (acre, acre->data[i]->name);
762         cairo_move_to (cr, ACRE_LEGEND_LINE_SIZE + ACRE_LEGEND_PAD, 0);
763         cairo_set_source_rgb (cr, 0, 0, 0);
764         _show_layout (cr, layout);
765
766         cairo_translate (cr, 0, ACRE_LEGEND_PAD + ACRE_FONT_SIZE);
767     }
768
769     cairo_restore (cr);
770 }
771
772 static void
773 _draw_frame_and_ticks (acre_t *acre)
774 {
775     cairo_t *cr = acre->cr;
776
777     cairo_save (cr);
778
779     cairo_set_source_rgb (cr, 0, 0, 0); /* black */
780
781     /* ticks */
782     _draw_ticks (acre, acre->x_axis.min, acre->x_axis.max, ACRE_TICKS_X);
783     _draw_ticks (acre, acre->y_axis.min, acre->y_axis.max, ACRE_TICKS_Y);
784
785     /* frame */
786     cairo_rectangle (cr,
787                      acre->chart.x - 0.5, acre->chart.y - 0.5,
788                      acre->chart.width + 1.0, acre->chart.height + 1.0);
789     cairo_set_line_width (cr, 1.0);
790     cairo_stroke (cr);
791
792     cairo_restore (cr);
793 }
794
795 /* Draw the plot to the given cairo context within a user-space
796  * rectangle from (0, 0) to (width, height). This size includes all
797  * space for extra-plot elements (such as the title, the axis labels,
798  * etc.)
799  */
800 void
801 acre_draw (acre_t *acre, cairo_t *cr, int width, int height)
802 {
803     acre->cr = cr;
804
805     acre->width = width;
806     acre->height = height;
807
808     acre->chart.width = width;
809     acre->chart.height = height;
810
811     cairo_save (cr);
812
813     cairo_set_source_rgb (cr, 1, 1, 1);
814
815     _choose_colors (acre);
816
817     /* We compute the axis ranges before doing label layout so that we
818      * can account for the width of the y-axis value labels. */
819     _compute_axis_ranges (acre);
820
821     _draw_title_and_labels (acre);
822
823     /* And we recompute the axis ranges now that the title and axis
824      * label space is all measured and accounted for. */
825     _compute_axis_ranges (acre);
826
827     _draw_data (acre);
828
829     if (acre->num_data > 1)
830         _draw_legend (acre);
831
832     _draw_frame_and_ticks (acre);
833 }
834
835 /* Create a new dataset---a collection of (x, y) datapoints. A single
836  * plot can contain multiple datasets, (see acre_add_data). */
837 acre_data_t *
838 acre_data_create (void)
839 {
840     acre_data_t *data;
841
842     data = xmalloc (sizeof (acre_data_t));
843
844     data->name = NULL;
845
846     data->points = NULL;
847     data->points_size = 0;
848     data->num_points = 0;
849
850     return data;
851 }
852
853 /* Destroy an acre dataset. Do not call this function if the dataset
854  * has been added to an acre_t plot with acre_add_data. */
855 void
856 acre_data_destroy (acre_data_t *data)
857 {
858     free (data->points);
859
860     free (data);
861 }
862
863 /* Set the label for this dataset (to appear in the plot's key). */
864 void
865 acre_data_set_name (acre_data_t *data, const char *name)
866 {
867     free (data->name);
868
869     data->name = strdup (name);
870 }
871
872 /* Add a datapoint to the given dataset. */
873 void
874 acre_data_add_point_2d (acre_data_t *data, double x, double y)
875 {
876     if (data->num_points >= data->points_size) {
877         data->points_size *= 2;
878         if (data->points_size == 0)
879             data->points_size = 16;
880         data->points = xrealloc_ab (data->points,
881                                     data->points_size,
882                                     sizeof (acre_data_point_2d_t));
883     }
884
885     data->points[data->num_points].x = x;
886     data->points[data->num_points].y = y;
887     data->num_points++;
888 }