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