]> git.cworth.org Git - acre/blob - acre.c
Draw title and axis labels with pango
[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
49 /* Create a new, empty plot. */
50 acre_t *
51 acre_create (void)
52 {
53     acre_t *acre;
54
55     acre = xmalloc (sizeof (acre_t));
56
57     acre->title = NULL;
58     acre->x_axis_label = NULL;
59     acre->y_axis_label = NULL;
60
61     acre->data = NULL;
62     acre->data_size = 0;
63     acre->num_data = 0;
64
65     return acre;
66 }
67
68 /* Destroy a plot. */
69 void
70 acre_destroy (acre_t *acre)
71 {
72     unsigned int i;
73
74     free (acre->title);
75     free (acre->x_axis_label);
76     free (acre->y_axis_label);
77
78     for (i = 0; i < acre->num_data; i++)
79         acre_data_destroy (acre->data[i]);
80
81     free (acre->data);
82
83     free (acre);
84 }
85
86 void
87 acre_set_title (acre_t *acre, const char *title)
88 {
89     free (acre->title);
90
91     acre->title = strdup (title);
92 }
93
94 void
95 acre_set_x_axis_label (acre_t *acre, const char *label)
96 {
97     free (acre->x_axis_label);
98
99     acre->x_axis_label = strdup (label);
100 }
101
102 void
103 acre_set_y_axis_label (acre_t *acre, const char *label)
104 {
105     free (acre->y_axis_label);
106
107     acre->y_axis_label = strdup (label);
108 }
109
110 /* Add a dataset to the plot. The plot assumes ownership of the
111  * dataset so it is not necessary to call acre_data_destroy on it. */
112 void
113 acre_add_data (acre_t *acre, acre_data_t *data)
114 {
115     if (acre->num_data >= acre->data_size) {
116         acre->data_size *= 2;
117         if (acre->data_size == 0)
118             acre->data_size = 1;
119         acre->data = xrealloc_ab (acre->data,
120                                   acre->data_size,
121                                   sizeof (acre_data_t *));
122     }
123
124     acre->data[acre->num_data] = data;
125     acre->num_data++;
126 }
127
128 #define ACRE_FONT_FAMILY "sans"
129 #define ACRE_FONT_SIZE 12
130 #define ACRE_TITLE_FONT_SIZE 32
131 #define ACRE_PAD (ACRE_FONT_SIZE)
132 #define ACRE_TICK_SIZE 4
133
134 /* Draw the plot to the given cairo context within a user-space
135  * rectangle from (0, 0) to (width, height). This size includes all
136  * space for extra-plot elements (such as the title, the axis labels,
137  * etc.)
138  */
139 void
140 acre_draw (acre_t *acre, cairo_t *cr, double width, double height)
141 {
142     PangoFontDescription *acre_font, *title_font;
143     PangoLayout *title_layout, *x_axis_layout, *y_axis_layout;
144     int title_width, title_height;
145     int x_axis_width, x_axis_height;
146     int y_axis_width, y_axis_height;
147     PangoRectangle chart, new_chart;
148
149     cairo_save (cr);
150
151     cairo_set_source_rgb (cr, 1, 1, 1);
152
153     cairo_paint (cr);
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     chart.x = 0;
185     chart.y = 0;
186     chart.width = width;
187     chart.height = height;
188     while (1) {
189         pango_layout_set_width (title_layout, chart.width * PANGO_SCALE);
190         pango_layout_set_width (x_axis_layout, chart.width * PANGO_SCALE);
191         pango_layout_set_width (y_axis_layout, 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 = width - chart.x - ACRE_PAD;
200
201         new_chart.y = ACRE_PAD + title_height + ACRE_PAD;
202         new_chart.height = height - chart.y - (ACRE_FONT_SIZE + ACRE_PAD + x_axis_height + ACRE_PAD);
203
204         if (new_chart.x == chart.x &&
205             new_chart.y == chart.y &&
206             new_chart.width == chart.width &&
207             new_chart.height == chart.height)
208         {
209             break;
210         }
211
212         chart.x = new_chart.x;
213         chart.y = new_chart.y;
214         chart.width = new_chart.width;
215         chart.height = new_chart.height;
216     }
217
218     cairo_set_source_rgb (cr, 0, 0, 0);
219
220     cairo_move_to (cr, chart.x, ACRE_PAD);
221     pango_cairo_show_layout (cr, title_layout);
222
223     cairo_save (cr);
224     {
225         cairo_translate (cr, ACRE_PAD, chart.y + 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, chart.x,
233                    chart.y + chart.height + ACRE_FONT_SIZE + ACRE_PAD);
234     pango_cairo_show_layout (cr, x_axis_layout);
235
236     cairo_rectangle (cr,
237                      chart.x - 0.5, chart.y - 0.5,
238                      chart.width + 1.0, chart.height + 1.0);
239     cairo_set_line_width (cr, 1.0);
240     cairo_stroke (cr);
241 }
242
243 /* Create a new dataset---a collection of (x, y) datapoints. A single
244  * plot can contain multiple datasets, (see acre_add_data). */
245 acre_data_t *
246 acre_data_create (void)
247 {
248     acre_data_t *data;
249
250     data = xmalloc (sizeof (acre_data_t));
251
252     data->name = NULL;
253
254     data->points = NULL;
255     data->points_size = 0;
256     data->num_points = 0;
257
258     return data;
259 }
260
261 /* Destroy an acre dataset. Do not call this function if the dataset
262  * has been added to an acre_t plot with acre_add_data. */
263 void
264 acre_data_destroy (acre_data_t *data)
265 {
266     free (data->points);
267
268     free (data);
269 }
270
271 /* Set the label for this dataset (to appear in the plot's key). */
272 void
273 acre_data_set_name (acre_data_t *data, const char *name)
274 {
275     free (data->name);
276
277     data->name = strdup (name);
278 }
279
280 /* Add a datapoint to the given dataset. */
281 void
282 acre_data_add_point_2d (acre_data_t *data, double x, double y)
283 {
284     if (data->num_points >= data->points_size) {
285         data->points_size *= 2;
286         if (data->points_size == 0)
287             data->points_size = 16;
288         data->points = xrealloc_ab (data->points,
289                                     data->points_size,
290                                     sizeof (acre_data_point_2d_t));
291     }
292
293     data->points[data->num_points].x = x;
294     data->points[data->num_points].y = y;
295     data->num_points++;
296 }