#include "libpspp/cast.h"
#include "libpspp/str.h"
+#include "libpspp/array.h"
#include "output/chart-item-provider.h"
#include "gl/xalloc.h"
+#include "data/variable.h"
+#include "language/stats/freq.h"
+
+
+static int
+compare_category_3way (const void *a_, const void *b_, const void *bc_)
+{
+ const struct category *const*a = a_;
+ const struct category *const*b = b_;
+ const struct barchart *bc = bc_;
+
+ return value_compare_3way (&(*a)->val, &(*b)->val, var_get_width (bc->var[1]));
+}
+
+
+static unsigned int
+hash_freq_2level_ptr (const void *a_, const void *bc_)
+{
+ const struct freq *const *ap = a_;
+ const struct barchart *bc = bc_;
+
+ size_t hash = value_hash (&(*ap)->values[0], bc->widths[0], 0);
+
+ if (bc->n_vars > 1)
+ hash = value_hash (&(*ap)->values[1], bc->widths[1], hash);
+
+ return hash;
+}
+
+
+static int
+compare_freq_2level_ptr_3way (const void *a_, const void *b_, const void *bc_)
+{
+ const struct freq *const *ap = a_;
+ const struct freq *const *bp = b_;
+ const struct barchart *bc = bc_;
+
+ const int level0 = value_compare_3way (&(*ap)->values[0], &(*bp)->values[0], bc->widths[0]);
+
+ if (level0 == 0 && bc->n_vars > 1)
+ return value_compare_3way (&(*ap)->values[1], &(*bp)->values[1], bc->widths[1]);
+
+ return level0;
+}
+
+
/* Creates and returns a chart that will render a barchart with
- the given TITLE and the N_BARS described in BARS. */
+ the given TITLE and the N_BARS described in CATS. */
struct chart_item *
-barchart_create (const char *title, const char *ylabel, const struct slice *bars, int n_bars)
+barchart_create (const struct variable **var, int n_vars,
+ const char *ylabel,
+ struct freq *const *cats, int n_cats)
{
struct barchart *bar;
int i;
- bar = xmalloc (sizeof *bar);
- chart_item_init (&bar->chart_item, &barchart_class, title);
- bar->bars = xnmalloc (n_bars, sizeof *bar->bars);
- bar->largest = 0;
+ const int pidx = 0;
+ const int sidx = 1;
+
+
+ int width = var_get_width (var[pidx]);
+
+ assert (n_vars >= 1);
+
+ bar = xzalloc (sizeof *bar);
+ bar->var = var;
+ bar->n_vars = n_vars;
+ bar->n_nzcats = n_cats;
+ chart_item_init (&bar->chart_item, &barchart_class, var_to_string (var[pidx]));
+
+ bar->largest = -1;
bar->ylabel = strdup (ylabel);
- for (i = 0; i < n_bars; i++)
+
{
- const struct slice *src = &bars[i];
- struct slice *dst = &bar->bars[i];
+ int idx = 0;
+ hmap_init (&bar->primaries);
+
+ /*
+ Iterate the categories and create a hash table of the primary categories.
+ We need to do this to find out how many there are and to cache the labels.
+ */
+ for (i = 0; i < n_cats; i++)
+ {
+ const struct freq *src = cats[i];
+ size_t hash = value_hash (&src->values[pidx], width, 0);
+
+ struct category *foo;
+ int flag = 0;
+ HMAP_FOR_EACH_WITH_HASH (foo, struct category, node, hash, &bar->primaries)
+ {
+ if (value_equal (&foo->val, &src->values[pidx], width))
+ {
+ flag = 1;
+ break;
+ }
+ }
- ds_init_string (&dst->label, &src->label);
- dst->magnitude = src->magnitude;
- if (dst->magnitude > bar->largest)
- bar->largest = dst->magnitude;
+ if (!flag)
+ {
+ struct category *s = xzalloc (sizeof *s);
+ s->idx = idx++;
+ value_init (&s->val, var_get_width (var[pidx]));
+ value_copy (&s->val, &src->values[pidx], var_get_width (var[pidx]));
+ ds_init_empty (&s->label);
+ var_append_value_name (var[pidx], &s->val, &s->label);
+
+ hmap_insert (&bar->primaries, &s->node, hash);
+ }
+ }
+
+ bar->n_pcats = hmap_count (&bar->primaries);
+ }
+
+ if (n_vars > 1)
+ {
+ hmap_init (&bar->secondaries);
+ int idx = 0;
+ /* Iterate the categories, and create a hash table of secondary categories */
+ for (i = 0; i < n_cats; i++)
+ {
+ struct freq *src = cats[i];
+
+ struct category *foo;
+ int flag = 0;
+ size_t hash = value_hash (&src->values[sidx], var_get_width (var[sidx]), 0);
+ HMAP_FOR_EACH_WITH_HASH (foo, struct category, node, hash, &bar->secondaries)
+ {
+ if (value_equal (&foo->val, &src->values[sidx], var_get_width (var[sidx])))
+ {
+ flag = 1;
+ break;
+ }
+ }
+
+ if (!flag)
+ {
+ struct category *s = xzalloc (sizeof *s);
+ s->idx = idx++;
+ value_init (&s->val, var_get_width (var[sidx]));
+ value_copy (&s->val, &src->values[sidx], var_get_width (var[sidx]));
+ ds_init_empty (&s->label);
+ var_append_value_name (var[sidx], &s->val, &s->label);
+
+ hmap_insert (&bar->secondaries, &s->node, hash);
+ bar->ss = xrealloc (bar->ss, idx * sizeof *bar->ss);
+ bar->ss[idx - 1] = s;
+ }
+ }
+
+ int n_category = hmap_count (&bar->secondaries);
+
+ sort (bar->ss, n_category, sizeof *bar->ss,
+ compare_category_3way, bar);
}
- bar->n_bars = n_bars;
+
+
+ /* Deep copy. Not necessary for cmd line, but essential for the GUI,
+ since an expose callback will access these structs which may not
+ exist.
+ */
+ bar->cats = xcalloc (n_cats, sizeof *bar->cats);
+
+ bar->widths[0] = var_get_width (bar->var[0]);
+ if (n_vars > 1)
+ bar->widths[1] = var_get_width (bar->var[1]);
+
+ {
+ struct hmap level2table;
+ hmap_init (&level2table);
+ int x = 0;
+
+ for (i = 0; i < n_cats; i++)
+ {
+ struct freq *c = cats[i];
+
+ struct freq *foo;
+ int flag = 0;
+ size_t hash = hash_freq_2level_ptr (&c, bar);
+ HMAP_FOR_EACH_WITH_HASH (foo, struct freq, node, hash, &level2table)
+ {
+ if (0 == compare_freq_2level_ptr_3way (&foo, &c, bar))
+ {
+ foo->count += c->count;
+
+ if (foo->count > bar->largest)
+ bar->largest = foo->count;
+
+ flag = 1;
+ break;
+ }
+ }
+
+ if (!flag)
+ {
+ struct freq *aggregated_freq = freq_clone (c, n_vars, bar->widths);
+ hmap_insert (&level2table, &aggregated_freq->node, hash);
+
+ if (c->count > bar->largest)
+ bar->largest = aggregated_freq->count;
+
+ bar->cats[x++] = aggregated_freq;
+ }
+ }
+
+ bar->n_nzcats = hmap_count (&level2table);
+ hmap_destroy (&level2table);
+ }
+
+ sort (bar->cats, bar->n_nzcats, sizeof *bar->cats,
+ compare_freq_2level_ptr_3way, bar);
+
return &bar->chart_item;
}
+static void
+destroy_cat_map (struct hmap *m)
+{
+ struct category *foo = NULL;
+ struct category *next = NULL;
+ HMAP_FOR_EACH_SAFE (foo, next, struct category, node, m)
+ {
+ ds_destroy (&foo->label);
+ free (foo);
+ }
+
+ hmap_destroy (m);
+}
+
static void
barchart_destroy (struct chart_item *chart_item)
{
struct barchart *bar = to_barchart (chart_item);
+
int i;
- for (i = 0; i < bar->n_bars; i++)
+ destroy_cat_map (&bar->primaries);
+ if (bar->ss)
+ {
+ destroy_cat_map (&bar->secondaries);
+ }
+
+ for (i = 0; i < bar->n_nzcats; i++)
{
- struct slice *slice = &bar->bars[i];
- ds_destroy (&slice->label);
+ freq_destroy (bar->cats[i], bar->n_vars, bar->widths);
}
+
+ free (bar->cats);
free (bar->ylabel);
- free (bar->bars);
+ free (bar->ss);
free (bar);
}