Frequencies: Added the /BARCHART subcommand.
authorJohn Darrington <john@darrington.wattle.id.au>
Sun, 18 Jan 2015 10:43:06 +0000 (11:43 +0100)
committerJohn Darrington <john@darrington.wattle.id.au>
Wed, 21 Jan 2015 19:33:18 +0000 (20:33 +0100)
NEWS
doc/statistics.texi
src/language/stats/frequencies.c
src/output/automake.mk
src/output/cairo.c
src/output/charts/barchart-cairo.c [new file with mode: 0644]
src/output/charts/barchart.c [new file with mode: 0644]
src/output/charts/barchart.h [new file with mode: 0644]
tests/output/charts.at

diff --git a/NEWS b/NEWS
index 5ffa65200efeabe0fa51a2a5bd93ee56d44b2c1b..a52e25f7b4efd270550184c4effe178c28878ae0 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,8 @@ Please send PSPP bug reports to bug-gnu-pspp@gnu.org.
  
 Changes since 0.8.4:
 
+ * The FREQUENCIES command can now generate barcharts.
+
  * The FACTOR command can now perform PROMAX rotations.
 
  * SPSS/PC+ system files are now supported on GET and other commands
index 789e37bc9591a9f56bb09361c6a4ddc1fb0160ce..f0160f40a8a2888b626c4f61f00ca3c15758a216 100644 (file)
@@ -136,9 +136,11 @@ FREQUENCIES
                    [@{FREQ[(@var{y_max})],PERCENT[(@var{y_max})]@}] [@{NONORMAL,NORMAL@}]
         /PIECHART=[MINIMUM(@var{x_min})] [MAXIMUM(@var{x_max})]
                   [@{FREQ,PERCENT@}] [@{NOMISSING,MISSING@}]
+        /BARCHART=[MINIMUM(@var{x_min})] [MAXIMUM(@var{x_max})]
+                  [@{FREQ,PERCENT@}]
+
 
 (These options are not currently implemented.)
-        /BARCHART=@dots{}
         /HBAR=@dots{}
         /GROUPED=@dots{}
 @end display
index 17e03130923bab5888580f0493d39f8decd712e3..254bda3ef709c8fd1c6785390ed583cd9211ca6c 100644 (file)
@@ -1,6 +1,6 @@
 /*
   PSPP - a program for statistical analysis.
-  Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2014 Free Software Foundation, Inc.
+  Copyright (C) 1997-9, 2000, 2007, 2009, 2010, 2011, 2014, 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
@@ -53,6 +53,7 @@
 
 
 #include "output/chart-item.h"
+#include "output/charts/barchart.h"
 #include "output/charts/piechart.h"
 #include "output/charts/plot-hist.h"
 #include "output/tab.h"
@@ -222,7 +223,7 @@ struct frq_proc
     int n_stats;
 
     /* Histogram and pie chart settings. */
-    struct frq_chart *hist, *pie;
+    struct frq_chart *hist, *pie, *bar;
   };
 
 
@@ -241,6 +242,10 @@ static void do_piechart(const struct frq_chart *pie,
                        const struct variable *var,
                        const struct freq_tab *frq_tab);
 
+static void do_barchart(const struct frq_chart *bar,
+                       const struct variable *var,
+                       const struct freq_tab *frq_tab);
+
 static void dump_statistics (const struct frq_proc *frq, 
                             const struct var_freqs *vf,
                             const struct variable *wv);
@@ -563,6 +568,9 @@ postcalc (struct frq_proc *frq, const struct dataset *ds)
       if (frq->pie)
         do_piechart(frq->pie, vf->var, &vf->tab);
 
+      if (frq->bar)
+        do_barchart(frq->bar, vf->var, &vf->tab);
+
       cleanup_freq_tab (vf);
     }
 }
@@ -582,6 +590,10 @@ cmd_frequencies (struct lexer *lexer, struct dataset *ds)
   double pie_max = DBL_MAX;
   bool pie_missing = false;
 
+  double bar_min = -DBL_MAX;
+  double bar_max = DBL_MAX;
+  bool bar_freq = true;
+
   double hi_min = -DBL_MAX;
   double hi_max = DBL_MAX;
   int hi_scale = FRQ_FREQ;
@@ -610,6 +622,7 @@ cmd_frequencies (struct lexer *lexer, struct dataset *ds)
 
   frq.hist = NULL;
   frq.pie = NULL;
+  frq.bar = NULL;
 
 
   /* Accept an optional, completely pointless "/VARIABLES=" */
@@ -980,6 +993,66 @@ cmd_frequencies (struct lexer *lexer, struct dataset *ds)
            }
          sbc_piechart = true;
        }
+      else if (lex_match_id (lexer, "BARCHART"))
+        {
+         lex_match (lexer, T_EQUALS);
+         while (lex_token (lexer) != T_ENDCMD
+                && lex_token (lexer) != T_SLASH)
+           {
+             if (lex_match_id (lexer, "MINIMUM"))
+               {
+                 lex_force_match (lexer, T_LPAREN);
+                 if (lex_force_num (lexer))
+                   {
+                     bar_min = lex_number (lexer);
+                     lex_get (lexer);
+                   }
+                 lex_force_match (lexer, T_RPAREN);
+               }
+             else if (lex_match_id (lexer, "MAXIMUM"))
+               {
+                 lex_force_match (lexer, T_LPAREN);
+                 if (lex_force_num (lexer))
+                   {
+                     bar_max = lex_number (lexer);
+                     lex_get (lexer);
+                   }
+                 lex_force_match (lexer, T_RPAREN);
+               }
+             else if (lex_match_id (lexer, "FREQ"))
+               {
+                 if ( lex_match (lexer, T_LPAREN))
+                   {
+                     if (lex_force_num (lexer))
+                       {
+                         lex_number (lexer);
+                         lex_get (lexer);
+                       }
+                     lex_force_match (lexer, T_RPAREN);
+                   }
+                 bar_freq = true;
+               }
+             else if (lex_match_id (lexer, "PERCENT"))
+               {
+                 if ( lex_match (lexer, T_LPAREN))
+                   {
+                     if (lex_force_num (lexer))
+                       {
+                         lex_number (lexer);
+                         lex_get (lexer);
+                       }
+                     lex_force_match (lexer, T_RPAREN);
+                   }
+                 bar_freq = false;
+               }
+             else
+               {
+                 lex_error (lexer, NULL);
+                 goto error;
+               }
+           }
+         sbc_barchart = true;
+       }
       else if (lex_match_id (lexer, "MISSING"))
         {
          lex_match (lexer, T_EQUALS);
@@ -1024,9 +1097,6 @@ cmd_frequencies (struct lexer *lexer, struct dataset *ds)
 /* Figure out which charts the user requested.  */
 
   {
-    if (sbc_barchart)
-      msg (SW, _("Bar charts are not implemented."));
-
     if (sbc_histogram)
       {
        struct frq_chart *hist;
@@ -1066,6 +1136,15 @@ cmd_frequencies (struct lexer *lexer, struct dataset *ds)
        frq.n_percentiles+=2;
       }
 
+    if (sbc_barchart)
+      {
+       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->y_scale = bar_freq ? FRQ_FREQ : FRQ_PERCENT;
+      }
+
     if (sbc_piechart)
       {
        struct frq_chart *pie;
@@ -1256,7 +1335,7 @@ add_slice (const struct frq_chart *pie, const struct freq *freq,
    The caller is responsible for freeing slices
 */
 static struct slice *
-freq_tab_to_slice_array(const struct frq_chart *pie,
+freq_tab_to_slice_array(const struct frq_chart *catchart,
                         const struct freq_tab *frq_tab,
                        const struct variable *var,
                        int *n_slicesp)
@@ -1264,14 +1343,34 @@ freq_tab_to_slice_array(const struct frq_chart *pie,
   struct slice *slices;
   int n_slices;
   int i;
+  double total = 0;
 
   slices = xnmalloc (frq_tab->n_valid + frq_tab->n_missing, sizeof *slices);
   n_slices = 0;
 
+  
   for (i = 0; i < frq_tab->n_valid; i++)
-    n_slices += add_slice (pie, &frq_tab->valid[i], var, &slices[n_slices]);
+    {
+      const struct freq *f = &frq_tab->valid[i];
+      total += f->count;
+      if (f->count > catchart->x_max)
+       continue;
+
+      if (f->count < catchart->x_min)
+       continue;
+
+      n_slices += add_slice (catchart, f, var, &slices[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 (pie, &frq_tab->missing[i], var, &slices[n_slices]);
+    n_slices += add_slice (catchart, &frq_tab->missing[i], var, &slices[n_slices]);
 
   *n_slicesp = n_slices;
   return slices;
@@ -1301,6 +1400,26 @@ do_piechart(const struct frq_chart *pie, const struct variable *var,
   free (slices);
 }
 
+
+static void
+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);
+
+  chart_item_submit (barchart_create (var_to_string (var), 
+                                     (bar->y_scale == FRQ_FREQ) ? _("Count") : _("Percent"),
+                                     slices, n_slices));
+
+  for (i = 0; i < n_slices; i++)
+    ds_destroy (&slices[i].label);
+  free (slices);
+}
+
+
 /* Calculates all the pertinent statistics for VF, putting them in array
    D[]. */
 static void
index 39c52d7496f59be166d53c70d9366a29e47d4299..04fddfd94230e625106ca7ba26950dbf17a1d7b4 100644 (file)
@@ -14,6 +14,8 @@ src_output_liboutput_la_SOURCES = \
        src/output/charts/boxplot.h \
        src/output/charts/np-plot.c \
        src/output/charts/np-plot.h \
+       src/output/charts/barchart.c \
+       src/output/charts/barchart.h \
        src/output/charts/piechart.c \
        src/output/charts/piechart.h \
        src/output/charts/plot-hist.c \
@@ -68,6 +70,7 @@ src_output_liboutput_la_SOURCES += \
        src/output/cairo.h \
        src/output/charts/boxplot-cairo.c \
        src/output/charts/np-plot-cairo.c \
+       src/output/charts/barchart-cairo.c \
        src/output/charts/piechart-cairo.c \
        src/output/charts/plot-hist-cairo.c \
        src/output/charts/roc-chart-cairo.c \
index a962deb7acbfe49b6f2c699398ddb4b13fce31d6..4af75caae455f3a5f03f1c687bcfba7efc45c324 100644 (file)
@@ -30,6 +30,7 @@
 #include "output/charts/boxplot.h"
 #include "output/charts/np-plot.h"
 #include "output/charts/piechart.h"
+#include "output/charts/barchart.h"
 #include "output/charts/plot-hist.h"
 #include "output/charts/roc-chart.h"
 #include "output/charts/spreadlevel-plot.h"
@@ -1410,6 +1411,8 @@ xr_draw_chart (const struct chart_item *chart_item, cairo_t *cr,
     xrchart_draw_np_plot (chart_item, cr, &geom);
   else if (is_piechart (chart_item))
     xrchart_draw_piechart (chart_item, cr, &geom);
+  else if (is_barchart (chart_item))
+    xrchart_draw_barchart (chart_item, cr, &geom);
   else if (is_roc_chart (chart_item))
     xrchart_draw_roc (chart_item, cr, &geom);
   else if (is_scree (chart_item))
diff --git a/src/output/charts/barchart-cairo.c b/src/output/charts/barchart-cairo.c
new file mode 100644 (file)
index 0000000..3ba3f00
--- /dev/null
@@ -0,0 +1,87 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "output/charts/barchart.h"
+#include "output/charts/piechart.h"
+
+#include <math.h>
+
+#include "output/cairo-chart.h"
+
+#include "gl/minmax.h"
+
+#include "gettext.h"
+#define _(msgid) gettext (msgid)
+
+
+static void
+draw_bar (cairo_t *cr, const struct xrchart_geometry *geom,
+         const struct barchart *bc, int bar)
+{
+  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));
+}
+
+
+void
+xrchart_draw_barchart (const struct chart_item *chart_item, cairo_t *cr,
+                       struct xrchart_geometry *geom)
+{
+  struct barchart *bc = to_barchart (chart_item);
+  int i;
+
+  xrchart_write_title (cr, geom, _("BARCHART"));
+
+  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++)
+    {
+      draw_bar (cr, geom, bc, i);
+    }
+}
+
diff --git a/src/output/charts/barchart.c b/src/output/charts/barchart.c
new file mode 100644 (file)
index 0000000..f379987
--- /dev/null
@@ -0,0 +1,76 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#include <config.h>
+
+#include "output/charts/barchart.h"
+#include "output/charts/piechart.h"
+
+#include <stdlib.h>
+
+#include "libpspp/cast.h"
+#include "libpspp/str.h"
+#include "output/chart-item-provider.h"
+
+#include "gl/xalloc.h"
+
+/* Creates and returns a chart that will render a barchart with
+   the given TITLE and the N_BARS described in BARS. */
+struct chart_item *
+barchart_create (const char *title, const char *ylabel, const struct slice *bars, int n_bars)
+{
+  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;
+  bar->ylabel = strdup (ylabel);
+  for (i = 0; i < n_bars; i++)
+    {
+      const struct slice *src = &bars[i];
+      struct slice *dst = &bar->bars[i];
+
+      ds_init_string (&dst->label, &src->label);
+      dst->magnitude = src->magnitude;
+      if (dst->magnitude > bar->largest)
+       bar->largest = dst->magnitude;
+    }
+  bar->n_bars = n_bars;
+  return &bar->chart_item;
+}
+
+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++)
+    {
+      struct slice *slice = &bar->bars[i];
+      ds_destroy (&slice->label);
+    }
+  free (bar->ylabel);
+  free (bar->bars);
+  free (bar);
+}
+
+const struct chart_item_class barchart_class =
+  {
+    barchart_destroy
+  };
diff --git a/src/output/charts/barchart.h b/src/output/charts/barchart.h
new file mode 100644 (file)
index 0000000..ea6c27e
--- /dev/null
@@ -0,0 +1,95 @@
+/* PSPP - a program for statistical analysis.
+   Copyright (C) 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
+   the Free Software Foundation, either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef BARCHART_H
+#define BARCHART_H
+
+#include "libpspp/str.h"
+#include "output/chart-item.h"
+
+struct barchart
+  {
+    struct chart_item chart_item;
+    struct slice *bars;
+    int n_bars;
+    double largest;
+    char *ylabel;
+  };
+
+struct chart_item *barchart_create (const char *title, const char *ylabel,
+                                    const struct slice *, int n_bars);
+\f
+/* This boilerplate for barchart, a subclass of chart_item, was
+   autogenerated by mk-class-boilerplate. */
+
+#include <assert.h>
+#include "libpspp/cast.h"
+
+extern const struct chart_item_class barchart_class;
+
+/* Returns true if SUPER is a barchart, otherwise false. */
+static inline bool
+is_barchart (const struct chart_item *super)
+{
+  return super->class == &barchart_class;
+}
+
+/* Returns SUPER converted to barchart.  SUPER must be a barchart, as
+   reported by is_barchart. */
+static inline struct barchart *
+to_barchart (const struct chart_item *super)
+{
+  assert (is_barchart (super));
+  return UP_CAST (super, struct barchart, chart_item);
+}
+
+/* Returns INSTANCE converted to chart_item. */
+static inline struct chart_item *
+barchart_super (const struct barchart *instance)
+{
+  return CONST_CAST (struct chart_item *, &instance->chart_item);
+}
+
+/* Increments INSTANCE's reference count and returns INSTANCE. */
+static inline struct barchart *
+barchart_ref (const struct barchart *instance)
+{
+  return to_barchart (chart_item_ref (&instance->chart_item));
+}
+
+/* Decrements INSTANCE's reference count, then destroys INSTANCE if
+   the reference count is now zero. */
+static inline void
+barchart_unref (struct barchart *instance)
+{
+  chart_item_unref (&instance->chart_item);
+}
+
+/* Returns true if INSTANCE's reference count is greater than 1,
+   false otherwise. */
+static inline bool
+barchart_is_shared (const struct barchart *instance)
+{
+  return chart_item_is_shared (&instance->chart_item);
+}
+
+static inline void
+barchart_submit (struct barchart *instance)
+{
+  chart_item_submit (&instance->chart_item);
+}
+\f
+#endif /* output/charts/barchart.h */
index 049fc281922acecfb022fcedaf2eaeda53badc00..0b951845d8157067c58d58d84ce9b443b2673c7a 100644 (file)
@@ -155,3 +155,40 @@ dnl The --testing-mode flag is important!!
 AT_CHECK([pspp --testing-mode -O format=csv histogram.sps], [0], [ignore])
 
 AT_CLEANUP
+
+
+AT_SETUP([FREQUENCIES 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.
+
+
+FREQUENCIES /VARIABLES=religion nationality /BARCHART /PIECHART. 
+])
+
+
+AT_CHECK([pspp  -O format=csv xxx.sps], [0], [ignore])
+
+AT_CLEANUP