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