limit the number of variables in barchart from crosstabs #58968
[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 "data/settings.h"
32 #include "language/stats/freq.h"
33
34
35 static int
36 compare_category_3way (const void *a_, const void *b_, const void *bc_)
37 {
38   const struct category *const*a = a_;
39   const struct category *const*b = b_;
40   const struct barchart *bc = bc_;
41
42   return value_compare_3way (&(*a)->val, &(*b)->val, var_get_width (bc->var[1]));
43 }
44
45
46 static unsigned int
47 hash_freq_2level_ptr (const void *a_, const void *bc_)
48 {
49   const struct freq *const *ap = a_;
50   const struct barchart *bc = bc_;
51
52   size_t hash = value_hash (&(*ap)->values[0], bc->widths[0], 0);
53
54   if (bc->n_vars > 1)
55     hash = value_hash (&(*ap)->values[1], bc->widths[1], hash);
56
57   return hash;
58 }
59
60
61 static int
62 compare_freq_2level_ptr_3way (const void *a_, const void *b_, const void *bc_)
63 {
64   const struct freq *const *ap = a_;
65   const struct freq *const *bp = b_;
66   const struct barchart *bc = bc_;
67
68   const int level0 = value_compare_3way (&(*ap)->values[0], &(*bp)->values[0], bc->widths[0]);
69
70   if (level0 == 0 && bc->n_vars > 1)
71     return value_compare_3way (&(*ap)->values[1], &(*bp)->values[1], bc->widths[1]);
72
73   return level0;
74 }
75
76 /* Print out a textual representation of a barchart.
77    This is intended only for testing, and not as a means
78    of visualising the data.
79 */
80 static void
81 barchart_dump (const struct barchart *bc, FILE *fp)
82 {
83   fprintf (fp, "Graphic: Barchart\n");
84   fprintf (fp, "Percentage: %d\n", bc->percent);
85   fprintf (fp, "Total Categories: %d\n", bc->n_nzcats);
86   fprintf (fp, "Primary Categories: %d\n", bc->n_pcats);
87   fprintf (fp, "Largest Category: %g\n", bc->largest);
88   fprintf (fp, "Total Count: %g\n", bc->total_count);
89
90   fprintf (fp, "Y Label: \"%s\"\n", bc->ylabel);
91
92   fprintf (fp, "Categorical Variables:\n");
93   for (int i = 0; i < bc->n_vars; ++i)
94     {
95       fprintf (fp, "  Var: \"%s\"\n", var_get_name (bc->var[i]));
96     }
97
98   fprintf (fp, "Categories:\n");
99   struct category *cat;
100   HMAP_FOR_EACH (cat, struct category, node, &bc->primaries)
101     {
102       fprintf (fp, "  %d \"%s\"\n", cat->idx, ds_cstr(&cat->label));
103     }
104
105   if (bc->ss)
106     {
107       fprintf (fp, "Sub-categories:\n");
108       for (int i = 0; i < bc->n_nzcats / bc->n_pcats; ++i)
109         {
110           const struct category *cat = bc->ss[i];
111           fprintf (fp, "  %d \"%s\"\n", cat->idx, ds_cstr(&cat->label));
112         }
113     }
114
115   fprintf (fp, "All Categories:\n");
116   for (int i = 0; i < bc->n_nzcats; ++i)
117     {
118       const struct freq *frq = bc->cats[i];
119       fprintf (fp, "Count: %g; ", frq->count);
120
121       struct string s = DS_EMPTY_INITIALIZER;
122       var_append_value_name (bc->var[0], &frq->values[0], &s);
123
124       fprintf (fp, "Cat: \"%s\"", ds_cstr (&s));
125       ds_clear (&s);
126
127       if (bc->ss)
128         {
129           var_append_value_name (bc->var[1], &frq->values[1], &s);
130           fprintf (fp, ", \"%s\"", ds_cstr (&s));
131         }
132       ds_destroy (&s);
133       fputc ('\n', fp);
134     }
135
136   fputc ('\n', fp);
137 }
138
139
140 /* Creates and returns a chart that will render a barchart with
141    the given TITLE and the N_CATS described in CATS.
142
143    VAR is an array containing the categorical variables, and N_VAR
144    the number of them. N_VAR must be exactly 1 or 2.
145
146    CATS are the counts of the values of those variables. N_CATS is the
147    number of distinct values.
148 */
149 struct chart_item *
150 barchart_create (const struct variable **var, int n_vars,
151                  const char *ylabel, bool percent,
152                  struct freq *const *cats, int n_cats)
153 {
154   struct barchart *bar;
155   int i;
156
157   const int pidx = 0;
158   const int sidx = 1;
159
160
161   int width = var_get_width (var[pidx]);
162
163   assert (n_vars >= 1 && n_vars <= 2);
164
165   bar = xzalloc (sizeof *bar);
166   bar->percent = percent;
167   bar->var = var;
168   bar->n_vars = n_vars;
169   bar->n_nzcats = n_cats;
170   chart_item_init (&bar->chart_item, &barchart_class, var_to_string (var[pidx]));
171
172   bar->largest = -1;
173   bar->ylabel = strdup (ylabel);
174
175     {
176       int idx = 0;
177       hmap_init (&bar->primaries);
178
179       /*
180          Iterate the categories and create a hash table of the primary categories.
181          We need to do this to find out how many there are and to cache the labels.
182       */
183       for (i = 0; i < n_cats; i++)
184         {
185           const struct freq *src = cats[i];
186           size_t hash = value_hash (&src->values[pidx], width, 0);
187
188           struct category *foo;
189           int flag = 0;
190           HMAP_FOR_EACH_WITH_HASH (foo, struct category, node, hash, &bar->primaries)
191             {
192               if (value_equal (&foo->val, &src->values[pidx], width))
193                 {
194                   flag = 1;
195                   break;
196                 }
197             }
198
199           if (!flag)
200             {
201               struct category *s = xzalloc (sizeof *s);
202               s->idx = idx++;
203               s->width = var_get_width (var[pidx]);
204               value_init (&s->val, s->width);
205               value_copy (&s->val, &src->values[pidx], s->width);
206               ds_init_empty (&s->label);
207               var_append_value_name (var[pidx], &s->val, &s->label);
208
209               hmap_insert (&bar->primaries, &s->node, hash);
210             }
211         }
212
213       bar->n_pcats = hmap_count (&bar->primaries);
214     }
215
216   if (n_vars > 1)
217     {
218       hmap_init (&bar->secondaries);
219       int idx = 0;
220       /* Iterate the categories, and create a hash table of secondary categories */
221       for (i = 0; i < n_cats; i++)
222         {
223           struct freq *src = cats[i];
224
225           struct category *foo;
226           int flag = 0;
227           size_t hash = value_hash (&src->values[sidx], var_get_width (var[sidx]), 0);
228           HMAP_FOR_EACH_WITH_HASH (foo, struct category, node, hash, &bar->secondaries)
229             {
230               if (value_equal (&foo->val, &src->values[sidx], var_get_width (var[sidx])))
231                 {
232                   flag = 1;
233                   break;
234                 }
235             }
236
237           if (!flag)
238             {
239               struct category *s = xzalloc (sizeof *s);
240               s->idx = idx++;
241               s->width = var_get_width (var[sidx]);
242               value_init (&s->val, s->width);
243               value_copy (&s->val, &src->values[sidx], var_get_width (var[sidx]));
244               ds_init_empty (&s->label);
245               var_append_value_name (var[sidx], &s->val, &s->label);
246
247               hmap_insert (&bar->secondaries, &s->node, hash);
248               bar->ss = xrealloc (bar->ss, idx * sizeof *bar->ss);
249               bar->ss[idx - 1] = s;
250             }
251         }
252
253       int n_category = hmap_count (&bar->secondaries);
254
255       sort (bar->ss, n_category, sizeof *bar->ss,
256             compare_category_3way, bar);
257     }
258
259
260   /* Deep copy.  Not necessary for cmd line, but essential for the GUI,
261      since an expose callback will access these structs which may not
262      exist.
263    */
264   bar->cats = xcalloc (n_cats, sizeof *bar->cats);
265
266   bar->widths[0] = var_get_width (bar->var[0]);
267   if (n_vars > 1)
268     bar->widths[1] = var_get_width (bar->var[1]);
269
270   {
271     struct hmap level2table;
272     hmap_init (&level2table);
273     int x = 0;
274
275     for (i = 0; i < n_cats; i++)
276       {
277         struct freq *c = cats[i];
278
279         struct freq *foo;
280         bool flag = false;
281         size_t hash = hash_freq_2level_ptr (&c, bar);
282         HMAP_FOR_EACH_WITH_HASH (foo, struct freq, node, hash, &level2table)
283           {
284             if (0 == compare_freq_2level_ptr_3way (&foo, &c, bar))
285               {
286                 foo->count += c->count;
287                 bar->total_count += c->count;
288
289                 if (foo->count > bar->largest)
290                   bar->largest = foo->count;
291
292                 flag = true;
293                 break;
294               }
295           }
296
297         if (!flag)
298           {
299             struct freq *aggregated_freq = freq_clone (c, n_vars, bar->widths);
300             hmap_insert (&level2table, &aggregated_freq->node, hash);
301
302             if (c->count > bar->largest)
303               bar->largest = aggregated_freq->count;
304
305             bar->total_count += c->count;
306             bar->cats[x++] = aggregated_freq;
307           }
308       }
309
310     bar->n_nzcats = hmap_count (&level2table);
311     hmap_destroy (&level2table);
312   }
313
314   sort (bar->cats, bar->n_nzcats, sizeof *bar->cats,
315         compare_freq_2level_ptr_3way, bar);
316
317   if (settings_get_testing_mode ())
318     barchart_dump (bar, stdout);
319
320   return &bar->chart_item;
321 }
322
323 static void
324 destroy_cat_map (struct hmap *m)
325 {
326   struct category *foo = NULL;
327   struct category *next = NULL;
328   HMAP_FOR_EACH_SAFE (foo, next, struct category, node, m)
329     {
330       value_destroy (&foo->val, foo->width);
331
332       ds_destroy (&foo->label);
333       free (foo);
334     }
335
336   hmap_destroy (m);
337 }
338
339 static void
340 barchart_destroy (struct chart_item *chart_item)
341 {
342   struct barchart *bar = to_barchart (chart_item);
343
344   int i;
345
346   destroy_cat_map (&bar->primaries);
347   if (bar->ss)
348     {
349       destroy_cat_map (&bar->secondaries);
350     }
351
352   for (i = 0; i < bar->n_nzcats; i++)
353     {
354       freq_destroy (bar->cats[i], bar->n_vars, bar->widths);
355     }
356
357   free (bar->cats);
358   free (bar->ylabel);
359   free (bar->ss);
360   free (bar);
361 }
362
363 const struct chart_item_class barchart_class =
364   {
365     barchart_destroy
366   };