]> git.cworth.org Git - acre/blob - acre.c
Split drawing of title and labels into its own function.
[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     acre_font = pango_font_description_new ();
156     pango_font_description_set_family (acre_font, ACRE_FONT_FAMILY);
157     pango_font_description_set_absolute_size (acre_font,
158                                               ACRE_FONT_SIZE * PANGO_SCALE);
159
160     title_font = pango_font_description_new ();
161     pango_font_description_set_family (title_font, ACRE_FONT_FAMILY);
162     pango_font_description_set_absolute_size (title_font,
163                                               ACRE_TITLE_FONT_SIZE * PANGO_SCALE);
164
165     title_layout = pango_cairo_create_layout (cr);
166     pango_layout_set_font_description (title_layout, title_font);
167     pango_layout_set_text (title_layout, acre->title, -1);
168     pango_layout_set_alignment (title_layout, PANGO_ALIGN_CENTER);
169
170     x_axis_layout = pango_cairo_create_layout (cr);
171     pango_layout_set_font_description (x_axis_layout, acre_font);
172     pango_layout_set_text (x_axis_layout, acre->x_axis_label, -1);
173     pango_layout_set_alignment (x_axis_layout, PANGO_ALIGN_CENTER);
174
175     y_axis_layout = pango_cairo_create_layout (cr);
176     pango_layout_set_font_description (y_axis_layout, acre_font);
177     pango_layout_set_text (y_axis_layout, acre->y_axis_label, -1);
178     pango_layout_set_alignment (y_axis_layout, PANGO_ALIGN_CENTER);
179
180     /* Iterate with the layout of the title and axis labels until they
181      * are stable, (this requires iteration since we don't know what
182      * to set their widths to in advance due to the wrapping of the
183      * other elements). */
184     acre->chart.x = 0;
185     acre->chart.y = 0;
186     acre->chart.width = acre->width;
187     acre->chart.height = acre->height;
188     while (1) {
189         pango_layout_set_width (title_layout, acre->chart.width * PANGO_SCALE);
190         pango_layout_set_width (x_axis_layout, acre->chart.width * PANGO_SCALE);
191         pango_layout_set_width (y_axis_layout, acre->chart.height * PANGO_SCALE);
192
193         pango_layout_get_pixel_size (title_layout, &title_width, &title_height);
194         pango_layout_get_pixel_size (x_axis_layout, &x_axis_width, &x_axis_height);
195         pango_layout_get_pixel_size (y_axis_layout, &y_axis_width, &y_axis_height);
196
197         new_chart.x = ACRE_PAD + y_axis_height +
198             ACRE_PAD + ACRE_FONT_SIZE;
199         new_chart.width = acre->width - acre->chart.x - ACRE_PAD;
200
201         new_chart.y = ACRE_PAD + title_height + ACRE_PAD;
202         new_chart.height = acre->height - acre->chart.y - (ACRE_FONT_SIZE + ACRE_PAD + x_axis_height + ACRE_PAD);
203
204         if (new_chart.x == acre->chart.x &&
205             new_chart.y == acre->chart.y &&
206             new_chart.width == acre->chart.width &&
207             new_chart.height == acre->chart.height)
208         {
209             break;
210         }
211
212         acre->chart.x = new_chart.x;
213         acre->chart.y = new_chart.y;
214         acre->chart.width = new_chart.width;
215         acre->chart.height = new_chart.height;
216     }
217
218     cairo_set_source_rgb (cr, 0, 0, 0);
219
220     cairo_move_to (cr, acre->chart.x, ACRE_PAD);
221     pango_cairo_show_layout (cr, title_layout);
222
223     cairo_save (cr);
224     {
225         cairo_translate (cr, ACRE_PAD, acre->chart.y + acre->chart.height);
226         cairo_rotate (cr, - M_PI / 2.0);
227         cairo_move_to (cr, 0, 0);
228         pango_cairo_show_layout (cr, y_axis_layout);
229     }
230     cairo_restore (cr);
231
232     cairo_move_to (cr, acre->chart.x,
233                    acre->chart.y + acre->chart.height +
234                    ACRE_FONT_SIZE + ACRE_PAD);
235     pango_cairo_show_layout (cr, x_axis_layout);
236 }
237
238 /* Draw the plot to the given cairo context within a user-space
239  * rectangle from (0, 0) to (width, height). This size includes all
240  * space for extra-plot elements (such as the title, the axis labels,
241  * etc.)
242  */
243 void
244 acre_draw (acre_t *acre, cairo_t *cr, int width, int height)
245 {
246     acre->cr = cr;
247     acre->width = width;
248     acre->height = height;
249
250     cairo_save (cr);
251
252     cairo_set_source_rgb (cr, 1, 1, 1);
253
254     cairo_paint (cr);
255
256     _draw_title_and_labels (acre);
257
258     cairo_rectangle (cr,
259                      acre->chart.x - 0.5, acre->chart.y - 0.5,
260                      acre->chart.width + 1.0, acre->chart.height + 1.0);
261     cairo_set_line_width (cr, 1.0);
262     cairo_stroke (cr);
263 }
264
265 /* Create a new dataset---a collection of (x, y) datapoints. A single
266  * plot can contain multiple datasets, (see acre_add_data). */
267 acre_data_t *
268 acre_data_create (void)
269 {
270     acre_data_t *data;
271
272     data = xmalloc (sizeof (acre_data_t));
273
274     data->name = NULL;
275
276     data->points = NULL;
277     data->points_size = 0;
278     data->num_points = 0;
279
280     return data;
281 }
282
283 /* Destroy an acre dataset. Do not call this function if the dataset
284  * has been added to an acre_t plot with acre_add_data. */
285 void
286 acre_data_destroy (acre_data_t *data)
287 {
288     free (data->points);
289
290     free (data);
291 }
292
293 /* Set the label for this dataset (to appear in the plot's key). */
294 void
295 acre_data_set_name (acre_data_t *data, const char *name)
296 {
297     free (data->name);
298
299     data->name = strdup (name);
300 }
301
302 /* Add a datapoint to the given dataset. */
303 void
304 acre_data_add_point_2d (acre_data_t *data, double x, double y)
305 {
306     if (data->num_points >= data->points_size) {
307         data->points_size *= 2;
308         if (data->points_size == 0)
309             data->points_size = 16;
310         data->points = xrealloc_ab (data->points,
311                                     data->points_size,
312                                     sizeof (acre_data_point_2d_t));
313     }
314
315     data->points[data->num_points].x = x;
316     data->points[data->num_points].y = y;
317     data->num_points++;
318 }