Added the /BARCHART option to CROSSTABS
[pspp] / src / output / charts / barchart.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2015 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/charts/barchart.h"
20 #include "output/charts/piechart.h"
21
22 #include <stdlib.h>
23
24 #include "libpspp/cast.h"
25 #include "libpspp/str.h"
26 #include "libpspp/array.h"
27 #include "output/chart-item-provider.h"
28
29 #include "gl/xalloc.h"
30 #include "data/variable.h"
31 #include "language/stats/freq.h"
32
33
34 static int
35 compare_category_3way (const void *a_, const void *b_, const void *bc_)
36 {
37   const struct category *const*a = a_;
38   const struct category *const*b = b_;
39   const struct barchart *bc = bc_;
40
41   return value_compare_3way (&(*a)->val, &(*b)->val, var_get_width (bc->var[1]));
42 }
43
44
45 static unsigned int
46 hash_freq_2level_ptr (const void *a_, const void *bc_)
47 {
48   const struct freq *const *ap = a_;
49   const struct barchart *bc = bc_;
50
51   size_t hash = value_hash (&(*ap)->values[0], bc->widths[0], 0);
52
53   if (bc->n_vars > 1)
54     hash = value_hash (&(*ap)->values[1], bc->widths[1], hash);
55
56   return hash;
57 }
58
59
60 static int
61 compare_freq_2level_ptr_3way (const void *a_, const void *b_, const void *bc_)
62 {
63   const struct freq *const *ap = a_;
64   const struct freq *const *bp = b_;
65   const struct barchart *bc = bc_;
66
67   const int level0 = value_compare_3way (&(*ap)->values[0], &(*bp)->values[0], bc->widths[0]);
68
69   if (level0 == 0 && bc->n_vars > 1)
70     return value_compare_3way (&(*ap)->values[1], &(*bp)->values[1], bc->widths[1]);
71
72   return level0;
73 }
74
75
76
77 /* Creates and returns a chart that will render a barchart with
78    the given TITLE and the N_BARS described in CATS. */
79 struct chart_item *
80 barchart_create (const struct variable **var, int n_vars,
81                  const char *ylabel, 
82                  struct freq *const *cats, int n_cats)
83 {
84   struct barchart *bar;
85   int i;
86
87   const int pidx = 0;
88   const int sidx = 1;
89
90
91   int width = var_get_width (var[pidx]);
92
93   assert (n_vars >= 1);
94
95   bar = xzalloc (sizeof *bar);
96   bar->var = var;
97   bar->n_vars = n_vars;
98   bar->n_nzcats = n_cats;
99   chart_item_init (&bar->chart_item, &barchart_class, var_to_string (var[pidx]));
100
101   bar->largest = -1;
102   bar->ylabel = strdup (ylabel);
103
104     {
105       int idx = 0;
106       hmap_init (&bar->primaries);
107
108       /* 
109          Iterate the categories and create a hash table of the primary categories.
110          We need to do this to find out how many there are and to cache the labels.
111       */
112       for (i = 0; i < n_cats; i++)
113         {
114           const struct freq *src = cats[i];
115           size_t hash = value_hash (&src->values[pidx], width, 0);
116
117           struct category *foo;
118           int flag = 0;
119           HMAP_FOR_EACH_WITH_HASH (foo, struct category, node, hash, &bar->primaries)
120             {
121               if (value_equal (&foo->val, &src->values[pidx], width))
122                 {
123                   flag = 1;
124                   break;
125                 }
126             }
127
128           if (!flag) 
129             {
130               struct category *s = xzalloc (sizeof *s);
131               s->idx = idx++;
132               value_init (&s->val, var_get_width (var[pidx]));
133               value_copy (&s->val, &src->values[pidx], var_get_width (var[pidx]));
134               ds_init_empty (&s->label);
135               var_append_value_name (var[pidx], &s->val, &s->label);
136
137               hmap_insert (&bar->primaries, &s->node, hash);
138             }
139         }
140
141       bar->n_pcats = hmap_count (&bar->primaries);
142     }
143
144   if (n_vars > 1)
145     {
146       hmap_init (&bar->secondaries);
147       int idx = 0;
148       /* Iterate the categories, and create a hash table of secondary categories */
149       for (i = 0; i < n_cats; i++)
150         {
151           struct freq *src = cats[i];
152
153           struct category *foo;
154           int flag = 0;
155           size_t hash = value_hash (&src->values[sidx], var_get_width (var[sidx]), 0);
156           HMAP_FOR_EACH_WITH_HASH (foo, struct category, node, hash, &bar->secondaries)
157             {
158               if (value_equal (&foo->val, &src->values[sidx], var_get_width (var[sidx])))
159                 {
160                   flag = 1;
161                   break;
162                 }
163             }
164       
165           if (!flag) 
166             {
167               struct category *s = xzalloc (sizeof *s);
168               s->idx = idx++;
169               value_init (&s->val, var_get_width (var[sidx]));
170               value_copy (&s->val, &src->values[sidx], var_get_width (var[sidx]));
171               ds_init_empty (&s->label);
172               var_append_value_name (var[sidx], &s->val, &s->label);
173
174               hmap_insert (&bar->secondaries, &s->node, hash);
175               bar->ss = xrealloc (bar->ss, idx * sizeof *bar->ss);
176               bar->ss[idx - 1] = s;
177             }
178         }
179
180       int n_category = hmap_count (&bar->secondaries);
181
182       sort (bar->ss, n_category, sizeof *bar->ss,
183             compare_category_3way, bar);
184     }
185     
186
187   /* Deep copy.  Not necessary for cmd line, but essential for the GUI,
188      since an expose callback will access these structs which may not
189      exist.
190    */
191   bar->cats = xcalloc (n_cats, sizeof *bar->cats);
192
193   bar->widths[0] = var_get_width (bar->var[0]);
194   if (n_vars > 1)
195     bar->widths[1] = var_get_width (bar->var[1]);
196
197   {
198     struct hmap level2table;
199     hmap_init (&level2table);
200     int x = 0;
201   
202     for (i = 0; i < n_cats; i++)
203       {
204         struct freq *c = cats[i];
205
206         struct freq *foo;
207         int flag = 0;
208         size_t hash = hash_freq_2level_ptr (&c, bar);
209         HMAP_FOR_EACH_WITH_HASH (foo, struct freq, node, hash, &level2table)
210           {
211             if (0 == compare_freq_2level_ptr_3way (&foo, &c, bar))
212               {
213                 foo->count += c->count;
214                 
215                 if (foo->count > bar->largest)
216                   bar->largest = foo->count;
217                 
218                 flag = 1;
219                 break;
220               }
221           }
222         
223         if (!flag) 
224           {
225             struct freq *aggregated_freq = freq_clone (c, n_vars, bar->widths); 
226             hmap_insert (&level2table, &aggregated_freq->node, hash);
227             
228             if (c->count > bar->largest)
229               bar->largest = aggregated_freq->count;
230             
231             bar->cats[x++] = aggregated_freq;
232           }
233       }
234
235     bar->n_nzcats = hmap_count (&level2table);
236     hmap_destroy (&level2table);
237   }
238
239   sort (bar->cats, bar->n_nzcats, sizeof *bar->cats,
240         compare_freq_2level_ptr_3way, bar);
241
242   return &bar->chart_item;
243 }
244
245 static void
246 destroy_cat_map (struct hmap *m)
247 {
248   struct category *foo = NULL;
249   struct category *next = NULL;
250   HMAP_FOR_EACH_SAFE (foo, next, struct category, node, m)
251     {
252       ds_destroy (&foo->label);
253       free (foo);
254     }
255
256   hmap_destroy (m);
257 }
258
259 static void
260 barchart_destroy (struct chart_item *chart_item)
261 {
262   struct barchart *bar = to_barchart (chart_item);
263
264   int i;
265
266   destroy_cat_map (&bar->primaries);
267   if (bar->ss)
268     {
269       destroy_cat_map (&bar->secondaries);
270     }
271
272   for (i = 0; i < bar->n_nzcats; i++)
273     {
274       freq_destroy (bar->cats[i], bar->n_vars, bar->widths);
275     }
276   
277   free (bar->cats);
278   free (bar->ylabel);
279   free (bar->ss);
280   free (bar);
281 }
282
283 const struct chart_item_class barchart_class =
284   {
285     barchart_destroy
286   };