Piecharts: Avoid the segment fill obscuring the labels
[pspp] / src / output / charts / piechart-cairo.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2009, 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/charts/piechart.h"
20
21 #include <math.h>
22
23 #include "output/cairo-chart.h"
24
25 #include "gl/minmax.h"
26
27 /* Draw a single slice of the pie */
28 static void
29 draw_segment(cairo_t *cr,
30              double x0, double y0,
31              double radius,
32              double start_angle, double segment_angle,
33              const struct xrchart_colour *colour)
34 {
35   cairo_move_to (cr, x0, y0);
36   cairo_arc (cr, x0, y0, radius, start_angle, start_angle + segment_angle);
37   cairo_line_to (cr, x0, y0);
38   cairo_save (cr);
39   cairo_set_source_rgb (cr,
40                         colour->red / 255.0,
41                         colour->green / 255.0,
42                         colour->blue / 255.0);
43   cairo_fill_preserve (cr);
44   cairo_restore (cr);
45   cairo_stroke (cr);
46 }
47
48 void
49 xrchart_draw_piechart (const struct chart_item *chart_item, cairo_t *cr,
50                        struct xrchart_geometry *geom)
51 {
52   const struct piechart *pie = to_piechart (chart_item);
53   double total_magnitude;
54   double left_label, right_label;
55   double centre_x, centre_y;
56   double radius;
57   double angle;
58   int i;
59
60   centre_x = (geom->axis[SCALE_ABSCISSA].data_max + geom->axis[SCALE_ORDINATE].data_min) / 2.0 ;
61   centre_y = (geom->axis[SCALE_ORDINATE].data_max + geom->axis[SCALE_ORDINATE].data_min) / 2.0 ;
62
63   left_label = geom->axis[SCALE_ORDINATE].data_min + (geom->axis[SCALE_ABSCISSA].data_max - geom->axis[SCALE_ORDINATE].data_min)/10.0;
64   right_label = geom->axis[SCALE_ABSCISSA].data_max - (geom->axis[SCALE_ABSCISSA].data_max - geom->axis[SCALE_ORDINATE].data_min)/10.0;
65
66   radius = MIN (5.0 / 12.0 * (geom->axis[SCALE_ORDINATE].data_max - geom->axis[SCALE_ORDINATE].data_min),
67                 1.0 / 4.0 * (geom->axis[SCALE_ABSCISSA].data_max - geom->axis[SCALE_ORDINATE].data_min));
68
69   radius = MIN (5.0 / 12.0 * (geom->axis[SCALE_ORDINATE].data_max - geom->axis[SCALE_ORDINATE].data_min),
70                 1.0 / 4.0 * (geom->axis[SCALE_ABSCISSA].data_max - geom->axis[SCALE_ORDINATE].data_min));
71
72   xrchart_write_title (cr, geom, "%s", chart_item_get_title (chart_item));
73
74   total_magnitude = 0.0;
75   for (i = 0; i < pie->n_slices; i++)
76     total_magnitude += pie->slices[i].magnitude;
77
78
79   /* Draw the segments */
80   angle = 0.0;
81   for (i = 0; i < pie->n_slices ; ++i )
82     {
83       const double segment_angle =
84         pie->slices[i].magnitude / total_magnitude * 2 * M_PI ;
85
86       /* Fill the segment */
87       draw_segment (cr,
88                     centre_x, centre_y, radius,
89                     angle, segment_angle,
90                     &data_colour[i % XRCHART_N_COLOURS]);
91
92       angle += segment_angle;
93     }
94
95
96   /* Now add the labels.
97      Don't put this in the loop above;  the labels must
98      be put in last, otherwise the segment fill could
99      obscure them.
100    */
101   angle = 0.0;
102   for (i = 0; i < pie->n_slices ; ++i )
103     {
104       const double segment_angle =
105         pie->slices[i].magnitude / total_magnitude * 2 * M_PI ;
106
107       const double label_x = centre_x +
108         radius * cos (angle + segment_angle/2.0);
109
110       const double label_y = centre_y +
111         radius * sin (angle + segment_angle/2.0);
112
113       if ( label_x < centre_x )
114         {
115           cairo_move_to (cr, label_x, label_y);
116           cairo_line_to (cr, left_label, label_y);
117           cairo_stroke (cr);
118           cairo_move_to (cr, left_label, label_y + 5);
119           xrchart_label (cr, 'l', 'x', geom->font_size,
120                          ds_cstr (&pie->slices[i].label));
121         }
122       else
123         {
124           cairo_move_to (cr, label_x, label_y);
125           cairo_line_to (cr, right_label, label_y);
126           cairo_stroke (cr);
127           cairo_move_to (cr, right_label, label_y + 5);
128           xrchart_label (cr, 'r', 'x', geom->font_size,
129                          ds_cstr (&pie->slices[i].label));
130         }
131
132       angle += segment_angle;
133     }
134
135   /* Draw an outline to the pie */
136   cairo_arc (cr, centre_x, centre_y, radius, 0, 2 * M_PI);
137   cairo_stroke (cr);
138 }
139