output: Refactor implementation of charts.
[pspp-builds.git] / src / output / charts / piechart.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2004, 2009 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
18 #include <config.h>
19
20 #include <output/charts/piechart.h>
21
22 #include <assert.h>
23 #include <float.h>
24 #include <gsl/gsl_math.h>
25 #include <math.h>
26 #include <stdio.h>
27
28 #include <data/value-labels.h>
29 #include <libpspp/str.h>
30 #include <output/charts/plot-chart.h>
31 #include <output/chart-provider.h>
32
33 #include "minmax.h"
34
35 struct piechart
36   {
37     struct chart chart;
38     char *title;
39     struct slice *slices;
40     int n_slices;
41   };
42
43 static const struct chart_class piechart_class;
44
45 /* Draw a single slice of the pie */
46 static void
47 draw_segment(plPlotter *,
48              double centre_x, double centre_y,
49              double radius,
50              double start_angle, double segment_angle,
51              const char *colour) ;
52
53
54
55 /* Creates and returns a chart that will render a piechart with
56    the given TITLE and the N_SLICES described in SLICES. */
57 struct chart *
58 piechart_create (const char *title, const struct slice *slices, int n_slices)
59 {
60   struct piechart *pie;
61   int i;
62
63   pie = xmalloc (sizeof *pie);
64   chart_init (&pie->chart, &piechart_class);
65   pie->title = xstrdup (title);
66   pie->slices = xnmalloc (n_slices, sizeof *pie->slices);
67   for (i = 0; i < n_slices; i++)
68     {
69       const struct slice *src = &slices[i];
70       struct slice *dst = &pie->slices[i];
71
72       ds_init_string (&dst->label, &src->label);
73       dst->magnitude = src->magnitude;
74     }
75   pie->n_slices = n_slices;
76   return &pie->chart;
77 }
78
79 static void
80 piechart_draw (const struct chart *chart, plPlotter *lp)
81 {
82   struct piechart *pie = (struct piechart *) chart;
83   struct chart_geometry geom;
84   double total_magnitude;
85   double left_label, right_label;
86   double centre_x, centre_y;
87   double radius;
88   double angle;
89   int i;
90
91   chart_geometry_init (lp, &geom);
92
93   left_label = geom.data_left + (geom.data_right - geom.data_left)/10.0;
94   right_label = geom.data_right - (geom.data_right - geom.data_left)/10.0;
95
96   centre_x = (geom.data_right + geom.data_left) / 2.0 ;
97   centre_y = (geom.data_top + geom.data_bottom) / 2.0 ;
98
99   radius = MIN (5.0 / 12.0 * (geom.data_top - geom.data_bottom),
100                 1.0 / 4.0 * (geom.data_right - geom.data_left));
101
102   chart_write_title (lp, &geom, "%s", pie->title);
103
104   total_magnitude = 0.0;
105   for (i = 0; i < pie->n_slices; i++)
106     total_magnitude += pie->slices[i].magnitude;
107
108   angle = 0.0;
109   for (i = 0; i < pie->n_slices ; ++i )
110     {
111       const double segment_angle =
112         pie->slices[i].magnitude / total_magnitude * 2 * M_PI ;
113
114       const double label_x = centre_x -
115         radius * sin(angle + segment_angle/2.0);
116
117       const double label_y = centre_y +
118         radius * cos(angle + segment_angle/2.0);
119
120       /* Fill the segment */
121       draw_segment (lp,
122                     centre_x, centre_y, radius,
123                     angle, segment_angle,
124                     data_colour[i % N_CHART_COLOURS]);
125
126       /* Now add the labels */
127       if ( label_x < centre_x )
128         {
129           pl_line_r (lp, label_x, label_y, left_label, label_y );
130           pl_moverel_r (lp, 0, 5);
131           pl_alabel_r (lp, 0, 0, ds_cstr (&pie->slices[i].label));
132         }
133       else
134         {
135           pl_line_r (lp, label_x, label_y, right_label, label_y);
136           pl_moverel_r (lp, 0, 5);
137           pl_alabel_r (lp, 'r', 0, ds_cstr (&pie->slices[i].label));
138         }
139
140       angle += segment_angle;
141     }
142
143   /* Draw an outline to the pie */
144   pl_filltype_r (lp,0);
145   pl_fcircle_r (lp, centre_x, centre_y, radius);
146
147   chart_geometry_free (lp);
148 }
149
150 /* Fill a segment with the current fill colour */
151 static void
152 fill_segment(plPlotter *lp,
153              double x0, double y0,
154              double radius,
155              double start_angle, double segment_angle)
156 {
157
158   const double start_x  = x0 - radius * sin(start_angle);
159   const double start_y  = y0 + radius * cos(start_angle);
160
161   const double stop_x   =
162     x0 - radius * sin(start_angle + segment_angle);
163
164   const double stop_y   =
165     y0 + radius * cos(start_angle + segment_angle);
166
167   assert(segment_angle <= 2 * M_PI);
168   assert(segment_angle >= 0);
169
170   if ( segment_angle > M_PI )
171     {
172       /* Then we must draw it in two halves */
173       fill_segment(lp, x0, y0, radius, start_angle, segment_angle / 2.0 );
174       fill_segment(lp, x0, y0, radius, start_angle + segment_angle / 2.0,
175                    segment_angle / 2.0 );
176     }
177   else
178     {
179       pl_move_r(lp, x0, y0);
180
181       pl_cont_r(lp, stop_x, stop_y);
182       pl_cont_r(lp, start_x, start_y);
183
184       pl_arc_r(lp,
185                x0, y0,
186                stop_x, stop_y,
187                start_x, start_y
188                );
189
190       pl_endpath_r(lp);
191     }
192 }
193
194 /* Draw a single slice of the pie */
195 static void
196 draw_segment(plPlotter *lp,
197              double x0, double y0,
198              double radius,
199              double start_angle, double segment_angle,
200              const char *colour)
201 {
202   const double start_x  = x0 - radius * sin(start_angle);
203   const double start_y  = y0 + radius * cos(start_angle);
204
205   pl_savestate_r(lp);
206
207   pl_savestate_r(lp);
208   pl_colorname_r(lp, colour);
209
210   pl_pentype_r(lp,1);
211   pl_filltype_r(lp,1);
212
213   fill_segment(lp, x0, y0, radius, start_angle, segment_angle);
214   pl_restorestate_r(lp);
215
216   /* Draw line dividing segments */
217   pl_pentype_r(lp, 1);
218   pl_fline_r(lp, x0, y0, start_x, start_y);
219
220
221   pl_restorestate_r(lp);
222 }
223
224 static void
225 piechart_destroy (struct chart *chart)
226 {
227   struct piechart *pie = (struct piechart *) chart;
228   int i;
229
230   free (pie->title);
231   for (i = 0; i < pie->n_slices; i++)
232     {
233       struct slice *slice = &pie->slices[i];
234       ds_destroy (&slice->label);
235     }
236   free (pie->slices);
237   free (pie);
238 }
239
240 static const struct chart_class piechart_class =
241   {
242     piechart_draw,
243     piechart_destroy
244   };