ddecbfccceb2564c347738b5f53f7f864d285d90
[pspp-builds.git] / src / output / charts / boxplot-cairo.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 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 #include <config.h>
18
19 #include <output/cairo-chart.h>
20
21 #include <math.h>
22
23 #include <output/charts/boxplot.h>
24 #include <math/box-whisker.h>
25 #include <math/chart-geometry.h>
26
27 /* Draw an OUTLIER on the plot CH
28  * at CENTRELINE
29  */
30 static void
31 draw_case (cairo_t *cr, const struct xrchart_geometry *geom, double centreline,
32            const struct outlier *outlier)
33 {
34   double y = geom->data_bottom + (outlier->value - geom->y_min) * geom->ordinate_scale;
35   xrchart_draw_marker (cr, centreline, y,
36                      outlier->extreme ? XRMARKER_ASTERISK : XRMARKER_CIRCLE,
37                      20);
38
39   cairo_move_to (cr, centreline + 10, y);
40   xrchart_label (cr, 'l', 'c', geom->font_size, ds_cstr (&outlier->label));
41 }
42
43 static void
44 boxplot_draw_box (cairo_t *cr, const struct xrchart_geometry *geom,
45                   double box_centre,
46                   double box_width,
47                   const struct box_whisker *bw,
48                   const char *name)
49 {
50   double whisker[2];
51   double hinge[3];
52   struct ll *ll;
53
54   const struct ll_list *outliers;
55
56   const double box_left = box_centre - box_width / 2.0;
57
58   const double box_right = box_centre + box_width / 2.0;
59
60   double box_bottom ;
61   double box_top ;
62   double bottom_whisker ;
63   double top_whisker ;
64
65   box_whisker_whiskers (bw, whisker);
66   box_whisker_hinges (bw, hinge);
67
68   box_bottom = geom->data_bottom + (hinge[0] - geom->y_min ) * geom->ordinate_scale;
69
70   box_top = geom->data_bottom + (hinge[2] - geom->y_min ) * geom->ordinate_scale;
71
72   bottom_whisker = geom->data_bottom + (whisker[0] - geom->y_min) *
73     geom->ordinate_scale;
74
75   top_whisker = geom->data_bottom + (whisker[1] - geom->y_min) * geom->ordinate_scale;
76
77   /* Draw the box */
78   cairo_rectangle (cr,
79                    box_left,
80                    box_bottom,
81                    box_right - box_left,
82                    box_top - box_bottom);
83   cairo_save (cr);
84   cairo_set_source_rgb (cr,
85                         geom->fill_colour.red / 255.0,
86                         geom->fill_colour.green / 255.0,
87                         geom->fill_colour.blue / 255.0);
88   cairo_fill (cr);
89   cairo_restore (cr);
90   cairo_stroke (cr);
91
92   /* Draw the median */
93   cairo_save (cr);
94   cairo_set_line_width (cr, cairo_get_line_width (cr) * 5);
95   cairo_move_to (cr,
96                  box_left,
97                  geom->data_bottom + (hinge[1] - geom->y_min) * geom->ordinate_scale);
98   cairo_line_to (cr,
99                  box_right,
100                  geom->data_bottom + (hinge[1] - geom->y_min) * geom->ordinate_scale);
101   cairo_stroke (cr);
102   cairo_restore (cr);
103
104   /* Draw the bottom whisker */
105   cairo_move_to (cr, box_left, bottom_whisker);
106   cairo_line_to (cr, box_right, bottom_whisker);
107   cairo_stroke (cr);
108
109   /* Draw top whisker */
110   cairo_move_to (cr, box_left, top_whisker);
111   cairo_line_to (cr, box_right, top_whisker);
112   cairo_stroke (cr);
113
114   /* Draw centre line.
115      (bottom half) */
116   cairo_move_to (cr, box_centre, bottom_whisker);
117   cairo_line_to (cr, box_centre, box_bottom);
118   cairo_stroke (cr);
119
120   /* (top half) */
121   cairo_move_to (cr, box_centre, top_whisker);
122   cairo_line_to (cr, box_centre, box_top);
123   cairo_stroke (cr);
124
125   outliers = box_whisker_outliers (bw);
126   for (ll = ll_head (outliers);
127        ll != ll_null (outliers); ll = ll_next (ll))
128     {
129       const struct outlier *outlier = ll_data (ll, struct outlier, ll);
130       draw_case (cr, geom, box_centre, outlier);
131     }
132
133   /* Draw  tick  mark on x axis */
134   draw_tick(cr, geom, TICK_ABSCISSA, box_centre - geom->data_left, "%s", name);
135 }
136
137 static void
138 boxplot_draw_yscale (cairo_t *cr, struct xrchart_geometry *geom,
139                      double y_max, double y_min)
140 {
141   double y_tick;
142   double d;
143
144   geom->y_max = y_max;
145   geom->y_min = y_min;
146
147   y_tick = chart_rounded_tick (fabs (geom->y_max - geom->y_min) / 5.0);
148
149   geom->y_min = (ceil (geom->y_min / y_tick) - 1.0) * y_tick;
150
151   geom->y_max = (floor (geom->y_max / y_tick) + 1.0) * y_tick;
152
153   geom->ordinate_scale = (fabs (geom->data_top - geom->data_bottom)
154                           / fabs (geom->y_max - geom->y_min));
155
156   for (d = geom->y_min; d <= geom->y_max; d += y_tick)
157     draw_tick (cr, geom, TICK_ORDINATE,
158                (d - geom->y_min) * geom->ordinate_scale, "%g", d);
159 }
160
161 void
162 xrchart_draw_boxplot (const struct chart_item *chart_item, cairo_t *cr,
163                       struct xrchart_geometry *geom)
164 {
165   const struct boxplot *boxplot = to_boxplot (chart_item);
166   double box_width;
167   size_t i;
168
169   boxplot_draw_yscale (cr, geom, boxplot->y_max, boxplot->y_min);
170   xrchart_write_title (cr, geom, "%s", chart_item->title);
171
172   box_width = (geom->data_right - geom->data_left) / boxplot->n_boxes / 2.0;
173   for (i = 0; i < boxplot->n_boxes; i++)
174     {
175       const struct boxplot_box *box = &boxplot->boxes[i];
176       const double box_centre = (i * 2 + 1) * box_width + geom->data_left;
177       boxplot_draw_box (cr, geom, box_centre, box_width, box->bw, box->label);
178     }
179 }