]> git.cworth.org Git - acre/blob - acre.c
573569598aa413aa1b1cc94a183da18e8ef06a86
[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 #include "acre.h"
21 #include "xmalloc.h"
22
23 #include <string.h>
24 #include <math.h>
25
26 typedef struct _acre_data_point_2d {
27     double x;
28     double y;
29 } acre_data_point_2d_t;
30
31 struct _acre_data {
32     char *name;
33
34     acre_data_point_2d_t *points;
35     unsigned int points_size;
36     unsigned int num_points;
37 };
38
39 struct _acre {
40     char *title;
41     char *x_axis_label;
42     char *y_axis_label;
43
44     acre_data_t **data;
45     unsigned int data_size;
46     unsigned int num_data;
47
48     /* Data for drawing. */
49     cairo_t *cr;
50
51     /* Total size including labels. */
52     int width;
53     int height;
54
55     /* Position and size of chart alone. */
56     PangoRectangle chart;
57 };
58
59 /* Create a new, empty plot. */
60 acre_t *
61 acre_create (void)
62 {
63     acre_t *acre;
64
65     acre = xmalloc (sizeof (acre_t));
66
67     acre->title = NULL;
68     acre->x_axis_label = NULL;
69     acre->y_axis_label = NULL;
70
71     acre->data = NULL;
72     acre->data_size = 0;
73     acre->num_data = 0;
74
75     return acre;
76 }
77
78 /* Destroy a plot. */
79 void
80 acre_destroy (acre_t *acre)
81 {
82     unsigned int i;
83
84     free (acre->title);
85     free (acre->x_axis_label);
86     free (acre->y_axis_label);
87
88     for (i = 0; i < acre->num_data; i++)
89         acre_data_destroy (acre->data[i]);
90
91     free (acre->data);
92
93     free (acre);
94 }
95
96 void
97 acre_set_title (acre_t *acre, const char *title)
98 {
99     free (acre->title);
100
101     acre->title = strdup (title);
102 }
103
104 void
105 acre_set_x_axis_label (acre_t *acre, const char *label)
106 {
107     free (acre->x_axis_label);
108
109     acre->x_axis_label = strdup (label);
110 }
111
112 void
113 acre_set_y_axis_label (acre_t *acre, const char *label)
114 {
115     free (acre->y_axis_label);
116
117     acre->y_axis_label = strdup (label);
118 }
119
120 /* Add a dataset to the plot. The plot assumes ownership of the
121  * dataset so it is not necessary to call acre_data_destroy on it. */
122 void
123 acre_add_data (acre_t *acre, acre_data_t *data)
124 {
125     if (acre->num_data >= acre->data_size) {
126         acre->data_size *= 2;
127         if (acre->data_size == 0)
128             acre->data_size = 1;
129         acre->data = xrealloc_ab (acre->data,
130                                   acre->data_size,
131                                   sizeof (acre_data_t *));
132     }
133
134     acre->data[acre->num_data] = data;
135     acre->num_data++;
136 }
137
138 #define ACRE_FONT_FAMILY "sans"
139 #define ACRE_FONT_SIZE 12
140 #define ACRE_TITLE_FONT_SIZE 32
141 #define ACRE_PAD (ACRE_FONT_SIZE)
142 #define ACRE_TICK_SIZE 4
143
144 static void
145 _draw_title_and_labels (acre_t *acre)
146 {
147     cairo_t *cr = acre->cr;
148     PangoFontDescription *acre_font, *title_font;
149     PangoLayout *title_layout, *x_axis_layout, *y_axis_layout;
150     int title_width, title_height;
151     int x_axis_width, x_axis_height;
152     int y_axis_width, y_axis_height;
153     PangoRectangle new_chart;
154
155     cairo_save (cr);
156
157     acre_font = pango_font_description_new ();
158     pango_font_description_set_family (acre_font, ACRE_FONT_FAMILY);
159     pango_font_description_set_absolute_size (acre_font,
160                                               ACRE_FONT_SIZE * PANGO_SCALE);
161
162     title_font = pango_font_description_new ();
163     pango_font_description_set_family (title_font, ACRE_FONT_FAMILY);
164     pango_font_description_set_absolute_size (title_font,
165                                               ACRE_TITLE_FONT_SIZE * PANGO_SCALE);
166
167     title_layout = pango_cairo_create_layout (cr);
168     pango_layout_set_font_description (title_layout, title_font);
169     pango_layout_set_text (title_layout, acre->title, -1);
170     pango_layout_set_alignment (title_layout, PANGO_ALIGN_CENTER);
171
172     x_axis_layout = pango_cairo_create_layout (cr);
173     pango_layout_set_font_description (x_axis_layout, acre_font);
174     pango_layout_set_text (x_axis_layout, acre->x_axis_label, -1);
175     pango_layout_set_alignment (x_axis_layout, PANGO_ALIGN_CENTER);
176
177     y_axis_layout = pango_cairo_create_layout (cr);
178     pango_layout_set_font_description (y_axis_layout, acre_font);
179     pango_layout_set_text (y_axis_layout, acre->y_axis_label, -1);
180     pango_layout_set_alignment (y_axis_layout, PANGO_ALIGN_CENTER);
181
182     /* Iterate with the layout of the title and axis labels until they
183      * are stable, (this requires iteration since we don't know what
184      * to set their widths to in advance due to the wrapping of the
185      * other elements). */
186     acre->chart.x = 0;
187     acre->chart.y = 0;
188     acre->chart.width = acre->width;
189     acre->chart.height = acre->height;
190     while (1) {
191         pango_layout_set_width (title_layout, acre->chart.width * PANGO_SCALE);
192         pango_layout_set_width (x_axis_layout, acre->chart.width * PANGO_SCALE);
193         pango_layout_set_width (y_axis_layout, acre->chart.height * PANGO_SCALE);
194
195         pango_layout_get_pixel_size (title_layout, &title_width, &title_height);
196         pango_layout_get_pixel_size (x_axis_layout, &x_axis_width, &x_axis_height);
197         pango_layout_get_pixel_size (y_axis_layout, &y_axis_width, &y_axis_height);
198
199         new_chart.x = ACRE_PAD + y_axis_height +
200             ACRE_PAD + ACRE_FONT_SIZE;
201         new_chart.width = acre->width - acre->chart.x - ACRE_PAD;
202
203         new_chart.y = ACRE_PAD + title_height + ACRE_PAD;
204         new_chart.height = acre->height - acre->chart.y - (ACRE_FONT_SIZE + ACRE_PAD + x_axis_height + ACRE_PAD);
205
206         if (new_chart.x == acre->chart.x &&
207             new_chart.y == acre->chart.y &&
208             new_chart.width == acre->chart.width &&
209             new_chart.height == acre->chart.height)
210         {
211             break;
212         }
213
214         acre->chart.x = new_chart.x;
215         acre->chart.y = new_chart.y;
216         acre->chart.width = new_chart.width;
217         acre->chart.height = new_chart.height;
218     }
219
220     cairo_set_source_rgb (cr, 0, 0, 0);
221
222     cairo_move_to (cr, acre->chart.x, ACRE_PAD);
223     pango_cairo_show_layout (cr, title_layout);
224
225     cairo_save (cr);
226     {
227         cairo_translate (cr, ACRE_PAD, acre->chart.y + acre->chart.height);
228         cairo_rotate (cr, - M_PI / 2.0);
229         cairo_move_to (cr, 0, 0);
230         pango_cairo_show_layout (cr, y_axis_layout);
231     }
232     cairo_restore (cr);
233
234     cairo_move_to (cr, acre->chart.x,
235                    acre->chart.y + acre->chart.height +
236                    ACRE_FONT_SIZE + ACRE_PAD);
237     pango_cairo_show_layout (cr, x_axis_layout);
238
239     cairo_restore (cr);
240 }
241
242 static void
243 _draw_frame_and_ticks (acre_t *acre)
244 {
245     cairo_t *cr = acre->cr;
246
247     cairo_save (cr);
248
249     cairo_rectangle (cr,
250                      acre->chart.x - 0.5, acre->chart.y - 0.5,
251                      acre->chart.width + 1.0, acre->chart.height + 1.0);
252     cairo_set_line_width (cr, 1.0);
253     cairo_set_source_rgb (cr, 0, 0, 0);
254     cairo_stroke (cr);
255
256     cairo_restore (cr);
257 }
258
259 /* Draw the plot to the given cairo context within a user-space
260  * rectangle from (0, 0) to (width, height). This size includes all
261  * space for extra-plot elements (such as the title, the axis labels,
262  * etc.)
263  */
264 void
265 acre_draw (acre_t *acre, cairo_t *cr, int width, int height)
266 {
267     acre->cr = cr;
268     acre->width = width;
269     acre->height = height;
270
271     cairo_save (cr);
272
273     cairo_set_source_rgb (cr, 1, 1, 1);
274
275     cairo_paint (cr);
276
277     _draw_title_and_labels (acre);
278
279     _draw_frame_and_ticks (acre);
280 }
281
282 /* Create a new dataset---a collection of (x, y) datapoints. A single
283  * plot can contain multiple datasets, (see acre_add_data). */
284 acre_data_t *
285 acre_data_create (void)
286 {
287     acre_data_t *data;
288
289     data = xmalloc (sizeof (acre_data_t));
290
291     data->name = NULL;
292
293     data->points = NULL;
294     data->points_size = 0;
295     data->num_points = 0;
296
297     return data;
298 }
299
300 /* Destroy an acre dataset. Do not call this function if the dataset
301  * has been added to an acre_t plot with acre_add_data. */
302 void
303 acre_data_destroy (acre_data_t *data)
304 {
305     free (data->points);
306
307     free (data);
308 }
309
310 /* Set the label for this dataset (to appear in the plot's key). */
311 void
312 acre_data_set_name (acre_data_t *data, const char *name)
313 {
314     free (data->name);
315
316     data->name = strdup (name);
317 }
318
319 /* Add a datapoint to the given dataset. */
320 void
321 acre_data_add_point_2d (acre_data_t *data, double x, double y)
322 {
323     if (data->num_points >= data->points_size) {
324         data->points_size *= 2;
325         if (data->points_size == 0)
326             data->points_size = 16;
327         data->points = xrealloc_ab (data->points,
328                                     data->points_size,
329                                     sizeof (acre_data_point_2d_t));
330     }
331
332     data->points[data->num_points].x = x;
333     data->points[data->num_points].y = y;
334     data->num_points++;
335 }