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