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