Added the /BARCHART option to CROSSTABS
authorJohn Darrington <john@darrington.wattle.id.au>
Sat, 24 Jan 2015 15:42:07 +0000 (16:42 +0100)
committerJohn Darrington <john@darrington.wattle.id.au>
Sat, 7 Feb 2015 16:16:18 +0000 (17:16 +0100)
20 files changed:
NEWS
doc/statistics.texi
src/language/stats/binomial.c
src/language/stats/chisquare.c
src/language/stats/crosstabs.q
src/language/stats/freq.c
src/language/stats/freq.h
src/language/stats/frequencies.c
src/math/chart-geometry.h
src/output/cairo-chart.h
src/output/charts/barchart-cairo.c
src/output/charts/barchart.c
src/output/charts/barchart.h
src/output/charts/piechart.c
src/output/charts/piechart.h
tests/language/stats/crosstabs.at
tests/math/chart-geometry-test.c
tests/math/chart-get-scale-test.c
tests/math/decimal-test.c
tests/output/charts.at

diff --git a/NEWS b/NEWS
index a52e25f7b4efd270550184c4effe178c28878ae0..05810946a3fe1044523393a9ec9d0f85f09329d4 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -6,7 +6,7 @@ Please send PSPP bug reports to bug-gnu-pspp@gnu.org.
  
 Changes since 0.8.4:
 
- * The FREQUENCIES command can now generate barcharts.
+ * The FREQUENCIES and CROSSTABS commands can now generate barcharts.
 
  * The FACTOR command can now perform PROMAX rotations.
 
index f0160f40a8a2888b626c4f61f00ca3c15758a216..7977e3387cc2e0713bbc890d2b1c19000ba7ba77 100644 (file)
@@ -209,12 +209,24 @@ histogram.
 The @subcmd{PIECHART} subcommand adds a pie chart for each variable to the data.  Each
 slice represents one value, with the size of the slice proportional to
 the value's frequency.  By default, all non-missing values are given
-slices.  The @subcmd{MINIMUM} and @subcmd{MAXIMUM} keywords can be used to limit the
-displayed slices to a given range of values.  The @subcmd{MISSING} keyword adds
-slices for missing values.
-
-The @subcmd{FREQ} and @subcmd{PERCENT} options on @subcmd{HISTOGRAM} and @subcmd{PIECHART} are accepted
-but not currently honoured.
+slices.  
+The @subcmd{MINIMUM} and @subcmd{MAXIMUM} keywords can be used to limit the
+displayed slices to a given range of values.  
+The keyword @subcmd{NOMISSING} causes missing values to be omitted from the
+piechart.  This is the default.
+If instead, @subcmd{MISSING} is specified, then a single slice
+will be included representing all system missing and user-missing cases.
+
+@cindex bar chart
+The @subcmd{BARCHART} subcommand produces a bar chart for each variable.
+The @subcmd{MINIMUM} and @subcmd{MAXIMUM} keywords can be used to omit
+categories whose counts which lie outside the specified limits.
+The @subcmd{FREQ} option (default) causes the ordinate to display the frequency
+of each category, whereas the @subcmd{PERCENT} option will display relative
+percentages.
+
+The @subcmd{FREQ} and @subcmd{PERCENT} options on @subcmd{HISTOGRAM} and 
+@subcmd{PIECHART} are accepted but not currently honoured.
 
 @node EXAMINE
 @section EXAMINE
@@ -514,6 +526,7 @@ CROSSTABS
                 ASRESIDUAL,ALL,NONE@}
         /STATISTICS=@{CHISQ,PHI,CC,LAMBDA,UC,BTAU,CTAU,RISK,GAMMA,D,
                      KAPPA,ETA,CORR,ALL,NONE@}
+        /BARCHART
         
 (Integer mode.)
         /VARIABLES=@var{var_list} (@var{low},@var{high})@dots{}
@@ -656,8 +669,15 @@ some statistics are calculated only in integer mode.
 @samp{/STATISTICS} without any settings selects CHISQ.  If the
 @subcmd{STATISTICS} subcommand is not given, no statistics are calculated.
 
+@cindex bar chart
+The @samp{/BARCHART} subcommand produces a clustered bar chart for the first two
+variables on each table.
+If a table has more than two variables, the counts for the third and subsequent levels 
+will be aggregated and the chart will be produces as if there were only two variables.  
+
+
 @strong{Please note:} Currently the implementation of @cmd{CROSSTABS} has the
-following bugs:
+following limitations:
 
 @itemize @bullet
 @item
index 697f3254ab1f14354986bcc50a5b591b4f65cf3c..b8ed5da9acecbf4e732c3da620ef7cc8bab4b582 100644 (file)
@@ -112,26 +112,26 @@ do_binomial (const struct dictionary *dict,
 
          if (bst->cutpoint != SYSMIS)
            {
-             if ( cat1[v].value.f >= value )
+             if ( cat1[v].values[0].f >= value )
                  cat1[v].count  += w;
              else
                  cat2[v].count += w;
            }
          else
            {
-             if ( SYSMIS == cat1[v].value.f )
+             if ( SYSMIS == cat1[v].values[0].f )
                {
-                 cat1[v].value.f = value;
+                 cat1[v].values[0].f = value;
                  cat1[v].count = w;
                }
-             else if ( cat1[v].value.f == value )
+             else if ( cat1[v].values[0].f == value )
                cat1[v].count += w;
-             else if ( SYSMIS == cat2[v].value.f )
+             else if ( SYSMIS == cat2[v].values[0].f )
                {
-                 cat2[v].value.f = value;
+                 cat2[v].values[0].f = value;
                  cat2[v].count = w;
                }
-             else if ( cat2[v].value.f == value )
+             else if ( cat2[v].values[0].f == value )
                cat2[v].count += w;
              else if ( bst->category1 == SYSMIS)
                msg (ME, _("Variable %s is not dichotomous"), var_get_name (var));
@@ -172,7 +172,7 @@ binomial_execute (const struct dataset *ds,
       cat[i] = xnmalloc (ost->n_vars, sizeof *cat[i]);
       for (v = 0; v < ost->n_vars; v++)
         {
-          cat[i][v].value.f = value;
+          cat[i][v].values[0].f = value;
           cat[i][v].count = 0;
         }
     }
@@ -209,8 +209,8 @@ binomial_execute (const struct dataset *ds,
            }
           else
             {
-              var_append_value_name (var, &cat[0][v].value, &catstr[0]);
-              var_append_value_name (var, &cat[1][v].value, &catstr[1]);
+              var_append_value_name (var, cat[0][v].values, &catstr[0]);
+              var_append_value_name (var, cat[1][v].values, &catstr[1]);
             }
 
           tab_hline (table, TAL_1, 0, tab_nc (table) -1, 1 + v * 3);
index 617bddba8f21ced7a5d25bda4d545065c5f8b7dd..e2ed8fef64808c003dc8bdf6073b1eaa11f2eb80 100644 (file)
@@ -331,7 +331,7 @@ chisquare_execute (const struct dataset *ds,
            {
              struct string str;
              double exp;
-             const union value *observed_value = &ff[i]->value;
+             const union value *observed_value = &ff[i]->values[0];
 
              ds_init_empty (&str);
              var_append_value_name (var, observed_value, &str);
@@ -407,7 +407,7 @@ chisquare_execute (const struct dataset *ds,
              struct string str;
              double exp;
 
-             const union value *observed_value = &ff[i]->value;
+             const union value *observed_value = &ff[i]->values[0];
 
              ds_init_empty (&str);
              var_append_value_name (ost->vars[v], observed_value, &str);
index aa7e2457ee24ae9d41d40bd989da985b2c822fe3..6e6cb20d9750b2c517c1d886a107f639db394c4e 100644 (file)
@@ -41,6 +41,7 @@
 #include "data/value-labels.h"
 #include "data/variable.h"
 #include "language/command.h"
+#include "language/stats/freq.h"
 #include "language/dictionary/split-file.h"
 #include "language/lexer/lexer.h"
 #include "language/lexer/variable-parser.h"
@@ -55,6 +56,8 @@
 #include "libpspp/pool.h"
 #include "libpspp/str.h"
 #include "output/tab.h"
+#include "output/chart-item.h"
+#include "output/charts/barchart.h"
 
 #include "gl/minmax.h"
 #include "gl/xalloc.h"
@@ -77,6 +80,7 @@
             tabl:!tables/notables,
             box:!box/nobox,
             pivot:!pivot/nopivot;
+     +barchart=;
      +cells[cl_]=count,expected,row,column,total,residual,sresidual,
                 asresidual,all,none;
      +statistics[st_]=chisq,phi,cc,lambda,uc,none,btau,ctau,risk,gamma,d,
 /* Number of directional statistics. */
 #define N_DIRECTIONAL 13
 
-/* A single table entry for general mode. */
-struct table_entry
-  {
-    struct hmap_node node;      /* Entry in hash table. */
-    double freq;                /* Frequency count. */
-    union value values[1];     /* Values. */
-  };
-
-static size_t
-table_entry_size (size_t n_values)
-{
-  return (offsetof (struct table_entry, values)
-          + n_values * sizeof (union value));
-}
 
 /* Indexes into the 'vars' member of struct pivot_table and
    struct crosstab member. */
@@ -136,7 +126,7 @@ struct pivot_table
 
     /* Data. */
     struct hmap data;
-    struct table_entry **entries;
+    struct freq **entries;
     size_t n_entries;
 
     /* Column values, number of columns. */
@@ -174,6 +164,7 @@ struct crosstabs_proc
     enum { INTEGER, GENERAL } mode;
     enum mv_class exclude;
     bool pivot;
+    bool barchart;
     bool bad_warn;
     struct fmt_spec weight_format;
 
@@ -241,7 +232,7 @@ cmd_crosstabs (struct lexer *lexer, struct dataset *ds)
     }
 
   proc.mode = proc.n_variables ? INTEGER : GENERAL;
-
+  proc.barchart = cmd.sbc_barchart > 0;
 
   proc.descending = cmd.val == CRS_DVALUE;
 
@@ -590,7 +581,7 @@ static void
 tabulate_integer_case (struct pivot_table *pt, const struct ccase *c,
                        double weight)
 {
-  struct table_entry *te;
+  struct freq *te;
   size_t hash;
   int j;
 
@@ -601,14 +592,14 @@ tabulate_integer_case (struct pivot_table *pt, const struct ccase *c,
       hash = hash_int (case_num (c, pt->vars[j]), hash);
     }
 
-  HMAP_FOR_EACH_WITH_HASH (te, struct table_entry, node, hash, &pt->data)
+  HMAP_FOR_EACH_WITH_HASH (te, struct freq, node, hash, &pt->data)
     {
       for (j = 0; j < pt->n_vars; j++)
         if ((int) case_num (c, pt->vars[j]) != (int) te->values[j].f)
           goto no_match;
 
       /* Found an existing entry. */
-      te->freq += weight;
+      te->count += weight;
       return;
 
     no_match: ;
@@ -616,7 +607,7 @@ tabulate_integer_case (struct pivot_table *pt, const struct ccase *c,
 
   /* No existing entry.  Create a new one. */
   te = xmalloc (table_entry_size (pt->n_vars));
-  te->freq = weight;
+  te->count = weight;
   for (j = 0; j < pt->n_vars; j++)
     te->values[j].f = (int) case_num (c, pt->vars[j]);
   hmap_insert (&pt->data, &te->node, hash);
@@ -626,7 +617,7 @@ static void
 tabulate_general_case (struct pivot_table *pt, const struct ccase *c,
                        double weight)
 {
-  struct table_entry *te;
+  struct freq *te;
   size_t hash;
   int j;
 
@@ -637,7 +628,7 @@ tabulate_general_case (struct pivot_table *pt, const struct ccase *c,
       hash = value_hash (case_data (c, var), var_get_width (var), hash);
     }
 
-  HMAP_FOR_EACH_WITH_HASH (te, struct table_entry, node, hash, &pt->data)
+  HMAP_FOR_EACH_WITH_HASH (te, struct freq, node, hash, &pt->data)
     {
       for (j = 0; j < pt->n_vars; j++)
         {
@@ -648,7 +639,7 @@ tabulate_general_case (struct pivot_table *pt, const struct ccase *c,
         }
 
       /* Found an existing entry. */
-      te->freq += weight;
+      te->count += weight;
       return;
 
     no_match: ;
@@ -656,7 +647,7 @@ tabulate_general_case (struct pivot_table *pt, const struct ccase *c,
 
   /* No existing entry.  Create a new one. */
   te = xmalloc (table_entry_size (pt->n_vars));
-  te->freq = weight;
+  te->count = weight;
   for (j = 0; j < pt->n_vars; j++)
     {
       const struct variable *var = pt->vars[j];
@@ -667,8 +658,8 @@ tabulate_general_case (struct pivot_table *pt, const struct ccase *c,
 \f
 /* Post-data reading calculations. */
 
-static int compare_table_entry_vars_3way (const struct table_entry *a,
-                                          const struct table_entry *b,
+static int compare_table_entry_vars_3way (const struct freq *a,
+                                          const struct freq *b,
                                           const struct pivot_table *pt,
                                           int idx0, int idx1);
 static int compare_table_entry_3way (const void *ap_, const void *bp_,
@@ -694,19 +685,20 @@ postcalc (struct crosstabs_proc *proc)
   /* Convert hash tables into sorted arrays of entries. */
   for (pt = &proc->pivots[0]; pt < &proc->pivots[proc->n_pivots]; pt++)
     {
-      struct table_entry *e;
+      struct freq *e;
       size_t i;
 
       pt->n_entries = hmap_count (&pt->data);
       pt->entries = xnmalloc (pt->n_entries, sizeof *pt->entries);
       i = 0;
-      HMAP_FOR_EACH (e, struct table_entry, node, &pt->data)
+      HMAP_FOR_EACH (e, struct freq, node, &pt->data)
         pt->entries[i++] = e;
       hmap_destroy (&pt->data);
 
       sort (pt->entries, pt->n_entries, sizeof *pt->entries,
             proc->descending ? compare_table_entry_3way_inv : compare_table_entry_3way,
            pt);
+
     }
 
   make_summary_table (proc);
@@ -726,6 +718,9 @@ postcalc (struct crosstabs_proc *proc)
               output_pivot_table (proc, &subset);
             }
         }
+      if (proc->barchart)
+       chart_item_submit 
+         (barchart_create (pt->vars, pt->n_vars, _("Count"), pt->entries, pt->n_entries));
     }
 
   /* Free output and prepare for next split file. */
@@ -780,8 +775,8 @@ make_pivot_table_subset (struct pivot_table *pt, size_t row0, size_t row1,
 }
 
 static int
-compare_table_entry_var_3way (const struct table_entry *a,
-                              const struct table_entry *b,
+compare_table_entry_var_3way (const struct freq *a,
+                              const struct freq *b,
                               const struct pivot_table *pt,
                               int idx)
 {
@@ -790,8 +785,8 @@ compare_table_entry_var_3way (const struct table_entry *a,
 }
 
 static int
-compare_table_entry_vars_3way (const struct table_entry *a,
-                               const struct table_entry *b,
+compare_table_entry_vars_3way (const struct freq *a,
+                               const struct freq *b,
                                const struct pivot_table *pt,
                                int idx0, int idx1)
 {
@@ -806,15 +801,15 @@ compare_table_entry_vars_3way (const struct table_entry *a,
   return 0;
 }
 
-/* Compare the struct table_entry at *AP to the one at *BP and
+/* Compare the struct freq at *AP to the one at *BP and
    return a strcmp()-type result. */
 static int
 compare_table_entry_3way (const void *ap_, const void *bp_, const void *pt_)
 {
-  const struct table_entry *const *ap = ap_;
-  const struct table_entry *const *bp = bp_;
-  const struct table_entry *a = *ap;
-  const struct table_entry *b = *bp;
+  const struct freq *const *ap = ap_;
+  const struct freq *const *bp = bp_;
+  const struct freq *a = *ap;
+  const struct freq *b = *bp;
   const struct pivot_table *pt = pt_;
   int cmp;
 
@@ -843,8 +838,8 @@ find_first_difference (const struct pivot_table *pt, size_t row)
     return pt->n_vars - 1;
   else
     {
-      const struct table_entry *a = pt->entries[row];
-      const struct table_entry *b = pt->entries[row - 1];
+      const struct freq *a = pt->entries[row];
+      const struct freq *b = pt->entries[row - 1];
       int col;
 
       for (col = pt->n_vars - 1; col >= 0; col--)
@@ -902,7 +897,7 @@ make_summary_table (struct crosstabs_proc *proc)
 
       valid = 0.;
       for (i = 0; i < pt->n_entries; i++)
-        valid += pt->entries[i]->freq;
+        valid += pt->entries[i]->count;
 
       n[0] = valid;
       n[1] = pt->missing;
@@ -1088,13 +1083,13 @@ build_matrix (struct pivot_table *x)
   const int row_var_width = var_get_width (x->vars[ROW_VAR]);
   int col, row;
   double *mp;
-  struct table_entry **p;
+  struct freq **p;
 
   mp = x->mat;
   col = row = 0;
   for (p = x->entries; p < &x->entries[x->n_entries]; p++)
     {
-      const struct table_entry *te = *p;
+      const struct freq *te = *p;
 
       while (!value_equal (&x->rows[row], &te->values[ROW_VAR], row_var_width))
         {
@@ -1110,7 +1105,7 @@ build_matrix (struct pivot_table *x)
           col++;
         }
 
-      *mp++ = te->freq;
+      *mp++ = te->count;
       if (++col >= x->n_cols)
         {
           col = 0;
@@ -1430,8 +1425,8 @@ find_crosstab (struct pivot_table *pt, size_t *row0p, size_t *row1p)
 
   for (row1 = row0 + 1; row1 < pt->n_entries; row1++)
     {
-      struct table_entry *a = pt->entries[row0];
-      struct table_entry *b = pt->entries[row1];
+      struct freq *a = pt->entries[row0];
+      struct freq *b = pt->entries[row1];
       if (compare_table_entry_vars_3way (a, b, pt, 2, pt->n_vars) != 0)
         break;
     }
@@ -1495,7 +1490,7 @@ enum_var_values (const struct pivot_table *pt, int var_idx,
       hmapx_init (&set);
       for (i = 0; i < pt->n_entries; i++)
         {
-          const struct table_entry *te = pt->entries[i];
+          const struct freq *te = pt->entries[i];
           const union value *value = &te->values[var_idx];
           size_t hash = value_hash (value, width, 0);
 
index 6c201021abf921a16aa2b4817613c7cd62f636df..535b39c440c44c26b4e0484562c09bd088ff9dc9 100644 (file)
 #include "libpspp/array.h"
 #include "libpspp/compiler.h"
 
+struct freq *
+freq_clone (const struct freq *in, int values, int *widths)
+{
+  int i;
+  struct freq *f = xmalloc (sizeof (struct freq) +
+                           (sizeof (union value) * (values - 1)));
+
+  f->node = in->node;
+  f->count = in->count;
+  for (i = 0; i < values; ++i)
+    {
+      value_init (&f->values[i],  widths[i]);
+      value_copy (&f->values[i], &in->values[i], widths[i]);
+    }
+
+  return f;
+}
+
+void
+freq_destroy (struct freq *f, int values, int *widths)
+{
+  int i;
+  for (i = 0; i < values; ++i)
+    {
+      value_destroy (&f->values[i],  widths[i]);
+    }
+
+  free (f);
+}
+
+
+
 void
 freq_hmap_destroy (struct hmap *hmap, int width)
 {
   struct freq *f, *next;
 
-  HMAP_FOR_EACH_SAFE (f, next, struct freq, hmap_node, hmap)
+  HMAP_FOR_EACH_SAFE (f, next, struct freq, node, hmap)
     {
-      value_destroy (&f->value, width);
-      hmap_delete (hmap, &f->hmap_node);
+      value_destroy (&f->values[0], width);
+      hmap_delete (hmap, &f->node);
       free (f);
     }
   hmap_destroy (hmap);
@@ -45,8 +77,8 @@ freq_hmap_search (struct hmap *hmap,
 {
   struct freq *f;
 
-  HMAP_FOR_EACH_WITH_HASH (f, struct freq, hmap_node, hash, hmap)
-    if (value_equal (value, &f->value, width))
+  HMAP_FOR_EACH_WITH_HASH (f, struct freq, node, hash, hmap)
+    if (value_equal (value, &f->values[0], width))
       return f;
 
   return NULL;
@@ -57,20 +89,20 @@ freq_hmap_insert (struct hmap *hmap,
                   const union value *value, int width, size_t hash)
 {
   struct freq *f = xmalloc (sizeof *f);
-  value_clone (&f->value, value, width);
+  value_clone (&f->values[0], value, width);
   f->count = 0;
-  hmap_insert (hmap, &f->hmap_node, hash);
+  hmap_insert (hmap, &f->node, hash);
   return f;
 }
 
-static int
+int
 compare_freq_ptr_3way (const void *a_, const void *b_, const void *width_)
 {
   const struct freq *const *ap = a_;
   const struct freq *const *bp = b_;
   const int *widthp = width_;
 
-  return value_compare_3way (&(*ap)->value, &(*bp)->value, *widthp);
+  return value_compare_3way (&(*ap)->values[0], &(*bp)->values[0], *widthp);
 }
 
 struct freq **
@@ -83,7 +115,7 @@ freq_hmap_sort (struct hmap *hmap, int width)
 
   entries = xnmalloc (n_entries, sizeof *entries);
   i = 0;
-  HMAP_FOR_EACH (f, struct freq, hmap_node, hmap)
+  HMAP_FOR_EACH (f, struct freq, node, hmap)
     entries[i++] = f;
   assert (i == n_entries);
 
@@ -102,7 +134,7 @@ freq_hmap_extract (struct hmap *hmap)
   n_freqs = hmap_count (hmap);
   freqs = xnmalloc (n_freqs, sizeof *freqs);
   i = 0;
-  HMAP_FOR_EACH (f, struct freq, hmap_node, hmap)
+  HMAP_FOR_EACH (f, struct freq, node, hmap)
     freqs[i++] = *f;
   assert (i == n_freqs);
 
index fd6081f4e13c465704ce2bf264b6a14d6daf9ebb..412a46ac9f232ebce882b1b26ec3b587371edbf3 100644 (file)
@@ -1,5 +1,5 @@
 /* PSPP - a program for statistical analysis.
-   Copyright (C) 2006, 2010 Free Software Foundation, Inc.
+   Copyright (C) 2006, 2010, 2015 Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
 /* Frequency table entry. */
 struct freq
   {
-    struct hmap_node hmap_node; /* Element in hash table. */
-    union value value;          /* The value. */
+    struct hmap_node node;      /* Element in hash table. */
     double count;              /* The number of occurrences of the value. */
+    union value values[1];      /* The value. */
   };
 
+
+struct freq *freq_clone (const struct freq *, int values, int *widths);
+void freq_destroy (struct freq *f, int values, int *widths);
+
+
+static inline size_t
+table_entry_size (size_t n_values)
+{
+  return (offsetof (struct freq, values)
+          + n_values * sizeof (union value));
+}
+
+
+int compare_freq_ptr_3way (const void *a_, const void *b_, const void *width_);
+
+
+
 void freq_hmap_destroy (struct hmap *, int width);
 
 struct freq *freq_hmap_search (struct hmap *, const union value *, int width,
@@ -38,4 +55,7 @@ struct freq *freq_hmap_insert (struct hmap *, const union value *, int width,
 struct freq **freq_hmap_sort (struct hmap *, int width);
 struct freq *freq_hmap_extract (struct hmap *);
 
+
+
+
 #endif /* language/stats/freq.h */
index 254bda3ef709c8fd1c6785390ed583cd9211ca6c..aa4eddfacc7831a9b8fab226f75f28e02a6b477a 100644 (file)
@@ -243,7 +243,7 @@ static void do_piechart(const struct frq_chart *pie,
                        const struct freq_tab *frq_tab);
 
 static void do_barchart(const struct frq_chart *bar,
-                       const struct variable *var,
+                       const struct variable **var,
                        const struct freq_tab *frq_tab);
 
 static void dump_statistics (const struct frq_proc *frq, 
@@ -264,7 +264,7 @@ compare_freq (const void *a_, const void *b_, const void *aux_)
     }
   else
     {
-      int cmp = value_compare_3way (&a->value, &b->value, aux->width);
+      int cmp = value_compare_3way (a->values, b->values, aux->width);
       return aux->ascending_value ? cmp : -cmp;
     }
 }
@@ -317,11 +317,11 @@ dump_freq_table (const struct var_freqs *vf, const struct variable *wv)
       valid_percent = f->count / ft->valid_cases * 100.0;
       cum_total += valid_percent;
 
-      label = var_lookup_value_label (vf->var, &f->value);
+      label = var_lookup_value_label (vf->var, f->values);
       if (label != NULL)
         tab_text (t, 0, r, TAB_LEFT, label);
 
-      tab_value (t, 1, r, TAB_NONE, &f->value, vf->var, NULL);
+      tab_value (t, 1, r, TAB_NONE, f->values, vf->var, NULL);
       tab_double (t, 2, r, TAB_NONE, f->count, NULL, RC_WEIGHT);
       tab_double (t, 3, r, TAB_NONE, percent, NULL, RC_OTHER);
       tab_double (t, 4, r, TAB_NONE, valid_percent, NULL, RC_OTHER);
@@ -334,11 +334,11 @@ dump_freq_table (const struct var_freqs *vf, const struct variable *wv)
 
       cum_freq += f->count;
 
-      label = var_lookup_value_label (vf->var, &f->value);
+      label = var_lookup_value_label (vf->var, f->values);
       if (label != NULL)
         tab_text (t, 0, r, TAB_LEFT, label);
 
-      tab_value (t, 1, r, TAB_NONE, &f->value, vf->var, NULL);
+      tab_value (t, 1, r, TAB_NONE, f->values, vf->var, NULL);
       tab_double (t, 2, r, TAB_NONE, f->count, NULL, RC_WEIGHT);
       tab_double (t, 3, r, TAB_NONE,
                  f->count / ft->total_cases * 100.0, NULL, RC_OTHER);
@@ -399,15 +399,15 @@ calc_percentiles (const struct frq_proc *frq, const struct var_freqs *vf)
             break;
 
           if (tp + 1 < rank || f + 1 >= ft->missing)
-            pc->value = f->value.f;
+            pc->value = f->values[0].f;
           else
-            pc->value = calc_percentile (pc->p, W, f->value.f, f[1].value.f);
+            pc->value = calc_percentile (pc->p, W, f->values[0].f, f[1].values[0].f);
         }
     }
   for (; percentile_idx < frq->n_percentiles; percentile_idx++)
     {
       struct percentile *pc = &frq->percentiles[percentile_idx];
-      pc->value = ft->valid[ft->n_valid - 1].value.f;
+      pc->value = ft->valid[ft->n_valid - 1].values[0].f;
     }
 }
 
@@ -419,7 +419,7 @@ not_missing (const void *f_, const void *v_)
   const struct freq *f = f_;
   const struct variable *v = v_;
 
-  return !var_is_value_missing (v, &f->value, MV_ANY);
+  return !var_is_value_missing (v, f->values, MV_ANY);
 }
 
 
@@ -569,7 +569,7 @@ postcalc (struct frq_proc *frq, const struct dataset *ds)
         do_piechart(frq->pie, vf->var, &vf->tab);
 
       if (frq->bar)
-        do_barchart(frq->bar, vf->var, &vf->tab);
+        do_barchart(frq->bar, &vf->var, &vf->tab);
 
       cleanup_freq_tab (vf);
     }
@@ -588,7 +588,7 @@ cmd_frequencies (struct lexer *lexer, struct dataset *ds)
 
   double pie_min = -DBL_MAX;
   double pie_max = DBL_MAX;
-  bool pie_missing = false;
+  bool pie_missing = true;
 
   double bar_min = -DBL_MAX;
   double bar_max = DBL_MAX;
@@ -798,7 +798,6 @@ cmd_frequencies (struct lexer *lexer, struct dataset *ds)
            {
              if (lex_match_id (lexer, "TABLE"))
                {
-                 
                }
              else if (lex_match_id (lexer, "NOTABLE"))
                {
@@ -1141,7 +1140,7 @@ cmd_frequencies (struct lexer *lexer, struct dataset *ds)
        frq.bar = xmalloc (sizeof *frq.bar);
        frq.bar->x_min = bar_min;
        frq.bar->x_max = bar_max;
-       frq.bar->include_missing = true;
+       frq.bar->include_missing = false;
        frq.bar->y_scale = bar_freq ? FRQ_FREQ : FRQ_PERCENT;
       }
 
@@ -1281,10 +1280,10 @@ freq_tab_to_hist (const struct frq_proc *frq, const struct freq_tab *ft,
   for (i = 0; i < ft->n_valid; i++)
     {
       const struct freq *f = &ft->valid[i];
-      if (chart_includes_value (frq->hist, var, &f->value))
+      if (chart_includes_value (frq->hist, var, f->values))
         {
-          x_min = MIN (x_min, f->value.f);
-          x_max = MAX (x_max, f->value.f);
+          x_min = MIN (x_min, f->values[0].f);
+          x_max = MAX (x_max, f->values[0].f);
           valid_freq += f->count;
         }
     }
@@ -1308,83 +1307,118 @@ freq_tab_to_hist (const struct frq_proc *frq, const struct freq_tab *ft,
   for (i = 0; i < ft->n_valid; i++)
     {
       const struct freq *f = &ft->valid[i];
-      if (chart_includes_value (frq->hist, var, &f->value))
-        histogram_add (histogram, f->value.f, f->count);
+      if (chart_includes_value (frq->hist, var, f->values))
+        histogram_add (histogram, f->values[0].f, f->count);
     }
 
   return histogram;
 }
 
-static int
-add_slice (const struct frq_chart *pie, const struct freq *freq,
-           const struct variable *var, struct slice *slice)
+
+/* Allocate an array of struct freqs and fill them from the data in FRQ_TAB,
+   according to the parameters of CATCHART
+   N_SLICES will contain the number of slices allocated.
+   The caller is responsible for freeing slices
+*/
+static struct freq *
+pick_cat_counts (const struct frq_chart *catchart,
+                const struct freq_tab *frq_tab,
+                int *n_slicesp)
 {
-  if (chart_includes_value (pie, var, &freq->value))
+  int n_slices = 0;
+  int i;
+  struct freq *slices = xnmalloc (frq_tab->n_valid + frq_tab->n_missing, sizeof *slices);
+  
+  for (i = 0; i < frq_tab->n_valid; i++)
+    {
+      const struct freq *f = &frq_tab->valid[i];
+      if (f->count > catchart->x_max)
+       continue;
+
+      if (f->count < catchart->x_min)
+       continue;
+      
+      slices[n_slices] = *f;
+      
+      n_slices++;
+    }
+
+  if (catchart->include_missing)
     {
-      ds_init_empty (&slice->label);
-      var_append_value_name (var, &freq->value, &slice->label);
-      slice->magnitude = freq->count;
-      return 1;
+      for (i = 0; i < frq_tab->n_missing; i++)
+       {
+         const struct freq *f = &frq_tab->missing[i];
+         slices[n_slices].count += f->count;
+         
+         if (i == 0)
+           slices[n_slices].values[0] = f->values[0];
+       }
+      
+      if (frq_tab->n_missing > 0)
+       n_slices++;
     }
-  else
-    return 0;
+
+  *n_slicesp = n_slices;
+  return slices;
 }
 
-/* Allocate an array of slices and fill them from the data in frq_tab
-   n_slices will contain the number of slices allocated.
+
+/* Allocate an array of struct freqs and fill them from the data in FRQ_TAB,
+   according to the parameters of CATCHART
+   N_SLICES will contain the number of slices allocated.
    The caller is responsible for freeing slices
 */
-static struct slice *
-freq_tab_to_slice_array(const struct frq_chart *catchart,
-                        const struct freq_tab *frq_tab,
-                       const struct variable *var,
-                       int *n_slicesp)
+static struct freq **
+pick_cat_counts_ptr (const struct frq_chart *catchart,
+                    const struct freq_tab *frq_tab,
+                    int *n_slicesp)
 {
-  struct slice *slices;
-  int n_slices;
+  int n_slices = 0;
   int i;
-  double total = 0;
-
-  slices = xnmalloc (frq_tab->n_valid + frq_tab->n_missing, sizeof *slices);
-  n_slices = 0;
-
+  struct freq **slices = xnmalloc (frq_tab->n_valid + frq_tab->n_missing, sizeof *slices);
   
   for (i = 0; i < frq_tab->n_valid; i++)
     {
-      const struct freq *f = &frq_tab->valid[i];
-      total += f->count;
+      struct freq *f = &frq_tab->valid[i];
       if (f->count > catchart->x_max)
        continue;
 
       if (f->count < catchart->x_min)
        continue;
-
-      n_slices += add_slice (catchart, f, var, &slices[n_slices]);
+      
+      slices[n_slices] = f;
+      
+      n_slices++;
     }
 
-  if (catchart->y_scale == FRQ_PERCENT) 
-    for (i = 0; i < frq_tab->n_valid; i++)
-      {
-       slices[i].magnitude /= total;
-       slices[i].magnitude *= 100.00;
-      }
-  
-  for (i = 0; i < frq_tab->n_missing; i++)
-    n_slices += add_slice (catchart, &frq_tab->missing[i], var, &slices[n_slices]);
+  if (catchart->include_missing)
+    {
+      for (i = 0; i < frq_tab->n_missing; i++)
+       {
+         const struct freq *f = &frq_tab->missing[i];
+         if (i == 0)
+           {
+             slices[n_slices] = xmalloc (sizeof (struct freq));
+             slices[n_slices]->values[0] = f->values[0];
+           }
+
+         slices[n_slices]->count += f->count;
+         
+       }
+    }
 
   *n_slicesp = n_slices;
   return slices;
 }
 
 
+
 static void
 do_piechart(const struct frq_chart *pie, const struct variable *var,
             const struct freq_tab *frq_tab)
 {
-  struct slice *slices;
-  int n_slices, i;
-
-  slices = freq_tab_to_slice_array (pie, frq_tab, var, &n_slices);
+  int n_slices;
+  struct freq *slices = pick_cat_counts (pie, frq_tab, &n_slices);
 
   if (n_slices < 2)
     msg (SW, _("Omitting pie chart for %s, which has only %d unique values."),
@@ -1393,29 +1427,22 @@ do_piechart(const struct frq_chart *pie, const struct variable *var,
     msg (SW, _("Omitting pie chart for %s, which has over 50 unique values."),
          var_get_name (var));
   else
-    chart_item_submit (piechart_create (var_to_string(var), slices, n_slices));
+    chart_item_submit (piechart_create (var, slices, n_slices));
 
-  for (i = 0; i < n_slices; i++)
-    ds_destroy (&slices[i].label);
   free (slices);
 }
 
 
 static void
-do_barchart(const struct frq_chart *bar, const struct variable *var,
+do_barchart(const struct frq_chart *bar, const struct variable **var,
             const struct freq_tab *frq_tab)
 {
-  struct slice *slices;
-  int n_slices, i;
-
-  slices = freq_tab_to_slice_array (bar, frq_tab, var, &n_slices);
+  int n_slices;
+  struct freq **slices = pick_cat_counts_ptr (bar, frq_tab, &n_slices);
 
-  chart_item_submit (barchart_create (var_to_string (var), 
+  chart_item_submit (barchart_create (var, 1,
                                      (bar->y_scale == FRQ_FREQ) ? _("Count") : _("Percent"),
                                      slices, n_slices));
-
-  for (i = 0; i < n_slices; i++)
-    ds_destroy (&slices[i].label);
   free (slices);
 }
 
@@ -1438,7 +1465,7 @@ calc_stats (const struct var_freqs *vf, double d[FRQ_ST_count])
       if (most_often < f->count)
         {
           most_often = f->count;
-          X_mode = f->value.f;
+          X_mode = f->values[0].f;
         }
       else if (most_often == f->count)
         {
@@ -1451,16 +1478,16 @@ calc_stats (const struct var_freqs *vf, double d[FRQ_ST_count])
   /* Calculate moments. */
   m = moments_create (MOMENT_KURTOSIS);
   for (f = ft->valid; f < ft->missing; f++)
-    moments_pass_one (m, f->value.f, f->count);
+    moments_pass_one (m, f->values[0].f, f->count);
   for (f = ft->valid; f < ft->missing; f++)
-    moments_pass_two (m, f->value.f, f->count);
+    moments_pass_two (m, f->values[0].f, f->count);
   moments_calculate (m, NULL, &d[FRQ_ST_MEAN], &d[FRQ_ST_VARIANCE],
                      &d[FRQ_ST_SKEWNESS], &d[FRQ_ST_KURTOSIS]);
   moments_destroy (m);
 
   /* Formulae below are taken from _SPSS Statistical Algorithms_. */
-  d[FRQ_ST_MINIMUM] = ft->valid[0].value.f;
-  d[FRQ_ST_MAXIMUM] = ft->valid[ft->n_valid - 1].value.f;
+  d[FRQ_ST_MINIMUM] = ft->valid[0].values[0].f;
+  d[FRQ_ST_MAXIMUM] = ft->valid[ft->n_valid - 1].values[0].f;
   d[FRQ_ST_MODE] = X_mode;
   d[FRQ_ST_RANGE] = d[FRQ_ST_MAXIMUM] - d[FRQ_ST_MINIMUM];
   d[FRQ_ST_SUM] = d[FRQ_ST_MEAN] * W;
index c543086a39e720cad5742fbef1b6e5084f43a21c..08ae1d0ca3e100c23eee6b4a2aea4f9db634a611 100644 (file)
 #define CHART_GEOMETRY_H
 
 struct decimal;
-void chart_rounded_tick(double tick, struct decimal *);
+void chart_rounded_tick (double tick, struct decimal *);
 
 void chart_get_scale (double high, double low,
-                          struct decimal *lower, struct decimal *interval, int *n_ticks);
+                     struct decimal *lower, struct decimal *interval, int *n_ticks);
 
 
 #endif
index 27000cb56fa4cd10fb7706d868fb5171d70fd914..24a2dce51690fd0e1007712f6d7dcd56a4602e39 100644 (file)
@@ -165,6 +165,8 @@ void xrchart_draw_roc (const struct chart_item *, cairo_t *,
                        struct xrchart_geometry *);
 void xrchart_draw_piechart (const struct chart_item *, cairo_t *,
                             struct xrchart_geometry *);
+void xrchart_draw_barchart (const struct chart_item *, cairo_t *,
+                            struct xrchart_geometry *);
 void xrchart_draw_histogram (const struct chart_item *, cairo_t *,
                              struct xrchart_geometry *);
 void xrchart_draw_np_plot (const struct chart_item *, cairo_t *,
index 3ba3f001c5959dec57748e61a22487cb39b5c130..397715ff386d38884ce4ca9838ec39a1bb648c74 100644 (file)
@@ -20,6 +20,7 @@
 #include "output/charts/piechart.h"
 
 #include <math.h>
+#include "data/variable.h"
 
 #include "output/cairo-chart.h"
 
 #define _(msgid) gettext (msgid)
 
 
+
 static void
-draw_bar (cairo_t *cr, const struct xrchart_geometry *geom,
-         const struct barchart *bc, int bar)
+abscissa_label (const struct barchart *bc, cairo_t *cr,
+               struct xrchart_geometry *geom,
+               const union value *prev,
+               double x_pos,
+               double width,
+               int n_last_cat)
 {
-  const double width =
-    (geom->axis[SCALE_ABSCISSA].data_max - geom->axis[SCALE_ABSCISSA].data_min) 
-    / 
-    (double) bc->n_bars ;
-
-  const double x_pos =
-    (geom->axis[SCALE_ABSCISSA].data_max - geom->axis[SCALE_ABSCISSA].data_min) *
-    bar 
-    / 
-    (double) bc->n_bars ;
-
-
-  double height = geom->axis[SCALE_ORDINATE].scale * bc->bars[bar].magnitude;
-
-  cairo_rectangle (cr,
-                  geom->axis[SCALE_ABSCISSA].data_min + x_pos + width * 0.1,
-                  geom->axis[SCALE_ORDINATE].data_min,
-                   width * 0.8, height);
-  cairo_save (cr);
-  cairo_set_source_rgb (cr,
-                        geom->fill_colour.red / 255.0,
-                        geom->fill_colour.green / 255.0,
-                        geom->fill_colour.blue / 255.0);
-  cairo_fill_preserve (cr);
-  cairo_restore (cr);
-  cairo_stroke (cr);
-
-  draw_tick (cr, geom, SCALE_ABSCISSA, true,
-            x_pos + width / 2.0, "%s", ds_cstr (&bc->bars[bar].label));
+  struct category *foo = NULL;
+  size_t hash = value_hash (prev, bc->widths[0], 0);
+  HMAP_FOR_EACH_WITH_HASH (foo, struct category, node, hash, &bc->primaries)
+    {
+      if (value_equal (&foo->val, prev, bc->widths[0])) 
+       break;
+    }
+  
+  draw_tick (cr, geom, SCALE_ABSCISSA, false,
+            x_pos - (width * n_last_cat) / 2.0,
+            "%s",  ds_cstr (&foo->label));
 }
 
 
+
+
 void
 xrchart_draw_barchart (const struct chart_item *chart_item, cairo_t *cr,
                        struct xrchart_geometry *geom)
@@ -72,16 +62,112 @@ xrchart_draw_barchart (const struct chart_item *chart_item, cairo_t *cr,
   struct barchart *bc = to_barchart (chart_item);
   int i;
 
-  xrchart_write_title (cr, geom, _("BARCHART"));
+  xrchart_write_title (cr, geom, _("Bar Chart"));
 
   xrchart_write_ylabel (cr, geom, bc->ylabel);
   xrchart_write_xlabel (cr, geom, chart_item_get_title (chart_item));
 
   xrchart_write_yscale (cr, geom, 0, bc->largest);
 
-  for (i = 0; i < bc->n_bars; i++)
+  const double abscale = geom->axis[SCALE_ABSCISSA].data_max - geom->axis[SCALE_ABSCISSA].data_min;
+  const double width = abscale / (double) (bc->n_nzcats + bc->n_pcats);
+
+  double x_pos = 0.5 * width;
+  union value *prev = NULL;
+
+  if (bc->ss)
     {
-      draw_bar (cr, geom, bc, i);
+      const int blob_size = 13;
+      const int height = blob_size * (hmap_count (&bc->secondaries) * 2);
+      
+      cairo_rectangle (cr,
+                      geom->axis[SCALE_ABSCISSA].data_max + 10,
+                      geom->axis[SCALE_ORDINATE].data_max - height,
+                      100, height);
+
+      cairo_stroke (cr);
+
+      int ypos = blob_size * 1.5;
+      for (i = 0 ; i < hmap_count (&bc->secondaries) ; ++i)
+       {
+         const struct category *foo = bc->ss[i];
+
+         cairo_move_to (cr, 
+                        geom->axis[SCALE_ABSCISSA].data_max + (1.5 * blob_size) + 20,
+                        geom->axis[SCALE_ORDINATE].data_max - ypos);
+         
+         xrchart_label (cr, 'l', 'b', geom->font_size, ds_cstr (&foo->label));
+
+         cairo_rectangle (cr,
+                          geom->axis[SCALE_ABSCISSA].data_max + 20,
+                          geom->axis[SCALE_ORDINATE].data_max - ypos,
+                          blob_size, blob_size);
+
+         cairo_save (cr);
+         cairo_set_source_rgb (cr,
+                               data_colour[foo->idx].red / 255.0,
+                               data_colour[foo->idx].green / 255.0,
+                               data_colour[foo->idx].blue / 255.0);
+         cairo_fill_preserve (cr);
+
+         cairo_restore (cr);
+         
+         cairo_stroke (cr);
+
+         ypos += blob_size * 2;
+       }
     }
+
+  int n_last_cat = 0;
+  for (i = 0; i < bc->n_nzcats; i++)
+    {
+      double height = geom->axis[SCALE_ORDINATE].scale * bc->cats[i]->count;
+
+      if (prev && !value_equal (prev, &bc->cats[i]->values[0], bc->widths[0]))
+       {
+         abscissa_label (bc, cr, geom, prev, x_pos, width, n_last_cat);
+
+         x_pos += width;
+         n_last_cat = 0;
+       }
+
+      cairo_rectangle (cr,
+                      geom->axis[SCALE_ABSCISSA].data_min + x_pos,
+                      geom->axis[SCALE_ORDINATE].data_min,
+                      width, height);
+      cairo_save (cr);
+
+
+      int cidx = 0;
+      if (bc->ss)
+       {
+         struct category *foo;
+         size_t hash = value_hash (&bc->cats[i]->values[1], bc->widths[1], 0);
+         HMAP_FOR_EACH_WITH_HASH (foo, struct category, node, hash, &bc->secondaries)
+           {
+             if (value_equal (&foo->val, &bc->cats[i]->values[1], bc->widths[1]))
+               {
+                 cidx = foo->idx;
+                 break;
+               }
+           }
+       }
+
+      cairo_set_source_rgb (cr,
+                           data_colour[cidx].red / 255.0,
+                           data_colour[cidx].green / 255.0,
+                           data_colour[cidx].blue / 255.0);
+      cairo_fill_preserve (cr);
+
+      cairo_restore (cr);
+      cairo_stroke (cr);
+
+      x_pos += width;
+
+      prev = &bc->cats[i]->values[0];
+      n_last_cat ++;
+    }
+
+  abscissa_label (bc, cr, geom, prev, x_pos, width, n_last_cat);
 }
 
index f37998747a58da1b66f11eebd7f15766990cdb86..5d35719a861796cbf1d82e3ab0f5ea9310e22c12 100644 (file)
 
 #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);
 }
 
index ea6c27e0796a246c025cd2e3a835abafb6a56766..dabf61b05036861d84b6307ab4fec151a3833ef6 100644 (file)
 #define BARCHART_H
 
 #include "libpspp/str.h"
+#include "libpspp/hmap.h"
+#include "data/value.h"
 #include "output/chart-item.h"
 
+
+struct category
+{
+  struct hmap_node node;
+  int idx;                     /* Unique zero based index */
+  struct string label;         /* The label to be displayed for this category */
+  union value val;             /* The value of this category */
+};
+
+
 struct barchart
   {
     struct chart_item chart_item;
-    struct slice *bars;
-    int n_bars;
+
+    /* The categories */
+    struct freq **cats;
+
+    /* The total number of categories (regardless of level) */
+    int n_nzcats;
+
+    /* The number of primary categories */
+    int n_pcats;
+
+    /* The largest count of all the categories */
     double largest;
+
+    /* The label for the ordinate (vertical axis) */
     char *ylabel;
+
+    /* The variables holding the categorical values */
+    const struct variable **var;
+    int n_vars;
+
+    int widths[2];
+
+    /* A hash table of struct category indexed by VAL */
+    struct hmap primaries;
+
+    /* A hash table of struct category indexed by VAL */
+    struct hmap secondaries;
+
+
+    /* A array of pointers to the members of the above hmap, 
+       sorted by VAL */
+    struct category **ss;
   };
 
-struct chart_item *barchart_create (const char *title, const char *ylabel,
-                                    const struct slice *, int n_bars);
+
+struct variable;
+struct freq;
+
+struct chart_item *barchart_create (const struct variable **, int n_vars,
+                                   const char *ylabel,
+                                    struct freq *const *, int n_cats);
 \f
 /* This boilerplate for barchart, a subclass of chart_item, was
    autogenerated by mk-class-boilerplate. */
index 366e729e7cd16b4811dbbcb42b0a76f9578f7fb2..0467c8338fcc3b37be8c34fae15fcbfc6d934db6 100644 (file)
 
 #include "libpspp/cast.h"
 #include "libpspp/str.h"
+#include "data/variable.h"
 #include "output/chart-item-provider.h"
 
 #include "gl/xalloc.h"
 
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+#define N_(msgid) msgid
+
+
 /* Creates and returns a chart that will render a piechart with
-   the given TITLE and the N_SLICES described in SLICES. */
+   the of VAR and the N_SLICES described in SLICES. */
 struct chart_item *
-piechart_create (const char *title, const struct slice *slices, int n_slices)
+piechart_create (const struct variable *var, const struct freq *slices, int n_slices)
 {
   struct piechart *pie;
   int i;
 
   pie = xmalloc (sizeof *pie);
-  chart_item_init (&pie->chart_item, &piechart_class, title);
+  chart_item_init (&pie->chart_item, &piechart_class, var_to_string (var));
   pie->slices = xnmalloc (n_slices, sizeof *pie->slices);
   for (i = 0; i < n_slices; i++)
     {
-      const struct slice *src = &slices[i];
+      const struct freq *src = &slices[i];
       struct slice *dst = &pie->slices[i];
 
-      ds_init_string (&dst->label, &src->label);
+      ds_init_empty (&dst->label);
+
+      if ( var_is_value_missing (var, &src->values[0], MV_ANY))
+       ds_assign_cstr (&dst->label, _("*MISSING*"));
+      else
+       var_append_value_name (var, &src->values[0], &dst->label);
 
       /* Chomp any whitespace from the RHS of the label.
         Doing this ensures that those labels to the right
         of the pie, appear right justified. */
       ds_rtrim (&dst->label, ss_cstr (" \t"));
-      dst->magnitude = src->magnitude;
+      ds_ltrim (&dst->label, ss_cstr (" \t"));
+      dst->magnitude = src->count;
     }
   pie->n_slices = n_slices;
   return &pie->chart_item;
index b59a7296d14e792031a595a1c024808c25a13451..1899a511c118515424ab0a6e558a18b519300de7 100644 (file)
@@ -19,6 +19,7 @@
 
 #include "libpspp/str.h"
 #include "output/chart-item.h"
+#include "language/stats/freq.h"
 
 struct piechart
   {
@@ -33,8 +34,10 @@ struct slice
     double magnitude;
   };
 
-struct chart_item *piechart_create (const char *title,
-                                    const struct slice *, int n_slices);
+struct variable;
+
+struct chart_item *piechart_create (const struct variable *var,
+                                    const struct freq *, int n_slices);
 \f
 /* This boilerplate for piechart, a subclass of chart_item, was
    autogenerated by mk-class-boilerplate. */
index 31546677686d20425d837076019d8da6688a6394..d2e9e8ff179baa72a653b5287792d53a8d834668 100644 (file)
@@ -1536,3 +1536,42 @@ Nominal by Nominal,Lambda,Symmetric,.000,.000,NaN,NaN
 ,,y Dependent,.184,.019,7.890,.   @&t@
 ])
 AT_CLEANUP
+
+
+
+AT_SETUP([CROSSTABS barchart])
+AT_DATA([bc.sps], [dnl
+SET FORMAT=F8.3.
+
+DATA LIST LIST NOTABLE /x (a20) y (f8) z (f8) w (f8) .
+BEGIN DATA.
+This  1  0 416
+That  2  0 121
+Other 2  0 335
+This  2  0 231
+That  3  0 112
+Other 4  0 130
+This  1  1 160
+That  2  1 211
+Other 2  1 352
+This  2  1 212
+That  3  1 121
+Other 4  1 101
+END DATA.
+
+WEIGHT BY w.
+
+CROSSTABS 
+         /table x BY y BY z
+         /table x BY y
+         /barchart.
+])
+
+AT_CHECK([pspp -O format=txt -o xxx bc.sps], [0], [ignore])
+
+AT_CHECK([test -e xxx-1.png], [0], [ignore])
+AT_CHECK([test -e xxx-2.png], [0], [ignore])
+
+AT_CHECK([diff xxx-1.png xxx-2.png], [0], [ignore])
+
+AT_CLEANUP
index a713cdb0d58557e8015169342f133f36a447ed1c..1cf67c3d96aabca5b160f6a3bfb6747ea0fa9ef8 100644 (file)
@@ -18,7 +18,7 @@
 #include <stdlib.h>
 #include "math/chart-geometry.h"
 #include "math/decimal.h"
-
+#include "libpspp/compiler.h"
 
 const double in[20] =
   {
@@ -45,7 +45,7 @@ const double in[20] =
   };
 
 int 
-main ()
+main (int argc UNUSED, char **argv UNUSED)
 {
   int i;
   for (i = 0; i < 20; ++i)
index 11a572deeb8a779d3c965cd12c14930bd961de1b..d4cbe18eb03f5a1094cde4bacb619b9773901f11 100644 (file)
 #include <assert.h>
 #include <string.h>
 
+#include "libpspp/compiler.h"
+
 #include "math/decimal.h"
+#include "math/chart-geometry.h"
 #include <limits.h>
 #include <float.h>
 #include <math.h>
 
-void
+#if 0
+static void
 dump_scale (const struct decimal *low, const struct decimal *interval, int n_ticks)
 {
   int i;
@@ -37,16 +41,16 @@ dump_scale (const struct decimal *low, const struct decimal *interval, int n_tic
       decimal_add (&tick, interval);
     }
 }
+#endif
 
 
-void
+static void
 test_range (double low, double high)
 {
   int n_ticks = 0;
   struct decimal interval;
   struct decimal lower;
 
-  
   chart_get_scale (high, low,
                   &lower, &interval, &n_ticks);
 
@@ -82,7 +86,7 @@ test_range (double low, double high)
 
 
 int 
-main (int argc, char **argv)
+main (int argc UNUSED, char **argv UNUSED)
 {
   test_range (0.2, 11);
   test_range (-0.2, 11);
index 3e1e64eaaf419dc15c4caf66d18257357af266f0..e586b19d6915a91445bc98251fa3fa35cfece8b4 100644 (file)
@@ -21,6 +21,7 @@
 #include <assert.h>
 #include <string.h>
 
+#include "libpspp/compiler.h"
 #include "math/decimal.h"
 #include <limits.h>
 #include <float.h>
@@ -33,7 +34,7 @@
    This function is used purely for testing, and need not and is not intended
    to be efficient.
  */
-char *
+static char *
 canonicalise_string (const char *s)
 {
   char *out;
@@ -109,7 +110,7 @@ canonicalise_string (const char *s)
 
 /* Tests both the decimal_to_string function, and the decimal_input_from_string 
    function */
-void
+static void
 test_run (const char *input)
   {
     struct decimal test;
@@ -131,7 +132,7 @@ test_run (const char *input)
   }
 
 
-void
+static void
 test_can (const char *in, const char *soll)
 {
   char *ist = canonicalise_string (in);
@@ -142,7 +143,8 @@ test_can (const char *in, const char *soll)
 }
 
 
-void
+#if 0
+static void
 dump_scale (const struct decimal *low, const struct decimal *interval, int n_ticks)
 {
   int i;
@@ -154,10 +156,11 @@ dump_scale (const struct decimal *low, const struct decimal *interval, int n_tic
       decimal_add (&tick, interval);
     }
 }
+#endif
 
 
 
-void
+static void
 test_ceil (double x)
 {
   struct decimal dx;
@@ -168,7 +171,7 @@ test_ceil (double x)
   assert (act == expected);
 }
 
-void
+static void
 test_floor (double x)
 {
   struct decimal dx;
@@ -180,12 +183,10 @@ test_floor (double x)
 }
 
 
-void
+static void
 test_addition (const struct decimal *one_, const struct decimal *two)
 {
   struct decimal one = *one_;
-  double d1 = decimal_to_double (&one);
-  double d2 = decimal_to_double (two);
   
   decimal_add (&one, two);
   
@@ -201,7 +202,7 @@ test_addition (const struct decimal *one_, const struct decimal *two)
 }
 
 
-void
+static void
 test_multiplication (const struct decimal *d, int m)
 {
   char b1[256];
@@ -221,7 +222,7 @@ test_multiplication (const struct decimal *d, int m)
 
 
 int 
-main (int argc, char **argv)
+main (int argc UNUSED, char **argv UNUSED)
 {
   /* Test that our canonicalise function works for all corner cases we
      can think of. */
index 3bc8837f563a52c45c8b315fbc7e5f8f67509996..7cbaeb2e823fc5ed78adf454743f0b91bddaf1f4 100644 (file)
@@ -192,3 +192,46 @@ FREQUENCIES /VARIABLES=religion nationality /BARCHART /PIECHART.
 AT_CHECK([pspp  -o pspp.txt xxx.sps], [0], [ignore])
 
 AT_CLEANUP
+
+
+
+AT_SETUP([CROSSTABS charts])
+AT_DATA([xxx.sps],[
+DATA LIST LIST /nationality (A10)  religion (A20) gender (A8).
+BEGIN DATA.
+Australian  Sikh      Male
+Australian  Sikh      Male
+Australian  Sikh      Male
+Australian  Sikh      Male
+British     Zoroastrian Female
+British     Buddist   Female
+British     Buddist   Female
+British      Zoroastrian Female
+German      Muslim    Male
+German      Christian Male
+German      Christian Female
+German      Christian Male
+German      Zoroastrian Female
+German      Sikh   Female
+German      Muslim Female
+German      Pastafarian Female
+German      "Jedi Knight" Female
+Belgian     Sikh      Male
+French       Muslim      Male
+French       Muslim      Male
+French       Christian      Male
+END DATA.
+
+
+CROSSTABS 
+         /tables = nationality by religion by gender
+         /tables = nationality by religion
+         /tables = religion by gender
+         /tables = nationality by religion by gender
+         /barchart.
+])
+
+
+AT_CHECK([pspp  -o pspp.txt xxx.sps], [0], [ignore])
+
+AT_CLEANUP