d799d7431d2c247f077108b7e931e2ee416e64f7
[pspp] / src / output / charts / box-whisker.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2004, 2008, 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/box-whisker.h>
21
22 #include <math.h>
23 #include <assert.h>
24
25 #include <libpspp/misc.h>
26 #include <math/chart-geometry.h>
27 #include <math/box-whisker.h>
28 #include <output/chart.h>
29 #include <output/chart-provider.h>
30 #include <output/charts/plot-chart.h>
31
32 /* Draw a box-and-whiskers plot
33 */
34
35 struct box
36   {
37     struct box_whisker *bw;
38     char *label;
39   };
40
41 struct boxplot
42   {
43     struct chart chart;
44     double y_min;
45     double y_max;
46     char *title;
47     struct box *boxes;
48     size_t n_boxes, boxes_allocated;
49   };
50
51 static const struct chart_class boxplot_chart_class;
52
53 struct boxplot *
54 boxplot_create (double y_min, double y_max, const char *title)
55 {
56   struct boxplot *boxplot = xmalloc (sizeof *boxplot);
57   chart_init (&boxplot->chart, &boxplot_chart_class);
58   boxplot->y_min = y_min;
59   boxplot->y_max = y_max;
60   boxplot->title = xstrdup (title);
61   boxplot->boxes = NULL;
62   boxplot->n_boxes = boxplot->boxes_allocated = 0;
63   return boxplot;
64 }
65
66 void
67 boxplot_add_box (struct boxplot *boxplot,
68                  struct box_whisker *bw, const char *label)
69 {
70   struct box *box;
71   if (boxplot->n_boxes >= boxplot->boxes_allocated)
72     boxplot->boxes = x2nrealloc (boxplot->boxes, &boxplot->boxes_allocated,
73                                  sizeof *boxplot->boxes);
74   box = &boxplot->boxes[boxplot->n_boxes++];
75   box->bw = bw;
76   box->label = xstrdup (label);
77 }
78
79 struct chart *
80 boxplot_get_chart (struct boxplot *boxplot)
81 {
82   return &boxplot->chart;
83 }
84
85 /* Draw an OUTLIER on the plot CH
86  * at CENTRELINE
87  */
88 static void
89 draw_case (plPlotter *lp, const struct chart_geometry *geom, double centreline,
90            const struct outlier *outlier)
91 {
92
93 #define MARKER_CIRCLE 4
94 #define MARKER_STAR 3
95
96   pl_fmarker_r(lp,
97                centreline,
98                geom->data_bottom + (outlier->value - geom->y_min) * geom->ordinate_scale,
99                outlier->extreme ? MARKER_STAR : MARKER_CIRCLE,
100                20);
101
102   pl_moverel_r(lp, 10,0);
103
104   pl_alabel_r(lp, 'l', 'c', ds_cstr (&outlier->label));
105 }
106
107 static void
108 boxplot_draw_box (plPlotter *lp, const struct chart_geometry *geom,
109                   double box_centre,
110                   double box_width,
111                   const struct box_whisker *bw,
112                   const char *name)
113 {
114   double whisker[2];
115   double hinge[3];
116   struct ll *ll;
117
118   const struct ll_list *outliers;
119
120   const double box_left = box_centre - box_width / 2.0;
121
122   const double box_right = box_centre + box_width / 2.0;
123
124   double box_bottom ;
125   double box_top ;
126   double bottom_whisker ;
127   double top_whisker ;
128
129   box_whisker_whiskers (bw, whisker);
130   box_whisker_hinges (bw, hinge);
131
132   box_bottom = geom->data_bottom + (hinge[0] - geom->y_min ) * geom->ordinate_scale;
133
134   box_top = geom->data_bottom + (hinge[2] - geom->y_min ) * geom->ordinate_scale;
135
136   bottom_whisker = geom->data_bottom + (whisker[0] - geom->y_min) *
137     geom->ordinate_scale;
138
139   top_whisker = geom->data_bottom + (whisker[1] - geom->y_min) * geom->ordinate_scale;
140
141   pl_savestate_r(lp);
142
143   /* Draw the box */
144   pl_savestate_r (lp);
145   pl_fillcolor_r (lp,
146                   geom->fill_colour.red * 257,
147                   geom->fill_colour.green * 257,
148                   geom->fill_colour.blue * 257);
149   pl_filltype_r (lp,1);
150   pl_fbox_r (lp,
151             box_left,
152             box_bottom,
153             box_right,
154             box_top);
155
156   pl_restorestate_r (lp);
157
158   /* Draw the median */
159   pl_savestate_r (lp);
160   pl_linewidth_r (lp, 5);
161   pl_fline_r (lp,
162              box_left,
163              geom->data_bottom + (hinge[1] - geom->y_min) * geom->ordinate_scale,
164              box_right,
165              geom->data_bottom + (hinge[1] - geom->y_min) * geom->ordinate_scale);
166   pl_restorestate_r (lp);
167
168   /* Draw the bottom whisker */
169   pl_fline_r (lp,
170              box_left,
171              bottom_whisker,
172              box_right,
173              bottom_whisker);
174
175   /* Draw top whisker */
176   pl_fline_r (lp,
177              box_left,
178              top_whisker,
179              box_right,
180              top_whisker);
181
182
183   /* Draw centre line.
184      (bottom half) */
185   pl_fline_r (lp,
186              box_centre, bottom_whisker,
187              box_centre, box_bottom);
188
189   /* (top half) */
190   pl_fline_r (lp,
191              box_centre, top_whisker,
192              box_centre, box_top);
193
194   outliers = box_whisker_outliers (bw);
195   for (ll = ll_head (outliers);
196        ll != ll_null (outliers); ll = ll_next (ll))
197     {
198       const struct outlier *outlier = ll_data (ll, struct outlier, ll);
199       draw_case (lp, geom, box_centre, outlier);
200     }
201
202   /* Draw  tick  mark on x axis */
203   draw_tick(lp, geom, TICK_ABSCISSA, box_centre - geom->data_left, "%s", name);
204
205   pl_restorestate_r(lp);
206 }
207
208 static void
209 boxplot_draw_yscale (plPlotter *lp, struct chart_geometry *geom,
210                      double y_max, double y_min)
211 {
212   double y_tick;
213   double d;
214
215   geom->y_max = y_max;
216   geom->y_min = y_min;
217
218   y_tick = chart_rounded_tick (fabs (geom->y_max - geom->y_min) / 5.0);
219
220   geom->y_min = (ceil (geom->y_min / y_tick) - 1.0) * y_tick;
221
222   geom->y_max = (floor (geom->y_max / y_tick) + 1.0) * y_tick;
223
224   geom->ordinate_scale = (fabs (geom->data_top - geom->data_bottom)
225                           / fabs (geom->y_max - geom->y_min));
226
227   /* Move to data bottom-left */
228   pl_move_r (lp, geom->data_left, geom->data_bottom);
229
230   for (d = geom->y_min; d <= geom->y_max; d += y_tick)
231     draw_tick (lp, geom, TICK_ORDINATE,
232                (d - geom->y_min) * geom->ordinate_scale, "%g", d);
233 }
234
235 static void
236 boxplot_chart_draw (const struct chart *chart, plPlotter *lp,
237                     struct chart_geometry *geom)
238 {
239   const struct boxplot *boxplot = (struct boxplot *) chart;
240   double box_width;
241   size_t i;
242
243   boxplot_draw_yscale (lp, geom, boxplot->y_max, boxplot->y_min);
244   chart_write_title (lp, geom, "%s", boxplot->title);
245
246   box_width = (geom->data_right - geom->data_left) / boxplot->n_boxes / 2.0;
247   for (i = 0; i < boxplot->n_boxes; i++)
248     {
249       const struct box *box = &boxplot->boxes[i];
250       const double box_centre = (i * 2 + 1) * box_width + geom->data_left;
251       boxplot_draw_box (lp, geom, box_centre, box_width, box->bw, box->label);
252     }
253 }
254
255 static void
256 boxplot_chart_destroy (struct chart *chart)
257 {
258   struct boxplot *boxplot = (struct boxplot *) chart;
259   size_t i;
260
261   free (boxplot->title);
262   for (i = 0; i < boxplot->n_boxes; i++)
263     {
264       struct box *box = &boxplot->boxes[i];
265       struct statistic *statistic = &box->bw->parent.parent;
266       statistic->destroy (statistic);
267       free (box->label);
268     }
269   free (boxplot->boxes);
270   free (boxplot);
271 }
272
273 static const struct chart_class boxplot_chart_class =
274   {
275     boxplot_chart_draw,
276     boxplot_chart_destroy
277   };