Fix handling of #! at beginning of PSPP syntax file; add regression test.
[pspp-builds.git] / src / output / charts / piechart.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2004 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 <float.h>
21 #include <assert.h>
22 #include <math.h>
23 #include <stdio.h>
24
25
26 #include <output/charts/piechart.h>
27 #include <output/charts/plot-chart.h>
28
29 #include <output/chart.h>
30 #include <libpspp/str.h>
31 #include <data/value-labels.h>
32
33 #include "minmax.h"
34
35
36 /* Pie charts of course need to know Pi :) */
37 #ifndef M_PI
38 #define M_PI ( 22.0 / 7.0 )
39 #endif
40
41
42
43 /* Draw a single slice of the pie */
44 static void
45 draw_segment(struct chart *ch,
46              double centre_x, double centre_y,
47              double radius,
48              double start_angle, double segment_angle,
49              const char *colour) ;
50
51
52
53 /* Draw a piechart */
54 void
55 piechart_plot(const char *title, const struct slice *slices, int n_slices)
56 {
57   int i;
58   double total_magnetude=0;
59
60   struct chart *ch = chart_create();
61
62   const double left_label = ch->data_left +
63     (ch->data_right - ch->data_left)/10.0;
64
65   const double right_label = ch->data_right -
66     (ch->data_right - ch->data_left)/10.0;
67
68   const double centre_x = (ch->data_right + ch->data_left ) / 2.0 ;
69   const double centre_y = (ch->data_top + ch->data_bottom ) / 2.0 ;
70
71   const double radius = MIN(
72                             5.0 / 12.0 * (ch->data_top - ch->data_bottom),
73                             1.0 / 4.0 * (ch->data_right - ch->data_left)
74                             );
75
76
77   chart_write_title(ch, title);
78
79   for (i = 0 ; i < n_slices ; ++i )
80     total_magnetude += slices[i].magnetude;
81
82   for (i = 0 ; i < n_slices ; ++i )
83     {
84       static double angle=0.0;
85
86       const double segment_angle =
87         slices[i].magnetude / total_magnetude * 2 * M_PI ;
88
89       const double label_x = centre_x -
90         radius * sin(angle + segment_angle/2.0);
91
92       const double label_y = centre_y +
93         radius * cos(angle + segment_angle/2.0);
94
95       /* Fill the segment */
96       draw_segment(ch,
97                    centre_x, centre_y, radius,
98                    angle, segment_angle,
99                    data_colour[i % N_CHART_COLOURS]);
100
101       /* Now add the labels */
102       if ( label_x < centre_x )
103         {
104           pl_line_r(ch->lp, label_x, label_y,
105                     left_label, label_y );
106           pl_moverel_r(ch->lp,0,5);
107           pl_alabel_r (ch->lp, 0, 0, ds_cstr (&slices[i].label));
108         }
109       else
110         {
111           pl_line_r(ch->lp,
112                     label_x, label_y,
113                     right_label, label_y
114                     );
115           pl_moverel_r(ch->lp,0,5);
116           pl_alabel_r (ch->lp, 'r', 0, ds_cstr (&slices[i].label));
117         }
118
119       angle += segment_angle;
120
121     }
122
123   /* Draw an outline to the pie */
124   pl_filltype_r(ch->lp,0);
125   pl_fcircle_r (ch->lp, centre_x, centre_y, radius);
126
127   chart_submit(ch);
128 }
129
130 static void
131 fill_segment(struct chart *ch,
132              double x0, double y0,
133              double radius,
134              double start_angle, double segment_angle) ;
135
136
137 /* Fill a segment with the current fill colour */
138 static void
139 fill_segment(struct chart *ch,
140              double x0, double y0,
141              double radius,
142              double start_angle, double segment_angle)
143 {
144
145   const double start_x  = x0 - radius * sin(start_angle);
146   const double start_y  = y0 + radius * cos(start_angle);
147
148   const double stop_x   =
149     x0 - radius * sin(start_angle + segment_angle);
150
151   const double stop_y   =
152     y0 + radius * cos(start_angle + segment_angle);
153
154   assert(segment_angle <= 2 * M_PI);
155   assert(segment_angle >= 0);
156
157   if ( segment_angle > M_PI )
158     {
159       /* Then we must draw it in two halves */
160       fill_segment(ch, x0, y0, radius, start_angle, segment_angle / 2.0 );
161       fill_segment(ch, x0, y0, radius, start_angle + segment_angle / 2.0,
162                    segment_angle / 2.0 );
163     }
164   else
165     {
166       pl_move_r(ch->lp, x0, y0);
167
168       pl_cont_r(ch->lp, stop_x, stop_y);
169       pl_cont_r(ch->lp, start_x, start_y);
170
171       pl_arc_r(ch->lp,
172                x0, y0,
173                stop_x, stop_y,
174                start_x, start_y
175                );
176
177       pl_endpath_r(ch->lp);
178     }
179 }
180
181
182
183 /* Draw a single slice of the pie */
184 static void
185 draw_segment(struct chart *ch,
186              double x0, double y0,
187              double radius,
188              double start_angle, double segment_angle,
189              const char *colour)
190 {
191   const double start_x  = x0 - radius * sin(start_angle);
192   const double start_y  = y0 + radius * cos(start_angle);
193
194   pl_savestate_r(ch->lp);
195
196   pl_savestate_r(ch->lp);
197   pl_colorname_r(ch->lp, colour);
198
199   pl_pentype_r(ch->lp,1);
200   pl_filltype_r(ch->lp,1);
201
202   fill_segment(ch, x0, y0, radius, start_angle, segment_angle);
203   pl_restorestate_r(ch->lp);
204
205   /* Draw line dividing segments */
206   pl_pentype_r(ch->lp, 1);
207   pl_fline_r(ch->lp, x0, y0, start_x, start_y);
208
209
210   pl_restorestate_r(ch->lp);
211 }
212