Recent spread-sheet-widget to version 0.2
[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_CATS described in CATS.
79
80    VAR is an array containing the categorical variables, and N_VAR
81    the number of them. N_VAR must be exactly 1 or 2.
82
83    CATS are the counts of the values of those variables. N_CATS is the
84    number of distinct values.
85 */
86 struct chart_item *
87 barchart_create (const struct variable **var, int n_vars,
88                  const char *ylabel, bool percent,
89                  struct freq *const *cats, int n_cats)
90 {
91   struct barchart *bar;
92   int i;
93
94   const int pidx = 0;
95   const int sidx = 1;
96
97
98   int width = var_get_width (var[pidx]);
99
100   assert (n_vars >= 1);
101
102   bar = xzalloc (sizeof *bar);
103   bar->percent = percent;
104   bar->var = var;
105   bar->n_vars = n_vars;
106   bar->n_nzcats = n_cats;
107   chart_item_init (&bar->chart_item, &barchart_class, var_to_string (var[pidx]));
108
109   bar->largest = -1;
110   bar->ylabel = strdup (ylabel);
111
112     {
113       int idx = 0;
114       hmap_init (&bar->primaries);
115
116       /*
117          Iterate the categories and create a hash table of the primary categories.
118          We need to do this to find out how many there are and to cache the labels.
119       */
120       for (i = 0; i < n_cats; i++)
121         {
122           const struct freq *src = cats[i];
123           size_t hash = value_hash (&src->values[pidx], width, 0);
124
125           struct category *foo;
126           int flag = 0;
127           HMAP_FOR_EACH_WITH_HASH (foo, struct category, node, hash, &bar->primaries)
128             {
129               if (value_equal (&foo->val, &src->values[pidx], width))
130                 {
131                   flag = 1;
132                   break;
133                 }
134             }
135
136           if (!flag)
137             {
138               struct category *s = xzalloc (sizeof *s);
139               s->idx = idx++;
140               s->width = var_get_width (var[pidx]);
141               value_init (&s->val, s->width);
142               value_copy (&s->val, &src->values[pidx], s->width);
143               ds_init_empty (&s->label);
144               var_append_value_name (var[pidx], &s->val, &s->label);
145
146               hmap_insert (&bar->primaries, &s->node, hash);
147             }
148         }
149
150       bar->n_pcats = hmap_count (&bar->primaries);
151     }
152
153   if (n_vars > 1)
154     {
155       hmap_init (&bar->secondaries);
156       int idx = 0;
157       /* Iterate the categories, and create a hash table of secondary categories */
158       for (i = 0; i < n_cats; i++)
159         {
160           struct freq *src = cats[i];
161
162           struct category *foo;
163           int flag = 0;
164           size_t hash = value_hash (&src->values[sidx], var_get_width (var[sidx]), 0);
165           HMAP_FOR_EACH_WITH_HASH (foo, struct category, node, hash, &bar->secondaries)
166             {
167               if (value_equal (&foo->val, &src->values[sidx], var_get_width (var[sidx])))
168                 {
169                   flag = 1;
170                   break;
171                 }
172             }
173
174           if (!flag)
175             {
176               struct category *s = xzalloc (sizeof *s);
177               s->idx = idx++;
178               s->width = var_get_width (var[sidx]);
179               value_init (&s->val, s->width);
180               value_copy (&s->val, &src->values[sidx], var_get_width (var[sidx]));
181               ds_init_empty (&s->label);
182               var_append_value_name (var[sidx], &s->val, &s->label);
183
184               hmap_insert (&bar->secondaries, &s->node, hash);
185               bar->ss = xrealloc (bar->ss, idx * sizeof *bar->ss);
186               bar->ss[idx - 1] = s;
187             }
188         }
189
190       int n_category = hmap_count (&bar->secondaries);
191
192       sort (bar->ss, n_category, sizeof *bar->ss,
193             compare_category_3way, bar);
194     }
195
196
197   /* Deep copy.  Not necessary for cmd line, but essential for the GUI,
198      since an expose callback will access these structs which may not
199      exist.
200    */
201   bar->cats = xcalloc (n_cats, sizeof *bar->cats);
202
203   bar->widths[0] = var_get_width (bar->var[0]);
204   if (n_vars > 1)
205     bar->widths[1] = var_get_width (bar->var[1]);
206
207   {
208     struct hmap level2table;
209     hmap_init (&level2table);
210     int x = 0;
211
212     for (i = 0; i < n_cats; i++)
213       {
214         struct freq *c = cats[i];
215
216         struct freq *foo;
217         bool flag = false;
218         size_t hash = hash_freq_2level_ptr (&c, bar);
219         HMAP_FOR_EACH_WITH_HASH (foo, struct freq, node, hash, &level2table)
220           {
221             if (0 == compare_freq_2level_ptr_3way (&foo, &c, bar))
222               {
223                 foo->count += c->count;
224                 bar->total_count += c->count;
225
226                 if (foo->count > bar->largest)
227                   bar->largest = foo->count;
228
229                 flag = true;
230                 break;
231               }
232           }
233
234         if (!flag)
235           {
236             struct freq *aggregated_freq = freq_clone (c, n_vars, bar->widths);
237             hmap_insert (&level2table, &aggregated_freq->node, hash);
238
239             if (c->count > bar->largest)
240               bar->largest = aggregated_freq->count;
241
242             bar->total_count += c->count;
243             bar->cats[x++] = aggregated_freq;
244           }
245       }
246
247     bar->n_nzcats = hmap_count (&level2table);
248     hmap_destroy (&level2table);
249   }
250
251   sort (bar->cats, bar->n_nzcats, sizeof *bar->cats,
252         compare_freq_2level_ptr_3way, bar);
253
254   return &bar->chart_item;
255 }
256
257 static void
258 destroy_cat_map (struct hmap *m)
259 {
260   struct category *foo = NULL;
261   struct category *next = NULL;
262   HMAP_FOR_EACH_SAFE (foo, next, struct category, node, m)
263     {
264       value_destroy (&foo->val, foo->width);
265
266       ds_destroy (&foo->label);
267       free (foo);
268     }
269
270   hmap_destroy (m);
271 }
272
273 static void
274 barchart_destroy (struct chart_item *chart_item)
275 {
276   struct barchart *bar = to_barchart (chart_item);
277
278   int i;
279
280   destroy_cat_map (&bar->primaries);
281   if (bar->ss)
282     {
283       destroy_cat_map (&bar->secondaries);
284     }
285
286   for (i = 0; i < bar->n_nzcats; i++)
287     {
288       freq_destroy (bar->cats[i], bar->n_vars, bar->widths);
289     }
290
291   free (bar->cats);
292   free (bar->ylabel);
293   free (bar->ss);
294   free (bar);
295 }
296
297 const struct chart_item_class barchart_class =
298   {
299     barchart_destroy
300   };