Histograms: Rotate labels when there are lots of bins
[pspp] / src / output / cairo-chart.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2004, 2009, 2010, 2011 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "output/cairo-chart.h"
20
21 #include <assert.h>
22 #include <cairo/cairo.h>
23 #include <pango/pango.h>
24 #include <pango/pangocairo.h>
25 #include <errno.h>
26 #include <float.h>
27 #include <math.h>
28 #include <stdarg.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include "libpspp/assertion.h"
34 #include "math/chart-geometry.h"
35 #include "output/cairo.h"
36 #include "output/chart-item.h"
37
38 #include "gl/error.h"
39 #include "gl/xalloc.h"
40 #include "gl/xvasprintf.h"
41
42 #include "gettext.h"
43 #define _(msgid) gettext (msgid)
44
45 void
46 xrchart_geometry_init (cairo_t *cr, struct xrchart_geometry *geom,
47                        double width, double length)
48 {
49   /* Set default chart geometry. */
50   geom->axis[SCALE_ORDINATE].data_max = 0.900 * length;
51   geom->axis[SCALE_ORDINATE].data_min = 0.120 * length;
52
53   geom->axis[SCALE_ABSCISSA].data_min = 0.150 * width;
54   geom->axis[SCALE_ABSCISSA].data_max = 0.800 * width;
55   geom->abscissa_bottom = 0.070 * length;
56   geom->ordinate_left = 0.050 * width;
57   geom->title_bottom = 0.920 * length;
58   geom->legend_left = 0.810 * width;
59   geom->legend_right = width;
60   geom->font_size = 15.0;
61   geom->in_path = false;
62   geom->dataset = NULL;
63   geom->n_datasets = 0;
64
65   geom->fill_colour.red = 255;
66   geom->fill_colour.green = 0;
67   geom->fill_colour.blue = 0;
68
69   cairo_set_line_width (cr, 1.0);
70
71   cairo_rectangle (cr, geom->axis[SCALE_ABSCISSA].data_min, geom->axis[SCALE_ORDINATE].data_min,
72                    geom->axis[SCALE_ABSCISSA].data_max - geom->axis[SCALE_ABSCISSA].data_min,
73                    geom->axis[SCALE_ORDINATE].data_max - geom->axis[SCALE_ORDINATE].data_min);
74   cairo_stroke (cr);
75 }
76
77 void
78 xrchart_geometry_free (cairo_t *cr UNUSED, struct xrchart_geometry *geom)
79 {
80   int i;
81
82   for (i = 0 ; i < geom->n_datasets; ++i)
83     free (geom->dataset[i]);
84   free (geom->dataset);
85 }
86
87 #if ! PANGO_VERSION_CHECK (1, 22, 0)
88 int pango_layout_get_baseline (PangoLayout    *layout);
89
90 /* Shamelessly copied from the pango source */
91 int
92 pango_layout_get_baseline (PangoLayout    *layout)
93 {
94   int baseline;
95
96   /* XXX this is so inefficient */
97   PangoLayoutIter *iter = pango_layout_get_iter (layout);
98   baseline = pango_layout_iter_get_baseline (iter);
99   pango_layout_iter_free (iter);
100
101   return baseline;
102 }
103 #endif
104
105
106
107 const struct xrchart_colour data_colour[XRCHART_N_COLOURS] =
108   {
109     { 165, 42, 42 },            /* brown */
110     { 255, 0, 0 },              /* red */
111     { 255, 165, 0 },            /* orange */
112     { 255, 255, 0 },            /* yellow */
113     { 0, 255, 0 },              /* green */
114     { 0, 0, 255 },              /* blue */
115     { 238, 130, 238 },          /* violet */
116     { 190, 190, 190 },          /* grey */
117     { 255, 192, 203 },          /* pink */
118   };
119
120 void
121 xrchart_draw_marker (cairo_t *cr, double x, double y,
122                      enum xrmarker_type marker, double size)
123 {
124   cairo_save (cr);
125   cairo_translate (cr, x, y);
126   cairo_scale (cr, size / 2.0, size / 2.0);
127   cairo_set_line_width (cr, cairo_get_line_width (cr) / (size / 2.0));
128   switch (marker)
129     {
130     case XRMARKER_CIRCLE:
131       cairo_arc (cr, 0, 0, 1.0, 0, 2 * M_PI);
132       cairo_stroke (cr);
133       break;
134
135     case XRMARKER_ASTERISK:
136       cairo_move_to (cr, 0, -1.0); /* | */
137       cairo_line_to (cr, 0, 1.0);
138       cairo_move_to (cr, -M_SQRT1_2, -M_SQRT1_2); /* / */
139       cairo_line_to (cr, M_SQRT1_2, M_SQRT1_2);
140       cairo_move_to (cr, -M_SQRT1_2, M_SQRT1_2); /* \ */
141       cairo_line_to (cr, M_SQRT1_2, -M_SQRT1_2);
142       cairo_stroke (cr);
143       break;
144
145     case XRMARKER_SQUARE:
146       cairo_rectangle (cr, -1.0, -1.0, 2.0, 2.0);
147       cairo_stroke (cr);
148       break;
149     }
150   cairo_restore (cr);
151 }
152
153 void
154 xrchart_label_rotate (cairo_t *cr, int horz_justify, int vert_justify,
155                       double font_size, const char *string, double angle)
156 {
157   PangoFontDescription *desc;
158   PangoLayout *layout;
159   double x, y;
160
161   desc = pango_font_description_from_string ("sans serif");
162   if (desc == NULL)
163     {
164       cairo_new_path (cr);
165       return;
166     }
167   pango_font_description_set_absolute_size (desc, font_size * PANGO_SCALE);
168
169   cairo_save (cr);
170   cairo_rotate (cr, angle);
171   cairo_get_current_point (cr, &x, &y);
172   cairo_translate (cr, x, y);
173   cairo_move_to (cr, 0, 0);
174   cairo_scale (cr, 1.0, -1.0);
175
176   layout = pango_cairo_create_layout (cr);
177   pango_layout_set_font_description (layout, desc);
178   pango_layout_set_text (layout, string, -1);
179   if (horz_justify != 'l')
180     {
181       int width_pango;
182       double width;
183
184       pango_layout_get_size (layout, &width_pango, NULL);
185       width = (double) width_pango / PANGO_SCALE;
186       if (horz_justify == 'r')
187         cairo_rel_move_to (cr, -width, 0);
188       else
189         cairo_rel_move_to (cr, -width / 2.0, 0);
190     }
191   if (vert_justify == 'x')
192     {
193       int baseline_pango = pango_layout_get_baseline (layout);
194       double baseline = (double) baseline_pango / PANGO_SCALE;
195       cairo_rel_move_to (cr, 0, -baseline);
196     }
197   else if (vert_justify != 't')
198     {
199       int height_pango;
200       double height;
201
202       pango_layout_get_size (layout, NULL, &height_pango);
203       height = (double) height_pango / PANGO_SCALE;
204       if (vert_justify == 'b')
205         cairo_rel_move_to (cr, 0, -height);
206       else if (vert_justify == 'c')
207         cairo_rel_move_to (cr, 0, -height / 2.0);
208     }
209   pango_cairo_show_layout (cr, layout);
210   g_object_unref (layout);
211
212   cairo_restore (cr);
213
214   cairo_new_path (cr);
215
216   pango_font_description_free (desc);
217 }
218
219 void
220 xrchart_label (cairo_t *cr, int horz_justify, int vert_justify,
221                double font_size, const char *string)
222 {
223   xrchart_label_rotate (cr, horz_justify, vert_justify, font_size, string, 0);
224 }
225
226
227 /* Draw a tick mark at position
228    If label is non null, then print it at the tick mark
229 */
230 static void
231 draw_tick_internal (cairo_t *cr, const struct xrchart_geometry *geom,
232                     enum tick_orientation orientation,
233                     bool rotated,
234                     double position,
235                     const char *s);
236
237 void
238 draw_tick (cairo_t *cr, const struct xrchart_geometry *geom,
239            enum tick_orientation orientation,
240            bool rotated,
241            double position,
242            const char *label, ...)
243 {
244   va_list ap;
245   char *s;
246   va_start (ap, label);
247   s = xvasprintf (label, ap);
248
249   if (fabs (position) < DBL_EPSILON)
250     position = 0;
251
252   draw_tick_internal (cr, geom, orientation, rotated, position, s);
253   free (s);
254   va_end (ap);
255 }
256
257
258 static void
259 draw_tick_internal (cairo_t *cr, const struct xrchart_geometry *geom,
260                     enum tick_orientation orientation,
261                     bool rotated,
262                     double position,
263                     const char *s)
264 {
265   const int tickSize = 10;
266   double x, y;
267
268   cairo_move_to (cr, geom->axis[SCALE_ABSCISSA].data_min, geom->axis[SCALE_ORDINATE].data_min);
269
270   if (orientation == SCALE_ABSCISSA)
271     {
272       cairo_rel_move_to (cr, position, 0);
273       cairo_rel_line_to (cr, 0, -tickSize);
274     }
275   else if (orientation == SCALE_ORDINATE)
276     {
277       cairo_rel_move_to (cr, 0, position);
278       cairo_rel_line_to (cr, -tickSize, 0);
279     }
280   else
281     NOT_REACHED ();
282   cairo_get_current_point (cr, &x, &y);
283
284   cairo_stroke (cr);
285
286   if (s != NULL)
287     {
288       cairo_move_to (cr, x, y);
289
290       if (orientation == SCALE_ABSCISSA)
291         {
292           if ( rotated) 
293             xrchart_label_rotate (cr, 'l', 'c', geom->font_size, s, -G_PI_4);
294           else
295             xrchart_label (cr, 'c', 't', geom->font_size, s);
296         }
297       else if (orientation == SCALE_ORDINATE)
298         {
299           if (fabs (position) < DBL_EPSILON)
300             cairo_rel_move_to (cr, 0, 10);
301           xrchart_label (cr, 'r', 'c', geom->font_size, s);
302         }
303     }
304 }
305
306
307 /* Write the title on a chart*/
308 void
309 xrchart_write_title (cairo_t *cr, const struct xrchart_geometry *geom,
310                    const char *title, ...)
311 {
312   va_list ap;
313   char *s;
314
315   cairo_save (cr);
316   cairo_move_to (cr, geom->axis[SCALE_ABSCISSA].data_min, geom->title_bottom);
317
318   va_start(ap, title);
319   s = xvasprintf (title, ap);
320   xrchart_label (cr, 'l', 'x', geom->font_size * 1.5, s);
321   free (s);
322   va_end (ap);
323
324   cairo_restore (cr);
325 }
326
327
328
329 static void
330 xrchart_write_scale (cairo_t *cr, struct xrchart_geometry *geom,
331                      double smin, double smax, int ticks, enum tick_orientation orient)
332 {
333   int s;
334
335   const double tick_interval =
336     chart_rounded_tick ((smax - smin) / (double) ticks);
337
338   int upper = ceil (smax / tick_interval);
339   int lower = floor (smin / tick_interval);
340
341   geom->axis[orient].max = tick_interval * upper;
342   geom->axis[orient].min = tick_interval * lower;
343
344   geom->axis[orient].scale = (fabs (geom->axis[orient].data_max - geom->axis[orient].data_min)
345      / fabs (geom->axis[orient].max - geom->axis[orient].min));
346
347   for (s = 0 ; s < upper - lower; ++s)
348     {
349       double pos = (s + lower) * tick_interval;
350       draw_tick (cr, geom, orient, false,
351                  s * tick_interval * geom->axis[orient].scale, "%g", pos);
352     }
353 }
354
355 /* Set the scale for the ordinate */
356 void
357 xrchart_write_yscale (cairo_t *cr, struct xrchart_geometry *geom,
358                     double smin, double smax, int ticks)
359 {
360   xrchart_write_scale (cr, geom, smin, smax, ticks, SCALE_ORDINATE);
361 }
362
363 /* Set the scale for the abscissa */
364 void
365 xrchart_write_xscale (cairo_t *cr, struct xrchart_geometry *geom,
366                     double smin, double smax, int ticks)
367 {
368   xrchart_write_scale (cr, geom, smin, smax, ticks, SCALE_ABSCISSA);
369 }
370
371
372 /* Write the abscissa label */
373 void
374 xrchart_write_xlabel (cairo_t *cr, const struct xrchart_geometry *geom,
375                     const char *label)
376 {
377   cairo_move_to (cr, geom->axis[SCALE_ABSCISSA].data_min, geom->abscissa_bottom);
378   xrchart_label (cr, 'l', 't', geom->font_size, label);
379 }
380
381 /* Write the ordinate label */
382 void
383 xrchart_write_ylabel (cairo_t *cr, const struct xrchart_geometry *geom,
384                     const char *label)
385 {
386   cairo_save (cr);
387   cairo_translate (cr, geom->ordinate_left,   geom->axis[SCALE_ORDINATE].data_min);
388   cairo_rotate (cr, M_PI / 2.0);
389
390   xrchart_label (cr, 'l', 'x', geom->font_size, label);
391   cairo_restore (cr);
392 }
393
394
395 void
396 xrchart_write_legend (cairo_t *cr, const struct xrchart_geometry *geom)
397 {
398   int i;
399   const int vstep = geom->font_size * 2;
400   const int xpad = 10;
401   const int ypad = 10;
402   const int swatch = 20;
403   const int legend_top = geom->axis[SCALE_ORDINATE].data_max;
404   const int legend_bottom = legend_top -
405     (vstep * geom->n_datasets + 2 * ypad );
406
407   cairo_save (cr);
408
409   cairo_rectangle (cr, geom->legend_left, legend_top,
410                    geom->legend_right - xpad - geom->legend_left,
411                    legend_bottom - legend_top);
412   cairo_stroke (cr);
413
414   for (i = 0 ; i < geom->n_datasets ; ++i )
415     {
416       const int ypos = legend_top - vstep * (i + 1);
417       const int xpos = geom->legend_left + xpad;
418       const struct xrchart_colour *colour;
419
420       cairo_move_to (cr, xpos, ypos);
421
422       cairo_save (cr);
423       colour = &data_colour [ i % XRCHART_N_COLOURS];
424       cairo_set_source_rgb (cr,
425                             colour->red / 255.0,
426                             colour->green / 255.0,
427                             colour->blue / 255.0);
428       cairo_rectangle (cr, xpos, ypos, swatch, swatch);
429       cairo_fill_preserve (cr);
430       cairo_stroke (cr);
431       cairo_restore (cr);
432
433       cairo_move_to (cr, xpos + swatch * 1.5, ypos);
434       xrchart_label (cr, 'l', 'x', geom->font_size, geom->dataset[i]);
435     }
436
437   cairo_restore (cr);
438 }
439
440 /* Start a new vector called NAME */
441 void
442 xrchart_vector_start (cairo_t *cr, struct xrchart_geometry *geom, const char *name)
443 {
444   const struct xrchart_colour *colour;
445
446   cairo_save (cr);
447
448   colour = &data_colour[geom->n_datasets % XRCHART_N_COLOURS];
449   cairo_set_source_rgb (cr,
450                         colour->red / 255.0,
451                         colour->green / 255.0,
452                         colour->blue / 255.0);
453
454   geom->n_datasets++;
455   geom->dataset = xrealloc (geom->dataset,
456                             geom->n_datasets * sizeof (*geom->dataset));
457
458   geom->dataset[geom->n_datasets - 1] = strdup (name);
459 }
460
461 /* Plot a data point */
462 void
463 xrchart_datum (cairo_t *cr, const struct xrchart_geometry *geom,
464              int dataset UNUSED, double x, double y)
465 {
466   double x_pos = (x - geom->axis[SCALE_ABSCISSA].min) * geom->axis[SCALE_ABSCISSA].scale + geom->axis[SCALE_ABSCISSA].data_min;
467   double y_pos = (y - geom->axis[SCALE_ORDINATE].min) * geom->axis[SCALE_ORDINATE].scale + geom->axis[SCALE_ORDINATE].data_min;
468
469   xrchart_draw_marker (cr, x_pos, y_pos, XRMARKER_SQUARE, 15);
470 }
471
472 void
473 xrchart_vector_end (cairo_t *cr, struct xrchart_geometry *geom)
474 {
475   cairo_stroke (cr);
476   cairo_restore (cr);
477   geom->in_path = false;
478 }
479
480 /* Plot a data point */
481 void
482 xrchart_vector (cairo_t *cr, struct xrchart_geometry *geom, double x, double y)
483 {
484   const double x_pos =
485     (x - geom->axis[SCALE_ABSCISSA].min) * geom->axis[SCALE_ABSCISSA].scale + geom->axis[SCALE_ABSCISSA].data_min ;
486
487   const double y_pos =
488     (y - geom->axis[SCALE_ORDINATE].min) * geom->axis[SCALE_ORDINATE].scale + geom->axis[SCALE_ORDINATE].data_min ;
489
490   if (geom->in_path)
491     cairo_line_to (cr, x_pos, y_pos);
492   else
493     {
494       cairo_move_to (cr, x_pos, y_pos);
495       geom->in_path = true;
496     }
497 }
498
499
500
501 /* Draw a line with slope SLOPE and intercept INTERCEPT.
502    between the points limit1 and limit2.
503    If lim_dim is XRCHART_DIM_Y then the limit{1,2} are on the
504    y axis otherwise the x axis
505 */
506 void
507 xrchart_line(cairo_t *cr, const struct xrchart_geometry *geom,
508            double slope, double intercept,
509            double limit1, double limit2, enum xrchart_dim lim_dim)
510 {
511   double x1, y1;
512   double x2, y2;
513
514   if ( lim_dim == XRCHART_DIM_Y )
515     {
516       x1 = ( limit1 - intercept ) / slope;
517       x2 = ( limit2 - intercept ) / slope;
518       y1 = limit1;
519       y2 = limit2;
520     }
521   else
522     {
523       x1 = limit1;
524       x2 = limit2;
525       y1 = slope * x1 + intercept;
526       y2 = slope * x2 + intercept;
527     }
528
529   y1 = (y1 - geom->axis[SCALE_ORDINATE].min) * geom->axis[SCALE_ORDINATE].scale + geom->axis[SCALE_ORDINATE].data_min;
530   y2 = (y2 - geom->axis[SCALE_ORDINATE].min) * geom->axis[SCALE_ORDINATE].scale + geom->axis[SCALE_ORDINATE].data_min;
531   x1 = (x1 - geom->axis[SCALE_ABSCISSA].min) * geom->axis[SCALE_ABSCISSA].scale + geom->axis[SCALE_ABSCISSA].data_min;
532   x2 = (x2 - geom->axis[SCALE_ABSCISSA].min) * geom->axis[SCALE_ABSCISSA].scale + geom->axis[SCALE_ABSCISSA].data_min;
533
534   cairo_move_to (cr, x1, y1);
535   cairo_line_to (cr, x2, y2);
536   cairo_stroke (cr);
537 }