4917c77320b450a365081cc841fda5ef118e37c4
[pspp-builds.git] / src / output / charts / box-whisker.c
1 /* PSPP - computes sample statistics.
2    Copyright (C) 2004 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or
5    modify it under the terms of the GNU General Public License as
6    published by the Free Software Foundation; either version 2 of the
7    License, or (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful, but
10    WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    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, write to the Free Software
16    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17    02110-1301, USA. */
18
19
20 #include <config.h>
21
22 #include <math.h>
23 #include <assert.h>
24 #include <libpspp/misc.h>
25
26 #include <output/charts/box-whisker.h>
27 #include <output/charts/plot-chart.h>
28
29 #include <output/chart.h>
30 #include <math/chart-geometry.h>
31 #include <math/factor-stats.h>
32
33
34
35
36 /* Draw a box-and-whiskers plot
37 */
38
39 /* Draw an outlier on the plot CH
40  * at CENTRELINE
41  * The outlier is in (*wvp)[idx]
42  * If EXTREME is non zero, then consider it to be an extreme
43  * value
44  */
45 void
46 draw_outlier(struct chart *ch, double centreline,
47              struct weighted_value **wvp,
48              int idx,
49              short extreme);
50
51
52 void
53 draw_outlier(struct chart *ch, double centreline,
54              struct weighted_value **wvp,
55              int idx,
56              short extreme
57              )
58 {
59   char label[10];
60
61 #define MARKER_CIRCLE 4
62 #define MARKER_STAR 3
63
64   pl_fmarker_r(ch->lp,
65                centreline,
66                ch->data_bottom +
67                (wvp[idx]->v.f - ch->y_min ) * ch->ordinate_scale,
68                extreme?MARKER_STAR:MARKER_CIRCLE,
69                20);
70
71   pl_moverel_r(ch->lp, 10,0);
72
73   snprintf(label, 10, "%d", wvp[idx]->case_nos->num);
74
75   pl_alabel_r(ch->lp, 'l', 'c', label);
76
77 }
78
79
80 void
81 boxplot_draw_boxplot(struct chart *ch,
82                      double box_centre,
83                      double box_width,
84                      struct metrics *m,
85                      const char *name)
86 {
87   double whisker[2];
88   int i;
89
90   const double *hinge = m->hinge;
91   struct weighted_value **wvp = m->wvp;
92   const int n_data = m->n_data;
93
94   const double step = (hinge[2] - hinge[0]) * 1.5;
95
96
97   const double box_left = box_centre - box_width / 2.0;
98
99   const double box_right = box_centre + box_width / 2.0;
100
101
102   const double box_bottom =
103     ch->data_bottom + ( hinge[0] - ch->y_min ) * ch->ordinate_scale;
104
105
106   const double box_top =
107     ch->data_bottom + ( hinge[2] - ch->y_min ) * ch->ordinate_scale;
108
109   assert(m);
110
111   /* Can't really draw a boxplot if there's no data */
112   if ( n_data == 0 )
113           return ;
114
115   whisker[1] = hinge[2];
116   whisker[0] = wvp[0]->v.f;
117
118   for ( i = 0 ; i < n_data ; ++i )
119     {
120       if ( hinge[2] + step >  wvp[i]->v.f)
121         whisker[1] = wvp[i]->v.f;
122
123       if ( hinge[0] - step >  wvp[i]->v.f)
124         whisker[0] = wvp[i]->v.f;
125
126     }
127
128   {
129   const double bottom_whisker =
130     ch->data_bottom + ( whisker[0] - ch->y_min ) * ch->ordinate_scale;
131
132   const double top_whisker =
133     ch->data_bottom + ( whisker[1] - ch->y_min ) * ch->ordinate_scale;
134
135
136   pl_savestate_r(ch->lp);
137
138
139   /* Draw the box */
140   pl_savestate_r(ch->lp);
141   pl_fillcolorname_r(ch->lp,ch->fill_colour);
142   pl_filltype_r(ch->lp,1);
143   pl_fbox_r(ch->lp,
144             box_left,
145             box_bottom,
146             box_right,
147             box_top);
148
149   pl_restorestate_r(ch->lp);
150
151
152
153   /* Draw the median */
154   pl_savestate_r(ch->lp);
155   pl_linewidth_r(ch->lp,5);
156   pl_fline_r(ch->lp,
157              box_left,
158              ch->data_bottom + ( hinge[1] - ch->y_min ) * ch->ordinate_scale,
159              box_right,
160              ch->data_bottom + ( hinge[1] - ch->y_min ) * ch->ordinate_scale);
161   pl_restorestate_r(ch->lp);
162
163
164   /* Draw the bottom whisker */
165   pl_fline_r(ch->lp,
166              box_left,
167              bottom_whisker,
168              box_right,
169              bottom_whisker);
170
171   /* Draw top whisker */
172   pl_fline_r(ch->lp,
173              box_left,
174              top_whisker,
175              box_right,
176              top_whisker);
177
178
179
180   /* Draw centre line.
181      (bottom half) */
182   pl_fline_r(ch->lp,
183              box_centre, bottom_whisker,
184              box_centre, box_bottom);
185
186   /* (top half) */
187   pl_fline_r(ch->lp,
188              box_centre, top_whisker,
189              box_centre, box_top);
190   }
191
192   /* Draw outliers */
193   for ( i = 0 ; i < n_data ; ++i )
194     {
195       if ( wvp[i]->v.f >= hinge[2] + step )
196         draw_outlier(ch, box_centre, wvp, i,
197                      ( wvp[i]->v.f > hinge[2] + 2 * step )
198                      );
199
200       if ( wvp[i]->v.f <= hinge[0] - step )
201         draw_outlier(ch, box_centre, wvp, i,
202                      ( wvp[i]->v.f < hinge[0] - 2 * step )
203                      );
204     }
205
206
207   /* Draw  tick  mark on x axis */
208   draw_tick(ch, TICK_ABSCISSA, box_centre - ch->data_left, name);
209
210   pl_restorestate_r(ch->lp);
211
212 }
213
214
215
216 void
217 boxplot_draw_yscale(struct chart *ch , double y_max, double y_min)
218 {
219   double y_tick;
220   double d;
221
222   if ( !ch )
223      return ;
224
225   ch->y_max  = y_max;
226   ch->y_min  = y_min;
227
228   y_tick = chart_rounded_tick(fabs(ch->y_max - ch->y_min) / 5.0);
229
230   ch->y_min = (ceil( ch->y_min  / y_tick ) - 1.0  ) * y_tick;
231
232   ch->y_max = ( floor( ch->y_max  / y_tick ) + 1.0  ) * y_tick;
233
234   ch->ordinate_scale = fabs(ch->data_top - ch->data_bottom)
235     / fabs(ch->y_max - ch->y_min) ;
236
237
238   /* Move to data bottom-left */
239   pl_move_r(ch->lp,
240             ch->data_left, ch->data_bottom);
241
242   for ( d = ch->y_min; d <= ch->y_max ; d += y_tick )
243     {
244       draw_tick (ch, TICK_ORDINATE, (d - ch->y_min ) * ch->ordinate_scale, "%g", d);
245     }
246
247 }