Merge master into output branch.
authorBen Pfaff <blp@gnu.org>
Tue, 11 Aug 2009 21:18:26 +0000 (14:18 -0700)
committerBen Pfaff <blp@gnu.org>
Tue, 11 Aug 2009 21:18:26 +0000 (14:18 -0700)
32 files changed:
1  2 
configure.ac
src/data/subcase.c
src/data/variable.c
src/language/command.c
src/language/data-io/data-parser.c
src/language/data-io/list.q
src/language/data-io/print.c
src/language/dictionary/split-file.c
src/language/stats/aggregate.c
src/language/stats/crosstabs.q
src/language/stats/frequencies.q
src/language/stats/regression.q
src/language/stats/roc.c
src/language/stats/roc.h
src/language/stats/t-test.q
src/language/stats/wilcoxon.c
src/language/syntax-file.c
src/libpspp/str.c
src/output/automake.mk
src/output/cairo.c
src/output/chart-provider.h
src/output/chart.c
src/output/charts/cartesian.c
src/output/charts/cartesian.h
src/output/charts/plot-chart.c
src/output/charts/plot-chart.h
src/output/charts/roc-chart.c
src/output/charts/roc-chart.h
src/output/table.c
src/output/table.h
src/ui/gui/find-dialog.c
src/ui/gui/psppire-output-window.h

diff --cc configure.ac
Simple merge
Simple merge
Simple merge
Simple merge
Simple merge
index 5f1b3a99e5fe01c66ff45d1a1529639cc70cc78c,c3f9b0881b959fac98a3cffac628662ffe143299..614fa1271e71b5ed3180e2f4e98e1b454745b769
@@@ -662,122 -651,122 +662,123 @@@ determine_layout (struct ll_list *targe
  /* Writes case C to output. */
  static void
  list_case (const struct ccase *c, casenumber case_idx,
 -           const struct dataset *ds)
 +           const struct dataset *ds, struct ll_list *targets)
  {
    struct dictionary *dict = dataset_dict (ds);
 -  struct outp_driver *d;
++  const char *encoding = dict_get_encoding (dict);
 +  struct list_target *target;
  
 -  for (d = outp_drivers (NULL); d; d = outp_drivers (d))
 -    if (d->class->special == 0)
 -      {
 -      const struct list_ext *prc = d->prc;
 -      const int max_width = n_chars_width (d);
 -      int column;
 -
 -      if (!prc->header_rows)
 -        {
 -          ds_put_format(&line_buffer, "%8s: ",
 -                          var_get_name (cmd.v_variables[0]));
 -        }
 -
 -
 -      for (column = 0; column < cmd.n_variables; column++)
 -        {
 -          const struct variable *v = cmd.v_variables[column];
 -            const struct fmt_spec *print = var_get_print_format (v);
 -          int width;
 -
 -          if (prc->type == 0 && column >= prc->n_vertical)
 -              {
 -                int name_len = strlen (var_get_name (v));
 -                width = MAX (name_len, print->w);
 -              }
 -          else
 -            width = print->w;
 -
 -          if (width + ds_length(&line_buffer) > max_width &&
 -              ds_length(&line_buffer) != 0)
 -            {
 -              if (!n_lines_remaining (d))
 -                {
 -                  outp_eject_page (d);
 -                  write_header (d);
 -                }
 +  ll_for_each (target, struct list_target, ll, targets)
 +    {
 +      struct outp_driver *d = target->driver;
  
 -              write_line (d, ds_cstr (&line_buffer));
 -              ds_clear(&line_buffer);
 +      if (d->class->special == 0)
 +        {
 +          const int max_width = n_chars_width (d);
 +          int column;
  
 -              if (!prc->header_rows)
 -                  ds_put_format (&line_buffer, "%8s: ", var_get_name (v));
 -            }
 +          if (!target->header_rows)
 +            {
 +              ds_put_format(&line_buffer, "%8s: ",
 +                            var_get_name (cmd.v_variables[0]));
 +            }
  
 -          if (width > print->w)
 -              ds_put_char_multiple(&line_buffer, ' ', width - print->w);
  
 -            if (fmt_is_string (print->type)
 -                || dict_contains_var (dict, v))
 -            {
 -              char *s = data_out (case_data (c, v), dict_get_encoding (dict), print);
 -              ds_put_cstr (&line_buffer, s);
 -              free (s);
 -            }
 -            else
 -              {
 -              char *s;
 -                union value case_idx_value;
 -                case_idx_value.f = case_idx;
 -                s = data_out (&case_idx_value, dict_get_encoding (dict), print);
 -              ds_put_cstr (&line_buffer, s);
 -              free (s);
 -              }
 -
 -          ds_put_char (&line_buffer, ' ');
 -        }
 +          for (column = 0; column < cmd.n_variables; column++)
 +            {
 +              const struct variable *v = cmd.v_variables[column];
 +              const struct fmt_spec *print = var_get_print_format (v);
 +              int width;
++              char *s;
 +
 +              if (target->type == 0 && column >= target->n_vertical)
 +                {
 +                  int name_len = strlen (var_get_name (v));
 +                  width = MAX (name_len, print->w);
 +                }
 +              else
 +                width = print->w;
 +
 +              if (width + ds_length(&line_buffer) > max_width &&
 +                  ds_length(&line_buffer) != 0)
 +                {
 +                  if (!n_lines_remaining (d))
 +                    {
 +                      outp_eject_page (d);
 +                      write_header (target);
 +                    }
 +
 +                  write_line (d, ds_cstr (&line_buffer));
 +                  ds_clear(&line_buffer);
 +
 +                  if (!target->header_rows)
 +                    ds_put_format (&line_buffer, "%8s: ", var_get_name (v));
 +                }
 +
 +              if (width > print->w)
 +                ds_put_char_multiple(&line_buffer, ' ', width - print->w);
 +
-               if (fmt_is_string (print->type)
-                   || dict_contains_var (dict, v))
-                 {
-                   data_out (case_data (c, v), print,
-                             ds_put_uninit (&line_buffer, print->w));
-                 }
++              if (fmt_is_string (print->type) || dict_contains_var (dict, v))
++                s = data_out (case_data (c, v), encoding, print);
 +              else
 +                {
 +                  union value case_idx_value;
 +                  case_idx_value.f = case_idx;
-                   data_out (&case_idx_value, print,
-                             ds_put_uninit (&line_buffer,print->w));
++                  s = data_out (&case_idx_value, encoding, print);
 +                }
 +
++              ds_put_cstr (&line_buffer, s);
++              free (s);
 +              ds_put_char(&line_buffer, ' ');
 +            }
  
 -      if (!n_lines_remaining (d))
 -        {
 -          outp_eject_page (d);
 -          write_header (d);
 -        }
 +          if (!n_lines_remaining (d))
 +            {
 +              outp_eject_page (d);
 +              write_header (target);
 +            }
  
 -      write_line (d, ds_cstr (&line_buffer));
 -      ds_clear(&line_buffer);
 -      }
 -    else if (d->class == &html_class)
 -      {
 -      struct html_driver_ext *x = d->ext;
 -      int column;
 +          write_line (d, ds_cstr (&line_buffer));
 +          ds_clear(&line_buffer);
 +        }
 +      else if (d->class == &html_class)
 +        {
 +          struct html_driver_ext *x = d->ext;
 +          int column;
  
 -      fputs ("  <TR>\n", x->file);
 +          fputs ("  <TR>\n", x->file);
  
 -      for (column = 0; column < cmd.n_variables; column++)
 -        {
 -          const struct variable *v = cmd.v_variables[column];
 -            const struct fmt_spec *print = var_get_print_format (v);
 -          char *s = NULL;
 -
 -            if (fmt_is_string (print->type)
 -                || dict_contains_var (dict, v))
 -            s = data_out (case_data (c, v), dict_get_encoding (dict), print);
 -            else
 -              {
 -                union value case_idx_value;
 -                case_idx_value.f = case_idx;
 -                s = data_out (&case_idx_value, dict_get_encoding (dict), print);
 -              }
 -
 -            fputs ("    <TD>", x->file);
 -            html_put_cell_contents (d, TAB_FIX, ss_buffer (s, print->w));
 -          free (s);
 -            fputs ("</TD>\n", x->file);
 -        }
 +          for (column = 0; column < cmd.n_variables; column++)
 +            {
 +              const struct variable *v = cmd.v_variables[column];
 +              const struct fmt_spec *print = var_get_print_format (v);
-               char buf[256];
++              char *s;
 +
 +              if (fmt_is_string (print->type)
 +                  || dict_contains_var (dict, v))
-                 data_out (case_data (c, v), print, buf);
++                s = data_out (case_data (c, v), encoding, print);
 +              else
 +                {
 +                  union value case_idx_value;
 +                  case_idx_value.f = case_idx;
-                   data_out (&case_idx_value, print, buf);
++                  s = data_out (&case_idx_value, encoding, print);
 +                }
 +
 +              fputs ("    <TD>", x->file);
-               html_put_cell_contents (d, TAB_FIX, ss_buffer (buf, print->w));
++              html_put_cell_contents (d, TAB_FIX, ss_cstr (s));
 +              fputs ("</TD>\n", x->file);
++
++              free (s);
 +            }
  
 -      fputs ("  </TR>\n", x->file);
 -      }
 -    else
 -      NOT_REACHED ();
 +          fputs ("  </TR>\n", x->file);
 +        }
 +      else
 +        NOT_REACHED ();
 +    }
  }
  
 +
  /*
     Local Variables:
     mode: c
Simple merge
index 5d3650e3b3de569bd6e3eee5e1ff30d3ffddd267,5d2b42d758eed2d1e305d05046d59cf0aa290877..d2f59ccc813fe8661435bb03bee0cbf1a01caa20
@@@ -94,11 -94,11 +94,12 @@@ output_split_file_values (const struct 
  
        tab_text_format (t, 0, i + 1, TAB_LEFT, "%s", var_get_name (v));
  
-       data_out (case_data (c, v), print, temp_buf);
-       temp_buf[print->w] = 0;
+       s = data_out (case_data (c, v), dict_get_encoding (dict), print);
 +
-       tab_text_format (t, 1, i + 1, 0, "%.*s", print->w, temp_buf);
+       tab_text_format (t, 1, i + 1, 0, "%.*s", print->w, s);
  
+       free (s);
+       
        val_lab = var_lookup_value_label (v, case_data (c, v));
        if (val_lab)
        tab_text (t, 2, i + 1, TAB_LEFT, val_lab);
Simple merge
index 7db55a2c24b2278072acad0fb26d00482a3db24b,99fa41d9c056a039ef9d92fdc9090758539c9d15..5695ed6478b64490095ea0c8abde83873fb38030
@@@ -1526,22 -1513,26 +1530,21 @@@ table_value_missing (struct crosstabs_p
                       struct tab_table *table, int c, int r, unsigned char opt,
                     const union value *v, const struct variable *var)
  {
 -  struct substring s;
 -  const struct fmt_spec *print = var_get_print_format (var);
 -
    const char *label = var_lookup_value_label (var, v);
 -  if (label)
 -    {
 -      tab_text (table, c, r, TAB_LEFT, label);
 -      return;
 -    }
 -
 -  s = ss_cstr (data_out_pool (v, dict_get_encoding (proc->dict), print,
 -                           table->container));
 -  if (proc->exclude == MV_NEVER && var_is_num_missing (var, v->f, MV_USER))
 -    s.string[s.length++] = 'M';
 -  while (s.length && *s.string == ' ')
 +  if (label != NULL)
 +    tab_text (table, c, r, TAB_LEFT, label);
 +  else
      {
 -      s.length--;
 -      s.string++;
 +      const struct fmt_spec *print = var_get_print_format (var);
 +      if (proc->exclude == MV_NEVER && var_is_value_missing (var, v, MV_USER))
 +        {
-           char *s = xmalloc (print->w + 2);
-           strcpy (&s[print->w], "M");
-           tab_text (table, c, r, opt, s + strspn (s, " "));
++          char *s = data_out (v, dict_get_encoding (proc->dict), print);
++          tab_text_format (table, c, r, opt, "%sM", s + strspn (s, " "));
 +          free (s);
 +        }
 +      else
-         tab_value (table, c, r, opt, v, print);
++        tab_value (table, c, r, opt, v, proc->dict, print);
      }
 -  tab_raw (table, c, r, opt, &s);
  }
  
  /* Draws a line across TABLE at the current row to indicate the most
@@@ -1570,18 -1561,22 +1573,22 @@@ format_cell_entry (struct tab_table *ta
  {
    const struct fmt_spec f = {FMT_F, 10, 1};
    union value v;
-   int len = 10;
-   char s[16];
 -  struct substring s;
++  char suffixes[3];
++  int suffix_len;
++  char *s;
  
    v.f = value;
-   data_out (&v, &f, s);
 -  s = ss_cstr (data_out_pool (&v, dict_get_encoding (dict), &f, table->container));
++  s = data_out (&v, dict_get_encoding (dict), &f);
 -  while (*s.string == ' ')
 -    {
 -      s.length--;
 -      s.string++;
 -    }
++  suffix_len = 0;
    if (suffix != 0)
-     s[len++] = suffix;
 -    s.string[s.length++] = suffix;
++    suffixes[suffix_len++] = suffix;
    if (mark_missing)
-     s[len++] = 'M';
-   s[len] = '\0';
 -    s.string[s.length++] = 'M';
++    suffixes[suffix_len++] = 'M';
++  suffixes[suffix_len] = '\0';
  
-   tab_text (table, c, r, TAB_RIGHT, s + strspn (s, " "));
 -  tab_raw (table, c, r, TAB_RIGHT, &s);
++  tab_text_format (table, c, r, TAB_RIGHT, "%s%s",
++                   s + strspn (s, " "), suffixes);
  }
  
  /* Displays the crosstabulation table. */
Simple merge
Simple merge
index 0000000000000000000000000000000000000000,1d61a55c57a3cad25c834c204c3571632d9df778..1d21a4f6a1221ed6b83f3c2bbb8d4e25c457c95a
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,1227 +1,1179 @@@
 -#include <output/charts/plot-chart.h>
 -#include <output/charts/cartesian.h>
+ /* PSPP - a program for statistical analysis.
+    Copyright (C) 2009 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 <language/stats/roc.h>
++
+ #include <data/procedure.h>
+ #include <language/lexer/variable-parser.h>
+ #include <language/lexer/value-parser.h>
+ #include <language/command.h>
+ #include <language/lexer/lexer.h>
+ #include <data/casegrouper.h>
+ #include <data/casereader.h>
+ #include <data/casewriter.h>
+ #include <data/dictionary.h>
+ #include <data/format.h>
+ #include <math/sort.h>
+ #include <data/subcase.h>
+ #include <libpspp/misc.h>
+ #include <gsl/gsl_cdf.h>
+ #include <output/table.h>
 -#define CUTPOINT 0
 -#define TP 1
 -#define FN 2
 -#define TN 3
 -#define FP 4
 -
 -
++#include <output/chart.h>
++#include <output/charts/roc-chart.h>
+ #include "gettext.h"
+ #define _(msgid) gettext (msgid)
+ #define N_(msgid) msgid
+ struct cmd_roc
+ {
+   size_t n_vars;
+   const struct variable **vars;
+   const struct dictionary *dict;
+   const struct variable *state_var ;
+   union value state_value;
+   /* Plot the roc curve */
+   bool curve;
+   /* Plot the reference line */
+   bool reference;
+   double ci;
+   bool print_coords;
+   bool print_se;
+   bool bi_neg_exp; /* True iff the bi-negative exponential critieria
+                     should be used */
+   enum mv_class exclude;
+   bool invert ; /* True iff a smaller test result variable indicates
+                  a positive result */
+   double pos;
+   double neg;
+   double pos_weighted;
+   double neg_weighted;
+ };
+ static int run_roc (struct dataset *ds, struct cmd_roc *roc);
+ int
+ cmd_roc (struct lexer *lexer, struct dataset *ds)
+ {
+   struct cmd_roc roc ;
+   const struct dictionary *dict = dataset_dict (ds);
+   roc.vars = NULL;
+   roc.n_vars = 0;
+   roc.print_se = false;
+   roc.print_coords = false;
+   roc.exclude = MV_ANY;
+   roc.curve = true;
+   roc.reference = false;
+   roc.ci = 95;
+   roc.bi_neg_exp = false;
+   roc.invert = false;
+   roc.pos = roc.pos_weighted = 0;
+   roc.neg = roc.neg_weighted = 0;
+   roc.dict = dataset_dict (ds);
+   if (!parse_variables_const (lexer, dict, &roc.vars, &roc.n_vars,
+                             PV_APPEND | PV_NO_DUPLICATE | PV_NUMERIC))
+     goto error;
+   if ( ! lex_force_match (lexer, T_BY))
+     {
+       goto error;
+     }
+   roc.state_var = parse_variable (lexer, dict);
+   if ( !lex_force_match (lexer, '('))
+     {
+       goto error;
+     }
+   parse_value (lexer, &roc.state_value, var_get_width (roc.state_var));
+   if ( !lex_force_match (lexer, ')'))
+     {
+       goto error;
+     }
+   while (lex_token (lexer) != '.')
+     {
+       lex_match (lexer, '/');
+       if (lex_match_id (lexer, "MISSING"))
+         {
+           lex_match (lexer, '=');
+           while (lex_token (lexer) != '.' && lex_token (lexer) != '/')
+             {
+             if (lex_match_id (lexer, "INCLUDE"))
+               {
+                 roc.exclude = MV_SYSTEM;
+               }
+             else if (lex_match_id (lexer, "EXCLUDE"))
+               {
+                 roc.exclude = MV_ANY;
+               }
+             else
+               {
+                   lex_error (lexer, NULL);
+                 goto error;
+               }
+           }
+       }
+       else if (lex_match_id (lexer, "PLOT"))
+       {
+         lex_match (lexer, '=');
+         if (lex_match_id (lexer, "CURVE"))
+           {
+             roc.curve = true;
+             if (lex_match (lexer, '('))
+               {
+                 roc.reference = true;
+                 lex_force_match_id (lexer, "REFERENCE");
+                 lex_force_match (lexer, ')');
+               }
+           }
+         else if (lex_match_id (lexer, "NONE"))
+           {
+             roc.curve = false;
+           }
+         else
+           {
+             lex_error (lexer, NULL);
+             goto error;
+           }
+       }
+       else if (lex_match_id (lexer, "PRINT"))
+       {
+         lex_match (lexer, '=');
+           while (lex_token (lexer) != '.' && lex_token (lexer) != '/')
+           {
+             if (lex_match_id (lexer, "SE"))
+               {
+                 roc.print_se = true;
+               }
+             else if (lex_match_id (lexer, "COORDINATES"))
+               {
+                 roc.print_coords = true;
+               }
+             else
+               {
+                 lex_error (lexer, NULL);
+                 goto error;
+               }
+           }
+       }
+       else if (lex_match_id (lexer, "CRITERIA"))
+       {
+         lex_match (lexer, '=');
+           while (lex_token (lexer) != '.' && lex_token (lexer) != '/')
+           {
+             if (lex_match_id (lexer, "CUTOFF"))
+               {
+                 lex_force_match (lexer, '(');
+                 if (lex_match_id (lexer, "INCLUDE"))
+                   {
+                     roc.exclude = MV_SYSTEM;
+                   }
+                 else if (lex_match_id (lexer, "EXCLUDE"))
+                   {
+                     roc.exclude = MV_USER | MV_SYSTEM;
+                   }
+                 else
+                   {
+                     lex_error (lexer, NULL);
+                     goto error;
+                   }
+                 lex_force_match (lexer, ')');
+               }
+             else if (lex_match_id (lexer, "TESTPOS"))
+               {
+                 lex_force_match (lexer, '(');
+                 if (lex_match_id (lexer, "LARGE"))
+                   {
+                     roc.invert = false;
+                   }
+                 else if (lex_match_id (lexer, "SMALL"))
+                   {
+                     roc.invert = true;
+                   }
+                 else
+                   {
+                     lex_error (lexer, NULL);
+                     goto error;
+                   }
+                 lex_force_match (lexer, ')');
+               }
+             else if (lex_match_id (lexer, "CI"))
+               {
+                 lex_force_match (lexer, '(');
+                 lex_force_num (lexer);
+                 roc.ci = lex_number (lexer);
+                 lex_get (lexer);
+                 lex_force_match (lexer, ')');
+               }
+             else if (lex_match_id (lexer, "DISTRIBUTION"))
+               {
+                 lex_force_match (lexer, '(');
+                 if (lex_match_id (lexer, "FREE"))
+                   {
+                     roc.bi_neg_exp = false;
+                   }
+                 else if (lex_match_id (lexer, "NEGEXPO"))
+                   {
+                     roc.bi_neg_exp = true;
+                   }
+                 else
+                   {
+                     lex_error (lexer, NULL);
+                     goto error;
+                   }
+                 lex_force_match (lexer, ')');
+               }
+             else
+               {
+                 lex_error (lexer, NULL);
+                 goto error;
+               }
+           }
+       }
+       else
+       {
+         lex_error (lexer, NULL);
+         break;
+       }
+     }
+   if ( ! run_roc (ds, &roc)) 
+     goto error;
+   free (roc.vars);
+   return CMD_SUCCESS;
+  error:
+   free (roc.vars);
+   return CMD_FAILURE;
+ }
+ static void
+ do_roc (struct cmd_roc *roc, struct casereader *group, struct dictionary *dict);
+ static int
+ run_roc (struct dataset *ds, struct cmd_roc *roc)
+ {
+   struct dictionary *dict = dataset_dict (ds);
+   bool ok;
+   struct casereader *group;
+   struct casegrouper *grouper = casegrouper_create_splits (proc_open (ds), dict);
+   while (casegrouper_get_next_group (grouper, &group))
+     {
+       do_roc (roc, group, dataset_dict (ds));
+     }
+   ok = casegrouper_destroy (grouper);
+   ok = proc_commit (ds) && ok;
+   return ok;
+ }
+ #if 0
+ static void
+ dump_casereader (struct casereader *reader)
+ {
+   struct ccase *c;
+   struct casereader *r = casereader_clone (reader);
+   for ( ; (c = casereader_read (r) ); case_unref (c))
+     {
+       int i;
+       for (i = 0 ; i < case_get_value_cnt (c); ++i)
+       {
+         printf ("%g ", case_data_idx (c, i)->f);
+       }
+       printf ("\n");
+     }
+   casereader_destroy (r);
+ }
+ #endif
+ /* 
+    Return true iff the state variable indicates that C has positive actual state.
+    As a side effect, this function also accumulates the roc->{pos,neg} and 
+    roc->{pos,neg}_weighted counts.
+  */
+ static bool
+ match_positives (const struct ccase *c, void *aux)
+ {
+   struct cmd_roc *roc = aux;
+   const struct variable *wv = dict_get_weight (roc->dict);
+   const double weight = wv ? case_data (c, wv)->f : 1.0;
+   const bool positive =
+   ( 0 == value_compare_3way (case_data (c, roc->state_var), &roc->state_value,
+     var_get_width (roc->state_var)));
+   if ( positive )
+     {
+       roc->pos++;
+       roc->pos_weighted += weight;
+     }
+   else
+     {
+       roc->neg++;
+       roc->neg_weighted += weight;
+     }
+   return positive;
+ }
+ #define VALUE  0
+ #define N_EQ   1
+ #define N_PRED 2
+ /* Some intermediate state for calculating the cutpoints and the 
+    standard error values */
+ struct roc_state
+ {
+   double auc;  /* Area under the curve */
+   double n1;  /* total weight of positives */
+   double n2;  /* total weight of negatives */
+   /* intermediates for standard error */
+   double q1hat; 
+   double q2hat;
+   /* intermediates for cutpoints */
+   struct casewriter *cutpoint_wtr;
+   struct casereader *cutpoint_rdr;
+   double prev_result;
+   double min;
+   double max;
+ };
 -      const double cp = case_data_idx (cpc, CUTPOINT)->f;
+ /* 
+    Return a new casereader based upon CUTPOINT_RDR.
+    The number of "positive" cases are placed into
+    the position TRUE_INDEX, and the number of "negative" cases
+    into FALSE_INDEX.
+    POS_COND and RESULT determine the semantics of what is 
+    "positive".
+    WEIGHT is the value of a single count.
+  */
+ static struct casereader *
+ accumulate_counts (struct casereader *cutpoint_rdr, 
+                  double result, double weight, 
+                  bool (*pos_cond) (double, double),
+                  int true_index, int false_index)
+ {
+   const struct caseproto *proto = casereader_get_proto (cutpoint_rdr);
+   struct casewriter *w =
+     autopaging_writer_create (proto);
+   struct casereader *r = casereader_clone (cutpoint_rdr);
+   struct ccase *cpc;
+   double prev_cp = SYSMIS;
+   for ( ; (cpc = casereader_read (r) ); case_unref (cpc))
+     {
+       struct ccase *new_case;
 -                      TP, FN);
++      const double cp = case_data_idx (cpc, ROC_CUTPOINT)->f;
+       assert (cp != SYSMIS);
+       /* We don't want duplicates here */
+       if ( cp == prev_cp )
+       continue;
+       new_case = case_clone (cpc);
+       if ( pos_cond (result, cp))
+       case_data_rw_idx (new_case, true_index)->f += weight;
+       else
+       case_data_rw_idx (new_case, false_index)->f += weight;
+       prev_cp = cp;
+       casewriter_write (w, new_case);
+     }
+   casereader_destroy (r);
+   return casewriter_make_reader (w);
+ }
+ static void output_roc (struct roc_state *rs, const struct cmd_roc *roc);
+ /*
+   This function does 3 things:
+   1. Counts the number of cases which are equal to every other case in READER,
+   and those cases for which the relationship between it and every other case
+   satifies PRED (normally either > or <).  VAR is variable defining a case's value
+   for this purpose.
+   2. Counts the number of true and false cases in reader, and populates
+   CUTPOINT_RDR accordingly.  TRUE_INDEX and FALSE_INDEX are the indices
+   which receive these values.  POS_COND is the condition defining true
+   and false.
+   
+   3. CC is filled with the cumulative weight of all cases of READER.
+ */
+ static struct casereader *
+ process_group (const struct variable *var, struct casereader *reader,
+              bool (*pred) (double, double),
+              const struct dictionary *dict,
+              double *cc,
+              struct casereader **cutpoint_rdr, 
+              bool (*pos_cond) (double, double),
+              int true_index,
+              int false_index)
+ {
+   const struct variable *w = dict_get_weight (dict);
+   struct casereader *r1 =
+     casereader_create_distinct (sort_execute_1var (reader, var), var, w);
+   const int weight_idx  = w ? var_get_case_index (w) :
+     caseproto_get_n_widths (casereader_get_proto (r1)) - 1;
+   
+   struct ccase *c1;
+   struct casereader *rclone = casereader_clone (r1);
+   struct casewriter *wtr;
+   struct caseproto *proto = caseproto_create ();
+   proto = caseproto_add_width (proto, 0);
+   proto = caseproto_add_width (proto, 0);
+   proto = caseproto_add_width (proto, 0);
+   wtr = autopaging_writer_create (proto);  
+   *cc = 0;
+   for ( ; (c1 = casereader_read (r1) ); case_unref (c1))
+     {
+       struct ccase *new_case = case_create (proto);
+       struct ccase *c2;
+       struct casereader *r2 = casereader_clone (rclone);
+       const double weight1 = case_data_idx (c1, weight_idx)->f;
+       const double d1 = case_data (c1, var)->f;
+       double n_eq = 0.0;
+       double n_pred = 0.0;
+       *cutpoint_rdr = accumulate_counts (*cutpoint_rdr, d1, weight1,
+                                        pos_cond,
+                                        true_index, false_index);
+       *cc += weight1;
+       for ( ; (c2 = casereader_read (r2) ); case_unref (c2))
+       {
+         const double d2 = case_data (c2, var)->f;
+         const double weight2 = case_data_idx (c2, weight_idx)->f;
+         if ( d1 == d2 )
+           {
+             n_eq += weight2;
+             continue;
+           }
+         else  if ( pred (d2, d1))
+           {
+             n_pred += weight2;
+           }
+       }
+       case_data_rw_idx (new_case, VALUE)->f = d1;
+       case_data_rw_idx (new_case, N_EQ)->f = n_eq;
+       case_data_rw_idx (new_case, N_PRED)->f = n_pred;
+       casewriter_write (wtr, new_case);
+       casereader_destroy (r2);
+     }
+   casereader_destroy (r1);
+   casereader_destroy (rclone);
+   return casewriter_make_reader (wtr);
+ }
+ /* Some more indeces into case data */
+ #define N_POS_EQ 1  /* number of positive cases with values equal to n */
+ #define N_POS_GT 2  /* number of postive cases with values greater than n */
+ #define N_NEG_EQ 3  /* number of negative cases with values equal to n */
+ #define N_NEG_LT 4  /* number of negative cases with values less than n */
+ static bool
+ gt (double d1, double d2)
+ {
+   return d1 > d2;
+ }
+ static bool
+ ge (double d1, double d2)
+ {
+   return d1 > d2;
+ }
+ static bool
+ lt (double d1, double d2)
+ {
+   return d1 < d2;
+ }
+ /*
+   Return a casereader with width 3,
+   populated with cases based upon READER.
+   The cases will have the values:
+   (N, number of cases equal to N, number of cases greater than N)
+   As a side effect, update RS->n1 with the number of positive cases.
+ */
+ static struct casereader *
+ process_positive_group (const struct variable *var, struct casereader *reader,
+                       const struct dictionary *dict,
+                       struct roc_state *rs)
+ {
+   return process_group (var, reader, gt, dict, &rs->n1,
+                       &rs->cutpoint_rdr,
+                       ge,
 -                      TN, FP);
++                      ROC_TP, ROC_FN);
+ }
+ /*
+   Return a casereader with width 3,
+   populated with cases based upon READER.
+   The cases will have the values:
+   (N, number of cases equal to N, number of cases less than N)
+   As a side effect, update RS->n2 with the number of negative cases.
+ */
+ static struct casereader *
+ process_negative_group (const struct variable *var, struct casereader *reader,
+                       const struct dictionary *dict,
+                       struct roc_state *rs)
+ {
+   return process_group (var, reader, lt, dict, &rs->n2,
+                       &rs->cutpoint_rdr,
+                       lt,
 -  case_data_rw_idx (cc, CUTPOINT)->f = cutpoint;
 -  case_data_rw_idx (cc, TP)->f = 0;
 -  case_data_rw_idx (cc, FN)->f = 0;
 -  case_data_rw_idx (cc, TN)->f = 0;
 -  case_data_rw_idx (cc, FP)->f = 0;
++                      ROC_TN, ROC_FP);
+ }
+ static void
+ append_cutpoint (struct casewriter *writer, double cutpoint)
+ {
+   struct ccase *cc = case_create (casewriter_get_proto (writer));
 -   be created with width 5, ready to take the values (cutpoint, TP, FN, TN, FP), and the
++  case_data_rw_idx (cc, ROC_CUTPOINT)->f = cutpoint;
++  case_data_rw_idx (cc, ROC_TP)->f = 0;
++  case_data_rw_idx (cc, ROC_FN)->f = 0;
++  case_data_rw_idx (cc, ROC_TN)->f = 0;
++  case_data_rw_idx (cc, ROC_FP)->f = 0;
+   casewriter_write (writer, cc);
+ }
+ /* 
+    Create and initialise the rs[x].cutpoint_rdr casereaders.  That is, the readers will
 -   However on exit from this function, only CUTPOINT entries will be set to their final
++   be created with width 5, ready to take the values (cutpoint, ROC_TP, ROC_FN, ROC_TN, ROC_FP), and the
+    reader will be populated with its final number of cases.
 -  subcase_init (&ordering, CUTPOINT, 0, SC_ASCEND);
++   However on exit from this function, only ROC_CUTPOINT entries will be set to their final
+    value.  The other entries will be initialised to zero.
+ */
+ static void
+ prepare_cutpoints (struct cmd_roc *roc, struct roc_state *rs, struct casereader *input)
+ {
+   int i;
+   struct casereader *r = casereader_clone (input);
+   struct ccase *c;
+   struct caseproto *proto = caseproto_create ();
+   struct subcase ordering;
 -  proto = caseproto_add_width (proto, 0); /* TP */
 -  proto = caseproto_add_width (proto, 0); /* FN */
 -  proto = caseproto_add_width (proto, 0); /* TN */
 -  proto = caseproto_add_width (proto, 0); /* FP */
++  subcase_init (&ordering, ROC_CUTPOINT, 0, SC_ASCEND);
+   proto = caseproto_add_width (proto, 0); /* cutpoint */
 -  struct tab_table *tbl = tab_create (n_cols, n_rows, 0);
++  proto = caseproto_add_width (proto, 0); /* ROC_TP */
++  proto = caseproto_add_width (proto, 0); /* ROC_FN */
++  proto = caseproto_add_width (proto, 0); /* ROC_TN */
++  proto = caseproto_add_width (proto, 0); /* ROC_FP */
+   for (i = 0 ; i < roc->n_vars; ++i)
+     {
+       rs[i].cutpoint_wtr = sort_create_writer (&ordering, proto);
+       rs[i].prev_result = SYSMIS;
+       rs[i].max = -DBL_MAX;
+       rs[i].min = DBL_MAX;
+     }
+   for (; (c = casereader_read (r)) != NULL; case_unref (c))
+     {
+       for (i = 0 ; i < roc->n_vars; ++i)
+       {
+         const union value *v = case_data (c, roc->vars[i]); 
+         const double result = v->f;
+         if ( mv_is_value_missing (var_get_missing_values (roc->vars[i]), v, roc->exclude))
+           continue;
+         minimize (&rs[i].min, result);
+         maximize (&rs[i].max, result);
+         if ( rs[i].prev_result != SYSMIS && rs[i].prev_result != result )
+           {
+             const double mean = (result + rs[i].prev_result ) / 2.0;
+             append_cutpoint (rs[i].cutpoint_wtr, mean);
+           }
+         rs[i].prev_result = result;
+       }
+     }
+   casereader_destroy (r);
+   /* Append the min and max cutpoints */
+   for (i = 0 ; i < roc->n_vars; ++i)
+     {
+       append_cutpoint (rs[i].cutpoint_wtr, rs[i].min - 1);
+       append_cutpoint (rs[i].cutpoint_wtr, rs[i].max + 1);
+       rs[i].cutpoint_rdr = casewriter_make_reader (rs[i].cutpoint_wtr);
+     }
+ }
+ static void
+ do_roc (struct cmd_roc *roc, struct casereader *reader, struct dictionary *dict)
+ {
+   int i;
+   struct roc_state *rs = xcalloc (roc->n_vars, sizeof *rs);
+   struct casereader *negatives = NULL;
+   struct casereader *positives = NULL;
+   struct caseproto *n_proto = caseproto_create ();
+   struct subcase up_ordering;
+   struct subcase down_ordering;
+   struct casewriter *neg_wtr = NULL;
+   struct casereader *input = casereader_create_filter_missing (reader,
+                                                              roc->vars, roc->n_vars,
+                                                              roc->exclude,
+                                                              NULL,
+                                                              NULL);
+   input = casereader_create_filter_missing (input,
+                                           &roc->state_var, 1,
+                                           roc->exclude,
+                                           NULL,
+                                           NULL);
+   neg_wtr = autopaging_writer_create (casereader_get_proto (input));
+   prepare_cutpoints (roc, rs, input);
+   /* Separate the positive actual state cases from the negative ones */
+   positives = 
+     casereader_create_filter_func (input,
+                                  match_positives,
+                                  NULL,
+                                  roc,
+                                  neg_wtr);
+   n_proto = caseproto_create ();
+       
+   n_proto = caseproto_add_width (n_proto, 0);
+   n_proto = caseproto_add_width (n_proto, 0);
+   n_proto = caseproto_add_width (n_proto, 0);
+   n_proto = caseproto_add_width (n_proto, 0);
+   n_proto = caseproto_add_width (n_proto, 0);
+   subcase_init (&up_ordering, VALUE, 0, SC_ASCEND);
+   subcase_init (&down_ordering, VALUE, 0, SC_DESCEND);
+   for (i = 0 ; i < roc->n_vars; ++i)
+     {
+       struct casewriter *w = NULL;
+       struct casereader *r = NULL;
+       struct ccase *c;
+       struct ccase *cpos;
+       struct casereader *n_neg ;
+       const struct variable *var = roc->vars[i];
+       struct casereader *neg ;
+       struct casereader *pos = casereader_clone (positives);
+       struct casereader *n_pos =
+       process_positive_group (var, pos, dict, &rs[i]);
+       if ( negatives == NULL)
+       {
+         negatives = casewriter_make_reader (neg_wtr);
+       }
+       neg = casereader_clone (negatives);
+       n_neg = process_negative_group (var, neg, dict, &rs[i]);
+       /* Merge the n_pos and n_neg casereaders */
+       w = sort_create_writer (&up_ordering, n_proto);
+       for ( ; (cpos = casereader_read (n_pos) ); case_unref (cpos))
+       {
+         struct ccase *pos_case = case_create (n_proto);
+         struct ccase *cneg;
+         const double jpos = case_data_idx (cpos, VALUE)->f;
+         while ((cneg = casereader_read (n_neg)))
+           {
+             struct ccase *nc = case_create (n_proto);
+             const double jneg = case_data_idx (cneg, VALUE)->f;
+             case_data_rw_idx (nc, VALUE)->f = jneg;
+             case_data_rw_idx (nc, N_POS_EQ)->f = 0;
+             case_data_rw_idx (nc, N_POS_GT)->f = SYSMIS;
+             *case_data_rw_idx (nc, N_NEG_EQ) = *case_data_idx (cneg, N_EQ);
+             *case_data_rw_idx (nc, N_NEG_LT) = *case_data_idx (cneg, N_PRED);
+             casewriter_write (w, nc);
+             case_unref (cneg);
+             if ( jneg > jpos)
+               break;
+           }
+         case_data_rw_idx (pos_case, VALUE)->f = jpos;
+         *case_data_rw_idx (pos_case, N_POS_EQ) = *case_data_idx (cpos, N_EQ);
+         *case_data_rw_idx (pos_case, N_POS_GT) = *case_data_idx (cpos, N_PRED);
+         case_data_rw_idx (pos_case, N_NEG_EQ)->f = 0;
+         case_data_rw_idx (pos_case, N_NEG_LT)->f = SYSMIS;
+         casewriter_write (w, pos_case);
+       }
+ /* These aren't used anymore */
+ #undef N_EQ
+ #undef N_PRED
+       r = casewriter_make_reader (w);
+       /* Propagate the N_POS_GT values from the positive cases
+        to the negative ones */
+       {
+       double prev_pos_gt = rs[i].n1;
+       w = sort_create_writer (&down_ordering, n_proto);
+       for ( ; (c = casereader_read (r) ); case_unref (c))
+         {
+           double n_pos_gt = case_data_idx (c, N_POS_GT)->f;
+           struct ccase *nc = case_clone (c);
+           if ( n_pos_gt == SYSMIS)
+             {
+               n_pos_gt = prev_pos_gt;
+               case_data_rw_idx (nc, N_POS_GT)->f = n_pos_gt;
+             }
+           
+           casewriter_write (w, nc);
+           prev_pos_gt = n_pos_gt;
+         }
+       r = casewriter_make_reader (w);
+       }
+       /* Propagate the N_NEG_LT values from the negative cases
+        to the positive ones */
+       {
+       double prev_neg_lt = rs[i].n2;
+       w = sort_create_writer (&up_ordering, n_proto);
+       for ( ; (c = casereader_read (r) ); case_unref (c))
+         {
+           double n_neg_lt = case_data_idx (c, N_NEG_LT)->f;
+           struct ccase *nc = case_clone (c);
+           if ( n_neg_lt == SYSMIS)
+             {
+               n_neg_lt = prev_neg_lt;
+               case_data_rw_idx (nc, N_NEG_LT)->f = n_neg_lt;
+             }
+           
+           casewriter_write (w, nc);
+           prev_neg_lt = n_neg_lt;
+         }
+       r = casewriter_make_reader (w);
+       }
+       {
+       struct ccase *prev_case = NULL;
+       for ( ; (c = casereader_read (r) ); case_unref (c))
+         {
+           const struct ccase *next_case = casereader_peek (r, 0);
+           const double j = case_data_idx (c, VALUE)->f;
+           double n_pos_eq = case_data_idx (c, N_POS_EQ)->f;
+           double n_pos_gt = case_data_idx (c, N_POS_GT)->f;
+           double n_neg_eq = case_data_idx (c, N_NEG_EQ)->f;
+           double n_neg_lt = case_data_idx (c, N_NEG_LT)->f;
+           if ( prev_case && j == case_data_idx (prev_case, VALUE)->f)
+             {
+               if ( 0 ==  case_data_idx (c, N_POS_EQ)->f)
+                 {
+                   n_pos_eq = case_data_idx (prev_case, N_POS_EQ)->f;
+                   n_pos_gt = case_data_idx (prev_case, N_POS_GT)->f;
+                 }
+               if ( 0 ==  case_data_idx (c, N_NEG_EQ)->f)
+                 {
+                   n_neg_eq = case_data_idx (prev_case, N_NEG_EQ)->f;
+                   n_neg_lt = case_data_idx (prev_case, N_NEG_LT)->f;
+                 }
+             }
+           if ( NULL == next_case || j != case_data_idx (next_case, VALUE)->f)
+             {
+               rs[i].auc += n_pos_gt * n_neg_eq + (n_pos_eq * n_neg_eq) / 2.0;
+               rs[i].q1hat +=
+                 n_neg_eq * ( pow2 (n_pos_gt) + n_pos_gt * n_pos_eq + pow2 (n_pos_eq) / 3.0);
+               rs[i].q2hat +=
+                 n_pos_eq * ( pow2 (n_neg_lt) + n_neg_lt * n_neg_eq + pow2 (n_neg_eq) / 3.0);
+             }
+           case_unref (prev_case);
+           prev_case = case_clone (c);
+         }
+       rs[i].auc /=  rs[i].n1 * rs[i].n2; 
+       if ( roc->invert ) 
+         rs[i].auc = 1 - rs[i].auc;
+       if ( roc->bi_neg_exp )
+         {
+           rs[i].q1hat = rs[i].auc / ( 2 - rs[i].auc);
+           rs[i].q2hat = 2 * pow2 (rs[i].auc) / ( 1 + rs[i].auc);
+         }
+       else
+         {
+           rs[i].q1hat /= rs[i].n2 * pow2 (rs[i].n1);
+           rs[i].q2hat /= rs[i].n1 * pow2 (rs[i].n2);
+         }
+       }
+     }
+   casereader_destroy (positives);
+   casereader_destroy (negatives);
+   output_roc (rs, roc);
+   free (rs);
+ }
+ static void
+ show_auc  (struct roc_state *rs, const struct cmd_roc *roc)
+ {
+   int i;
+   const int n_fields = roc->print_se ? 5 : 1;
+   const int n_cols = roc->n_vars > 1 ? n_fields + 1: n_fields;
+   const int n_rows = 2 + roc->n_vars;
 -  tab_dim (tbl, tab_natural_dimensions, NULL);
++  struct tab_table *tbl = tab_create (n_cols, n_rows);
+   if ( roc->n_vars > 1)
+     tab_title (tbl, _("Area Under the Curve"));
+   else
+     tab_title (tbl, _("Area Under the Curve (%s)"), var_to_string (roc->vars[0]));
+   tab_headers (tbl, n_cols - n_fields, 0, 1, 0);
 -  struct tab_table *tbl = tab_create (n_cols, n_rows, 0);
++  tab_dim (tbl, tab_natural_dimensions, NULL, NULL);
+   tab_text (tbl, n_cols - n_fields, 1, TAT_TITLE, _("Area"));
+   tab_hline (tbl, TAL_2, 0, n_cols - 1, 2);
+   tab_box (tbl,
+          TAL_2, TAL_2,
+          -1, TAL_1,
+          0, 0,
+          n_cols - 1,
+          n_rows - 1);
+   if ( roc->print_se )
+     {
+       tab_text (tbl, n_cols - 4, 1, TAT_TITLE, _("Std. Error"));
+       tab_text (tbl, n_cols - 3, 1, TAT_TITLE, _("Asymptotic Sig."));
+       tab_text (tbl, n_cols - 2, 1, TAT_TITLE, _("Lower Bound"));
+       tab_text (tbl, n_cols - 1, 1, TAT_TITLE, _("Upper Bound"));
+       tab_joint_text_format (tbl, n_cols - 2, 0, 4, 0,
+                            TAT_TITLE | TAB_CENTER,
+                            _("Asymp. %g%% Confidence Interval"), roc->ci);
+       tab_vline (tbl, 0, n_cols - 1, 0, 0);
+       tab_hline (tbl, TAL_1, n_cols - 2, n_cols - 1, 1);
+     }
+   if ( roc->n_vars > 1)
+     tab_text (tbl, 0, 1, TAT_TITLE, _("Variable under test"));
+   if ( roc->n_vars > 1)
+     tab_vline (tbl, TAL_2, 1, 0, n_rows - 1);
+   for ( i = 0 ; i < roc->n_vars ; ++i )
+     {
+       tab_text (tbl, 0, 2 + i, TAT_TITLE, var_to_string (roc->vars[i]));
+       tab_double (tbl, n_cols - n_fields, 2 + i, 0, rs[i].auc, NULL);
+       if ( roc->print_se )
+       {
+         double se ;
+         const double sd_0_5 = sqrt ((rs[i].n1 + rs[i].n2 + 1) /
+                                     (12 * rs[i].n1 * rs[i].n2));
+         double ci ;
+         double yy ;
+         se = rs[i].auc * (1 - rs[i].auc) + (rs[i].n1 - 1) * (rs[i].q1hat - pow2 (rs[i].auc)) +
+           (rs[i].n2 - 1) * (rs[i].q2hat - pow2 (rs[i].auc));
+         se /= rs[i].n1 * rs[i].n2;
+         se = sqrt (se);
+         tab_double (tbl, n_cols - 4, 2 + i, 0,
+                     se,
+                     NULL);
+         ci = 1 - roc->ci / 100.0;
+         yy = gsl_cdf_gaussian_Qinv (ci, se) ;
+         tab_double (tbl, n_cols - 2, 2 + i, 0,
+                     rs[i].auc - yy,
+                     NULL);
+         tab_double (tbl, n_cols - 1, 2 + i, 0,
+                     rs[i].auc + yy,
+                     NULL);
+         tab_double (tbl, n_cols - 3, 2 + i, 0,
+                     2.0 * gsl_cdf_ugaussian_Q (fabs ((rs[i].auc - 0.5 ) / sd_0_5)),
+                     NULL);
+       }
+     }
+   tab_submit (tbl);
+ }
+ static void
+ show_summary (const struct cmd_roc *roc)
+ {
+   const int n_cols = 3;
+   const int n_rows = 4;
 -  tab_dim (tbl, tab_natural_dimensions, NULL);
++  struct tab_table *tbl = tab_create (n_cols, n_rows);
+   tab_title (tbl, _("Case Summary"));
+   tab_headers (tbl, 1, 0, 2, 0);
 -  tbl = tab_create (n_cols, n_rows, 0);
++  tab_dim (tbl, tab_natural_dimensions, NULL, NULL);
+   tab_box (tbl,
+          TAL_2, TAL_2,
+          -1, -1,
+          0, 0,
+          n_cols - 1,
+          n_rows - 1);
+   tab_hline (tbl, TAL_2, 0, n_cols - 1, 2);
+   tab_vline (tbl, TAL_2, 1, 0, n_rows - 1);
+   tab_hline (tbl, TAL_2, 1, n_cols - 1, 1);
+   tab_vline (tbl, TAL_1, 2, 1, n_rows - 1);
+   tab_text (tbl, 0, 1, TAT_TITLE | TAB_LEFT, var_to_string (roc->state_var));
+   tab_text (tbl, 1, 1, TAT_TITLE, _("Unweighted"));
+   tab_text (tbl, 2, 1, TAT_TITLE, _("Weighted"));
+   tab_joint_text (tbl, 1, 0, 2, 0,
+                 TAT_TITLE | TAB_CENTER,
+                 _("Valid N (listwise)"));
+   tab_text (tbl, 0, 2, TAB_LEFT, _("Positive"));
+   tab_text (tbl, 0, 3, TAB_LEFT, _("Negative"));
+   tab_double (tbl, 1, 2, 0, roc->pos, &F_8_0);
+   tab_double (tbl, 1, 3, 0, roc->neg, &F_8_0);
+   tab_double (tbl, 2, 2, 0, roc->pos_weighted, 0);
+   tab_double (tbl, 2, 3, 0, roc->neg_weighted, 0);
+   tab_submit (tbl);
+ }
+ static void
+ show_coords (struct roc_state *rs, const struct cmd_roc *roc)
+ {
+   int x = 1;
+   int i;
+   const int n_cols = roc->n_vars > 1 ? 4 : 3;
+   int n_rows = 1;
+   struct tab_table *tbl ;
+   for (i = 0; i < roc->n_vars; ++i)
+     n_rows += casereader_count_cases (rs[i].cutpoint_rdr);
 -  tab_dim (tbl, tab_natural_dimensions, NULL);
++  tbl = tab_create (n_cols, n_rows);
+   if ( roc->n_vars > 1)
+     tab_title (tbl, _("Coordinates of the Curve"));
+   else
+     tab_title (tbl, _("Coordinates of the Curve (%s)"), var_to_string (roc->vars[0]));
+   tab_headers (tbl, 1, 0, 1, 0);
 -        const double se = case_data_idx (cc, TP)->f /
++  tab_dim (tbl, tab_natural_dimensions, NULL, NULL);
+   tab_hline (tbl, TAL_2, 0, n_cols - 1, 1);
+   if ( roc->n_vars > 1)
+     tab_text (tbl, 0, 0, TAT_TITLE, _("Test variable"));
+   tab_text (tbl, n_cols - 3, 0, TAT_TITLE, _("Positive if greater than or equal to"));
+   tab_text (tbl, n_cols - 2, 0, TAT_TITLE, _("Sensitivity"));
+   tab_text (tbl, n_cols - 1, 0, TAT_TITLE, _("1 - Specificity"));
+   tab_box (tbl,
+          TAL_2, TAL_2,
+          -1, TAL_1,
+          0, 0,
+          n_cols - 1,
+          n_rows - 1);
+   if ( roc->n_vars > 1)
+     tab_vline (tbl, TAL_2, 1, 0, n_rows - 1);
+   for (i = 0; i < roc->n_vars; ++i)
+     {
+       struct ccase *cc;
+       struct casereader *r = casereader_clone (rs[i].cutpoint_rdr);
+       if ( roc->n_vars > 1)
+       tab_text (tbl, 0, x, TAT_TITLE, var_to_string (roc->vars[i]));
+       if ( i > 0)
+       tab_hline (tbl, TAL_1, 0, n_cols - 1, x);
+       for (; (cc = casereader_read (r)) != NULL;
+          case_unref (cc), x++)
+       {
 -           case_data_idx (cc, TP)->f
++        const double se = case_data_idx (cc, ROC_TP)->f /
+           (
 -           case_data_idx (cc, FN)->f
++           case_data_idx (cc, ROC_TP)->f
+            +
 -        const double sp = case_data_idx (cc, TN)->f /
++           case_data_idx (cc, ROC_FN)->f
+            );
 -           case_data_idx (cc, TN)->f
++        const double sp = case_data_idx (cc, ROC_TN)->f /
+           (
 -           case_data_idx (cc, FP)->f
++           case_data_idx (cc, ROC_TN)->f
+            +
 -        tab_double (tbl, n_cols - 3, x, 0, case_data_idx (cc, CUTPOINT)->f,
++           case_data_idx (cc, ROC_FP)->f
+            );
 -static void
 -draw_roc (struct roc_state *rs, const struct cmd_roc *roc)
 -{
 -  int i;
 -
 -  struct chart *roc_chart = chart_create ();
 -
 -  chart_write_title (roc_chart, _("ROC Curve"));
 -  chart_write_xlabel (roc_chart, _("1 - Specificity"));
 -  chart_write_ylabel (roc_chart, _("Sensitivity"));
 -
 -  chart_write_xscale (roc_chart, 0, 1, 5);
 -  chart_write_yscale (roc_chart, 0, 1, 5);
 -
 -  if ( roc->reference )
 -    {
 -      chart_line (roc_chart, 1.0, 0,
 -                0.0, 1.0,
 -                CHART_DIM_X);
 -    }
 -
 -  for (i = 0; i < roc->n_vars; ++i)
 -    {
 -      struct ccase *cc;
 -      struct casereader *r = casereader_clone (rs[i].cutpoint_rdr);
 -
 -      chart_vector_start (roc_chart, var_get_name (roc->vars[i]));
 -      for (; (cc = casereader_read (r)) != NULL;
 -         case_unref (cc))
 -      {
 -        double se = case_data_idx (cc, TP)->f;
 -        double sp = case_data_idx (cc, TN)->f;
 -
 -        se /= case_data_idx (cc, FN)->f +
 -          case_data_idx (cc, TP)->f ;
 -
 -        sp /= case_data_idx (cc, TN)->f +
 -          case_data_idx (cc, FP)->f ;
 -
 -        chart_vector (roc_chart, 1 - sp, se);
 -      }
 -      chart_vector_end (roc_chart);
 -      casereader_destroy (r);
 -    }
 -
 -  chart_write_legend (roc_chart);
 -
 -  chart_submit (roc_chart);
 -}
 -
 -
++        tab_double (tbl, n_cols - 3, x, 0, case_data_idx (cc, ROC_CUTPOINT)->f,
+                     var_get_print_format (roc->vars[i]));
+         tab_double (tbl, n_cols - 2, x, 0, se, NULL);
+         tab_double (tbl, n_cols - 1, x, 0, 1 - sp, NULL);
+       }
+       casereader_destroy (r);
+     }
+   tab_submit (tbl);
+ }
 -    draw_roc (rs, roc);
+ static void
+ output_roc (struct roc_state *rs, const struct cmd_roc *roc)
+ {
+   show_summary (roc);
+   if ( roc->curve )
 -
++    {
++      struct roc_chart *rc;
++      size_t i;
++
++      rc = roc_chart_create (roc->reference);
++      for (i = 0; i < roc->n_vars; i++)
++        roc_chart_add_var (rc, var_get_name (roc->vars[i]),
++                           rs[i].cutpoint_rdr);
++      chart_submit (roc_chart_get_chart (rc));
++    }
+   show_auc (rs, roc);
+   if ( roc->print_coords )
+     show_coords (rs, roc);
+ }
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..5d63c96fb4e5872ff10eede534f35105c9176fb5
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,28 @@@
++/* PSPP - a program for statistical analysis.
++   Copyright (C) 2009 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 LANGUAGE_STATS_ROC_H
++#define LANGUAGE_STATS_ROC_H 1
++
++/* These are case indexes into the cutpoint case readers for ROC
++   output, used by roc.c and roc-chart.c. */
++#define ROC_CUTPOINT 0
++#define ROC_TP 1
++#define ROC_FN 2
++#define ROC_TN 3
++#define ROC_FP 4
++
++#endif /* language/stats/roc.h */
Simple merge
Simple merge
Simple merge
Simple merge
index 34aac525b8f47288317f56c666f91670dd4f37a8,ec92c559e044723180977db31ca43708c7f637bd..eb9f7b78e78ddcd9194020fe18049ca2676cf6bc
@@@ -8,20 -9,6 +8,22 @@@ src_output_liboutput_la_SOURCES = 
        src/output/afm.c \
        src/output/afm.h \
        src/output/ascii.c \
 +      src/output/chart.c \
 +      src/output/chart.h \
 +      src/output/charts/box-whisker.c \
 +      src/output/charts/box-whisker.h \
 +      src/output/charts/cartesian.c \
 +      src/output/charts/cartesian.h \
 +      src/output/charts/np-plot.c \
 +      src/output/charts/np-plot.h \
 +      src/output/charts/piechart.c \
 +      src/output/charts/piechart.h \
 +      src/output/charts/plot-chart.c \
 +      src/output/charts/plot-chart.h \
 +      src/output/charts/plot-hist.c \
 +      src/output/charts/plot-hist.h \
++      src/output/charts/roc-chart.c \
++      src/output/charts/roc-chart.h \
        src/output/html.c \
        src/output/htmlP.h \
        src/output/journal.c \
index a2d5fef9d1ddfd36914ef6f3fa22e55fdcb48424,0000000000000000000000000000000000000000..b0b4f7d3e1fe0dd0be16577171e21faccc3f4c2c
mode 100644,000000..100644
--- /dev/null
@@@ -1,911 -1,0 +1,911 @@@
-   chart_geometry_free (x->cairo);
 +/* PSPP - a program for statistical analysis.
 +   Copyright (C) 2009 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/cairo.h>
 +
 +#include <libpspp/assertion.h>
 +#include <libpspp/start-date.h>
 +#include <libpspp/version.h>
 +#include <output/afm.h>
 +#include <output/chart-provider.h>
 +#include <output/manager.h>
 +#include <output/output.h>
 +
 +#include <cairo/cairo-pdf.h>
 +#include <cairo/cairo-ps.h>
 +#include <cairo/cairo-svg.h>
 +#include <cairo/cairo.h>
 +#include <pango/pango-font.h>
 +#include <pango/pango-layout.h>
 +#include <pango/pango.h>
 +#include <pango/pangocairo.h>
 +#include <stdlib.h>
 +
 +#include "error.h"
 +#include "intprops.h"
 +#include "minmax.h"
 +#include "xalloc.h"
 +
 +#include "gettext.h"
 +#define _(msgid) gettext (msgid)
 +
 +/* Cairo driver options: (defaults listed first)
 +
 +   output-file="pspp.pdf"
 +   output-type=pdf|ps|png|svg
 +   paper-size=letter (see "papersize" file)
 +   orientation=portrait|landscape
 +   headers=on|off
 +
 +   left-margin=0.5in
 +   right-margin=0.5in
 +   top-margin=0.5in
 +   bottom-margin=0.5in
 +
 +   prop-font=serif
 +   emph-font=serif italic
 +   fixed-font=monospace
 +   font-size=10000
 +
 +   line-gutter=1pt
 +   line-spacing=1pt
 +   line-width=0.5pt
 + */
 +
 +/* Measurements as we present to the rest of PSPP. */
 +#define XR_POINT PANGO_SCALE
 +#define XR_INCH (XR_POINT * 72)
 +
 +/* Conversions to and from points. */
 +static double
 +xr_to_pt (int x)
 +{
 +  return x / (double) XR_POINT;
 +}
 +
 +static int
 +pt_to_xr (double x)
 +{
 +  return x * XR_POINT + 0.5;
 +}
 +
 +/* Output types. */
 +enum xr_output_type
 +  {
 +    XR_PDF,
 +    XR_PS,
 +    XR_SVG
 +  };
 +
 +/* A font for use with Cairo. */
 +struct xr_font
 +  {
 +    char *string;
 +    PangoFontDescription *desc;
 +    PangoLayout *layout;
 +    PangoFontMetrics *metrics;
 +  };
 +
 +/* Cairo output driver extension record. */
 +struct xr_driver_ext
 +  {
 +    cairo_t *cairo;
 +    struct xr_font fonts[OUTP_FONT_CNT];
 +
 +    bool draw_headers;          /* Draw headers at top of page? */
 +    int page_number;          /* Current page number. */
 +
 +    int line_gutter;          /* Space around lines. */
 +    int line_space;           /* Space between lines. */
 +    int line_width;           /* Width of lines. */
 +  };
 +
 +struct xr_driver_options
 +  {
 +    struct outp_driver *driver;
 +
 +    char *file_name;            /* Output file name. */
 +    enum xr_output_type file_type; /* Type of output file. */
 +
 +
 +    bool portrait;              /* Portrait mode? */
 +
 +    int paper_width;            /* Width of paper before dropping margins. */
 +    int paper_length;           /* Length of paper before dropping margins. */
 +    int left_margin;          /* Left margin in XR units. */
 +    int right_margin;         /* Right margin in XR units. */
 +    int top_margin;           /* Top margin in XR units. */
 +    int bottom_margin;                /* Bottom margin in XR units. */
 +  };
 +
 +static bool handle_option (void *options, const char *key,
 +                           const struct string *val);
 +static void draw_headers (struct outp_driver *this);
 +
 +static bool load_font (struct outp_driver *this, struct xr_font *);
 +static void free_font (struct xr_font *);
 +static int text_width (struct outp_driver *, const char *, enum outp_font);
 +\f
 +/* Driver initialization. */
 +
 +static struct outp_driver *
 +xr_allocate (const char *name, int types)
 +{
 +  struct outp_driver *this;
 +  struct xr_driver_ext *x;
 +  size_t i;
 +
 +  this = outp_allocate_driver (&cairo_class, name, types);
 +  this->width = this->length = 0;
 +  this->font_height = XR_POINT * 10;
 +  this->ext = x = xzalloc (sizeof *x);
 +  x->cairo = NULL;
 +  x->fonts[OUTP_FIXED].string = xstrdup ("monospace");
 +  x->fonts[OUTP_PROPORTIONAL].string = xstrdup ("serif");
 +  x->fonts[OUTP_EMPHASIS].string = xstrdup ("serif italic");
 +  for (i = 0; i < OUTP_FONT_CNT; i++)
 +    {
 +      struct xr_font *font = &x->fonts[i];
 +      font->desc = NULL;
 +      font->metrics = NULL;
 +      font->layout = NULL;
 +    }
 +  x->draw_headers = true;
 +  x->page_number = 0;
 +  x->line_gutter = XR_POINT;
 +  x->line_space = XR_POINT;
 +  x->line_width = XR_POINT / 2;
 +
 +  return this;
 +}
 +
 +static bool
 +xr_set_cairo (struct outp_driver *this, cairo_t *cairo)
 +{
 +  struct xr_driver_ext *x = this->ext;
 +  int i;
 +
 +  x->cairo = cairo;
 +
 +  cairo_set_line_width (x->cairo, xr_to_pt (x->line_width));
 +
 +  for (i = 0; i < OUTP_FONT_CNT; i++)
 +    if (!load_font (this, &x->fonts[i]))
 +      return false;
 +
 +  this->fixed_width = text_width (this, "0", OUTP_FIXED);
 +  this->prop_em_width = text_width (this, "0", OUTP_PROPORTIONAL);
 +
 +  this->horiz_line_width[OUTP_L_NONE] = 0;
 +  this->horiz_line_width[OUTP_L_SINGLE] = 2 * x->line_gutter + x->line_width;
 +  this->horiz_line_width[OUTP_L_DOUBLE] = (2 * x->line_gutter + x->line_space
 +                                           + 2 * x->line_width);
 +  memcpy (this->vert_line_width, this->horiz_line_width,
 +          sizeof this->vert_line_width);
 +
 +  return true;
 +}
 +
 +struct outp_driver *
 +xr_create_driver (cairo_t *cairo)
 +{
 +  struct outp_driver *this;
 +
 +  this = xr_allocate ("cairo", 0);
 +  this->width = INT_MAX / 8;
 +  this->length = INT_MAX / 8;
 +  if (!xr_set_cairo (this, cairo))
 +    {
 +      this->class->close_driver (this);
 +      outp_free_driver (this);
 +      return NULL;
 +    }
 +  return this;
 +}
 +
 +static bool
 +xr_open_driver (const char *name, int types, struct substring option_string)
 +{
 +  struct outp_driver *this;
 +  struct xr_driver_ext *x;
 +  struct xr_driver_options options;
 +  cairo_surface_t *surface;
 +  cairo_status_t status;
 +  double width_pt, length_pt;
 +
 +  this = xr_allocate (name, types);
 +  x = this->ext;
 +
 +  options.driver = this;
 +  options.file_name = xstrdup ("pspp.pdf");
 +  options.file_type = XR_PDF;
 +  options.portrait = true;
 +  outp_get_paper_size ("", &options.paper_width, &options.paper_length);
 +  options.left_margin = XR_INCH / 2;
 +  options.right_margin = XR_INCH / 2;
 +  options.top_margin = XR_INCH / 2;
 +  options.bottom_margin = XR_INCH / 2;
 +
 +  outp_parse_options (this->name, option_string, handle_option, &options);
 +
 +  width_pt = options.paper_width / 1000.0;
 +  length_pt = options.paper_length / 1000.0;
 +  if (options.portrait)
 +    {
 +      this->width = pt_to_xr (width_pt);
 +      this->length = pt_to_xr (length_pt);
 +    }
 +  else
 +    {
 +      this->width = pt_to_xr (width_pt);
 +      this->length = pt_to_xr (length_pt);
 +    }
 +  if (x->draw_headers)
 +    options.top_margin += 3 * this->font_height;
 +  this->width -= options.left_margin + options.right_margin;
 +  this->length -= options.top_margin + options.bottom_margin;
 +
 +  if (options.file_type == XR_PDF)
 +    surface = cairo_pdf_surface_create (options.file_name,
 +                                        width_pt, length_pt);
 +  else if (options.file_type == XR_PS)
 +    surface = cairo_ps_surface_create (options.file_name, width_pt, length_pt);
 +  else if (options.file_type == XR_SVG)
 +    surface = cairo_svg_surface_create (options.file_name,
 +                                        width_pt, length_pt);
 +  else
 +    NOT_REACHED ();
 +
 +  status = cairo_surface_status (surface);
 +  if (status != CAIRO_STATUS_SUCCESS)
 +    {
 +      error (0, 0, _("opening output file \"%s\": %s"),
 +             options.file_name, cairo_status_to_string (status));
 +      cairo_surface_destroy (surface);
 +      goto error;
 +    }
 +
 +  x->cairo = cairo_create (surface);
 +  cairo_surface_destroy (surface);
 +
 +  cairo_translate (x->cairo,
 +                   xr_to_pt (options.left_margin),
 +                   xr_to_pt (options.top_margin));
 +
 +  if (this->length / this->font_height < 15)
 +    {
 +      error (0, 0, _("The defined page is not long "
 +                     "enough to hold margins and headers, plus least 15 "
 +                     "lines of the default fonts.  In fact, there's only "
 +                     "room for %d lines."),
 +             this->length / this->font_height);
 +      goto error;
 +    }
 +
 +  if (!xr_set_cairo (this, x->cairo))
 +    goto error;
 +
 +  outp_register_driver (this);
 +  free (options.file_name);
 +  return true;
 +
 + error:
 +  this->class->close_driver (this);
 +  outp_free_driver (this);
 +  free (options.file_name);
 +  return false;
 +}
 +
 +static bool
 +xr_close_driver (struct outp_driver *this)
 +{
 +  struct xr_driver_ext *x = this->ext;
 +  bool ok = true;
 +  size_t i;
 +
 +  if (x->cairo != NULL)
 +    {
 +      cairo_status_t status;
 +
 +      cairo_surface_finish (cairo_get_target (x->cairo));
 +      status = cairo_status (x->cairo);
 +      if (status != CAIRO_STATUS_SUCCESS)
 +        error (0, 0, _("error writing output file for %s driver: %s"),
 +               this->name, cairo_status_to_string (status));
 +      cairo_destroy (x->cairo);
 +    }
 +
 +  for (i = 0; i < OUTP_FONT_CNT; i++)
 +    free_font (&x->fonts[i]);
 +  free (x);
 +
 +  return ok;
 +}
 +
 +/* Generic option types. */
 +enum
 +{
 +  output_file_arg,
 +  output_type_arg,
 +  paper_size_arg,
 +  orientation_arg,
 +  line_style_arg,
 +  boolean_arg,
 +  dimension_arg,
 +  string_arg,
 +  nonneg_int_arg
 +};
 +
 +/* All the options that the Cairo driver supports. */
 +static const struct outp_option option_tab[] =
 +{
 +  {"output-file",             output_file_arg,0},
 +  {"output-type",               output_type_arg,0},
 +  {"paper-size",              paper_size_arg, 0},
 +  {"orientation",             orientation_arg,0},
 +
 +  {"headers",                 boolean_arg,    1},
 +
 +  {"prop-font",               string_arg,     OUTP_PROPORTIONAL},
 +  {"emph-font",               string_arg,     OUTP_EMPHASIS},
 +  {"fixed-font",              string_arg,     OUTP_FIXED},
 +
 +  {"left-margin",             dimension_arg,  0},
 +  {"right-margin",            dimension_arg,  1},
 +  {"top-margin",              dimension_arg,  2},
 +  {"bottom-margin",           dimension_arg,  3},
 +  {"font-size",                       dimension_arg,  4},
 +  {"line-width",              dimension_arg,  5},
 +  {"line-gutter",             dimension_arg,  6},
 +  {"line-width",              dimension_arg,  7},
 +  {NULL, 0, 0},
 +};
 +
 +static bool
 +handle_option (void *options_, const char *key, const struct string *val)
 +{
 +  struct xr_driver_options *options = options_;
 +  struct outp_driver *this = options->driver;
 +  struct xr_driver_ext *x = this->ext;
 +  int subcat;
 +  char *value = ds_cstr (val);
 +
 +  switch (outp_match_keyword (key, option_tab, &subcat))
 +    {
 +    case -1:
 +      error (0, 0,
 +             _("unknown configuration parameter `%s' for %s device "
 +               "driver"), key, this->class->name);
 +      break;
 +    case output_file_arg:
 +      free (options->file_name);
 +      options->file_name = xstrdup (value);
 +      break;
 +    case output_type_arg:
 +      if (!strcmp (value, "pdf"))
 +        options->file_type = XR_PDF;
 +      else if (!strcmp (value, "ps"))
 +        options->file_type = XR_PS;
 +      else if (!strcmp (value, "svg"))
 +        options->file_type = XR_SVG;
 +      else
 +        {
 +          error (0, 0, _("unknown Cairo output type \"%s\""), value);
 +          return false;
 +        }
 +      break;
 +    case paper_size_arg:
 +      outp_get_paper_size (value,
 +                           &options->paper_width, &options->paper_length);
 +      break;
 +    case orientation_arg:
 +      if (!strcmp (value, "portrait"))
 +      options->portrait = true;
 +      else if (!strcmp (value, "landscape"))
 +      options->portrait = false;
 +      else
 +      error (0, 0, _("unknown orientation `%s' (valid orientations are "
 +                       "`portrait' and `landscape')"), value);
 +      break;
 +    case boolean_arg:
 +      if (!strcmp (value, "on") || !strcmp (value, "true")
 +          || !strcmp (value, "yes") || atoi (value))
 +        x->draw_headers = true;
 +      else if (!strcmp (value, "off") || !strcmp (value, "false")
 +               || !strcmp (value, "no") || !strcmp (value, "0"))
 +        x->draw_headers = false;
 +      else
 +        {
 +          error (0, 0, _("boolean value expected for %s"), key);
 +          return false;
 +        }
 +      break;
 +    case dimension_arg:
 +      {
 +      int dimension = outp_evaluate_dimension (value);
 +
 +      if (dimension <= 0)
 +          break;
 +      switch (subcat)
 +        {
 +        case 0:
 +          options->left_margin = dimension;
 +          break;
 +        case 1:
 +          options->right_margin = dimension;
 +          break;
 +        case 2:
 +          options->top_margin = dimension;
 +          break;
 +        case 3:
 +          options->bottom_margin = dimension;
 +          break;
 +        case 4:
 +          this->font_height = dimension;
 +          break;
 +        case 5:
 +          x->line_width = dimension;
 +          break;
 +        case 6:
 +          x->line_gutter = dimension;
 +          break;
 +        case 7:
 +          x->line_width = dimension;
 +          break;
 +        default:
 +          NOT_REACHED ();
 +        }
 +      }
 +      break;
 +    case string_arg:
 +      free (x->fonts[subcat].string);
 +      x->fonts[subcat].string = ds_xstrdup (val);
 +      break;
 +    default:
 +      NOT_REACHED ();
 +    }
 +
 +  return true;
 +}
 +\f
 +/* Basic file operations. */
 +
 +static void
 +xr_open_page (struct outp_driver *this)
 +{
 +  struct xr_driver_ext *x = this->ext;
 +
 +  x->page_number++;
 +
 +  if (x->draw_headers)
 +    draw_headers (this);
 +}
 +
 +static void
 +xr_close_page (struct outp_driver *this)
 +{
 +  struct xr_driver_ext *x = this->ext;
 +  cairo_show_page (x->cairo);
 +}
 +
 +static void
 +xr_output_chart (struct outp_driver *this, const struct chart *chart)
 +{
 +  struct xr_driver_ext *x = this->ext;
 +  struct chart_geometry geom;
 +
 +  outp_eject_page (this);
 +  outp_open_page (this);
 +
 +  cairo_save (x->cairo);
 +  cairo_translate (x->cairo, 0.0, xr_to_pt (this->length));
 +  cairo_scale (x->cairo, 1.0, -1.0);
 +  chart_geometry_init (x->cairo, &geom,
 +                       xr_to_pt (this->width), xr_to_pt (this->length));
 +  chart_draw (chart, x->cairo, &geom);
++  chart_geometry_free (x->cairo, &geom);
 +  cairo_restore (x->cairo);
 +
 +  outp_close_page (this);
 +}
 +\f
 +/* Draws a line from (x0,y0) to (x1,y1). */
 +static void
 +dump_line (struct outp_driver *this, int x0, int y0, int x1, int y1)
 +{
 +  struct xr_driver_ext *x = this->ext;
 +  cairo_new_path (x->cairo);
 +  cairo_move_to (x->cairo, xr_to_pt (x0), xr_to_pt (y0));
 +  cairo_line_to (x->cairo, xr_to_pt (x1), xr_to_pt (y1));
 +  cairo_stroke (x->cairo);
 +}
 +
 +/* Draws a horizontal line X0...X2 at Y if LEFT says so,
 +   shortening it to X0...X1 if SHORTEN is true.
 +   Draws a horizontal line X1...X3 at Y if RIGHT says so,
 +   shortening it to X2...X3 if SHORTEN is true. */
 +static void
 +horz_line (struct outp_driver *this,
 +           int x0, int x1, int x2, int x3, int y,
 +           enum outp_line_style left, enum outp_line_style right,
 +           bool shorten)
 +{
 +  if (left != OUTP_L_NONE && right != OUTP_L_NONE && !shorten)
 +    dump_line (this, x0, y, x3, y);
 +  else
 +    {
 +      if (left != OUTP_L_NONE)
 +        dump_line (this, x0, y, shorten ? x1 : x2, y);
 +      if (right != OUTP_L_NONE)
 +        dump_line (this, shorten ? x2 : x1, y, x3, y);
 +    }
 +}
 +
 +/* Draws a vertical line Y0...Y2 at X if TOP says so,
 +   shortening it to Y0...Y1 if SHORTEN is true.
 +   Draws a vertical line Y1...Y3 at X if BOTTOM says so,
 +   shortening it to Y2...Y3 if SHORTEN is true. */
 +static void
 +vert_line (struct outp_driver *this,
 +           int y0, int y1, int y2, int y3, int x,
 +           enum outp_line_style top, enum outp_line_style bottom,
 +           bool shorten)
 +{
 +  if (top != OUTP_L_NONE && bottom != OUTP_L_NONE && !shorten)
 +    dump_line (this, x, y0, x, y3);
 +  else
 +    {
 +      if (top != OUTP_L_NONE)
 +        dump_line (this, x, y0, x, shorten ? y1 : y2);
 +      if (bottom != OUTP_L_NONE)
 +        dump_line (this, x, shorten ? y2 : y1, x, y3);
 +    }
 +}
 +
 +/* Draws a generalized intersection of lines in the rectangle
 +   (X0,Y0)-(X3,Y3).  The line coming from the top to the center
 +   is of style TOP, from left to center of style LEFT, from
 +   bottom to center of style BOTTOM, and from right to center of
 +   style RIGHT. */
 +static void
 +xr_line (struct outp_driver *this,
 +         int x0, int y0, int x3, int y3,
 +         enum outp_line_style top, enum outp_line_style left,
 +         enum outp_line_style bottom, enum outp_line_style right)
 +{
 +  /* The algorithm here is somewhat subtle, to allow it to handle
 +     all the kinds of intersections that we need.
 +
 +     Three additional ordinates are assigned along the x axis.  The
 +     first is xc, midway between x0 and x3.  The others are x1 and
 +     x2; for a single vertical line these are equal to xc, and for
 +     a double vertical line they are the ordinates of the left and
 +     right half of the double line.
 +
 +     yc, y1, and y2 are assigned similarly along the y axis.
 +
 +     The following diagram shows the coordinate system and output
 +     for double top and bottom lines, single left line, and no
 +     right line:
 +
 +                 x0       x1 xc  x2      x3
 +               y0 ________________________
 +                  |        #     #       |
 +                  |        #     #       |
 +                  |        #     #       |
 +                  |        #     #       |
 +                  |        #     #       |
 +     y1 = y2 = yc |#########     #       |
 +                  |        #     #       |
 +                  |        #     #       |
 +                  |        #     #       |
 +                  |        #     #       |
 +               y3 |________#_____#_______|
 +  */
 +  struct xr_driver_ext *ext = this->ext;
 +
 +  /* Offset from center of each line in a pair of double lines. */
 +  int double_line_ofs = (ext->line_space + ext->line_width) / 2;
 +
 +  /* Are the lines along each axis single or double?
 +     (It doesn't make sense to have different kinds of line on the
 +     same axis, so we don't try to gracefully handle that case.) */
 +  bool double_vert = top == OUTP_L_DOUBLE || bottom == OUTP_L_DOUBLE;
 +  bool double_horz = left == OUTP_L_DOUBLE || right == OUTP_L_DOUBLE;
 +
 +  /* When horizontal lines are doubled,
 +     the left-side line along y1 normally runs from x0 to x2,
 +     and the right-side line along y1 from x3 to x1.
 +     If the top-side line is also doubled, we shorten the y1 lines,
 +     so that the left-side line runs only to x1,
 +     and the right-side line only to x2.
 +     Otherwise, the horizontal line at y = y1 below would cut off
 +     the intersection, which looks ugly:
 +               x0       x1     x2      x3
 +             y0 ________________________
 +                |        #     #       |
 +                |        #     #       |
 +                |        #     #       |
 +                |        #     #       |
 +             y1 |#########     ########|
 +                |                      |
 +                |                      |
 +             y2 |######################|
 +                |                      |
 +                |                      |
 +             y3 |______________________|
 +     It is more of a judgment call when the horizontal line is
 +     single.  We actually choose to cut off the line anyhow, as
 +     shown in the first diagram above.
 +  */
 +  bool shorten_y1_lines = top == OUTP_L_DOUBLE;
 +  bool shorten_y2_lines = bottom == OUTP_L_DOUBLE;
 +  bool shorten_yc_line = shorten_y1_lines && shorten_y2_lines;
 +  int horz_line_ofs = double_vert ? double_line_ofs : 0;
 +  int xc = (x0 + x3) / 2;
 +  int x1 = xc - horz_line_ofs;
 +  int x2 = xc + horz_line_ofs;
 +
 +  bool shorten_x1_lines = left == OUTP_L_DOUBLE;
 +  bool shorten_x2_lines = right == OUTP_L_DOUBLE;
 +  bool shorten_xc_line = shorten_x1_lines && shorten_x2_lines;
 +  int vert_line_ofs = double_horz ? double_line_ofs : 0;
 +  int yc = (y0 + y3) / 2;
 +  int y1 = yc - vert_line_ofs;
 +  int y2 = yc + vert_line_ofs;
 +
 +  if (!double_horz)
 +    horz_line (this, x0, x1, x2, x3, yc, left, right, shorten_yc_line);
 +  else
 +    {
 +      horz_line (this, x0, x1, x2, x3, y1, left, right, shorten_y1_lines);
 +      horz_line (this, x0, x1, x2, x3, y2, left, right, shorten_y2_lines);
 +    }
 +
 +  if (!double_vert)
 +    vert_line (this, y0, y1, y2, y3, xc, top, bottom, shorten_xc_line);
 +  else
 +    {
 +      vert_line (this, y0, y1, y2, y3, x1, top, bottom, shorten_x1_lines);
 +      vert_line (this, y0, y1, y2, y3, x2, top, bottom, shorten_x2_lines);
 +    }
 +}
 +
 +/* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
 +   and with the given JUSTIFICATION for THIS driver. */
 +static int
 +draw_text (struct outp_driver *this,
 +           const char *string, int x, int y, int max_width,
 +           enum outp_justification justification)
 +{
 +  struct outp_text text;
 +  int width;
 +
 +  text.font = OUTP_PROPORTIONAL;
 +  text.justification = justification;
 +  text.string = ss_cstr (string);
 +  text.h = max_width;
 +  text.v = this->font_height;
 +  text.x = x;
 +  text.y = y;
 +  this->class->text_metrics (this, &text, &width, NULL);
 +  this->class->text_draw (this, &text);
 +  return width;
 +}
 +
 +/* Writes STRING at location (X,Y) trimmed to the given MAX_WIDTH
 +   and with the given JUSTIFICATION for THIS driver. */
 +static int
 +text_width (struct outp_driver *this, const char *string, enum outp_font font)
 +{
 +  struct outp_text text;
 +  int width;
 +
 +  text.font = font;
 +  text.justification = OUTP_LEFT;
 +  text.string = ss_cstr (string);
 +  text.h = INT_MAX;
 +  text.v = this->font_height;
 +  text.x = 0;
 +  text.y = 0;
 +  this->class->text_metrics (this, &text, &width, NULL);
 +  return width;
 +}
 +
 +/* Writes LEFT left-justified and RIGHT right-justified within
 +   (X0...X1) at Y.  LEFT or RIGHT or both may be null. */
 +static void
 +draw_header_line (struct outp_driver *this,
 +                  const char *left, const char *right,
 +                  int x0, int x1, int y)
 +{
 +  int right_width = 0;
 +  if (right != NULL)
 +    right_width = (draw_text (this, right, x0, y, x1 - x0, OUTP_RIGHT)
 +                   + this->prop_em_width);
 +  if (left != NULL)
 +    draw_text (this, left, x0, y, x1 - x0 - right_width, OUTP_LEFT);
 +}
 +
 +/* Draw top of page headers for THIS driver. */
 +static void
 +draw_headers (struct outp_driver *this)
 +{
 +  struct xr_driver_ext *ext = this->ext;
 +  char *r1, *r2;
 +  int x0, x1;
 +  int y;
 +
 +  y = -3 * this->font_height;
 +  x0 = this->prop_em_width;
 +  x1 = this->width - this->prop_em_width;
 +
 +  /* Draw box. */
 +  cairo_rectangle (ext->cairo, 0, xr_to_pt (y), xr_to_pt (this->width),
 +                   xr_to_pt (2 * (this->font_height
 +                                  + ext->line_width + ext->line_gutter)));
 +  cairo_save (ext->cairo);
 +  cairo_set_source_rgb (ext->cairo, 0.9, 0.9, 0.9);
 +  cairo_fill_preserve (ext->cairo);
 +  cairo_restore (ext->cairo);
 +  cairo_stroke (ext->cairo);
 +
 +  y += ext->line_width + ext->line_gutter;
 +
 +  r1 = xasprintf (_("%s - Page %d"), get_start_date (), ext->page_number);
 +  r2 = xasprintf ("%s - %s", version, host_system);
 +
 +  draw_header_line (this, outp_title, r1, x0, x1, y);
 +  y += this->font_height;
 +
 +  draw_header_line (this, outp_subtitle, r2, x0, x1, y);
 +
 +  free (r1);
 +  free (r2);
 +}
 +\f
 +/* Format TEXT on THIS driver.
 +   If DRAW is nonzero, draw the text.
 +   The width of the widest line is stored into *WIDTH, if WIDTH
 +   is nonnull.
 +   The total height of the text written is stored into *HEIGHT,
 +   if HEIGHT is nonnull. */
 +static void
 +text (struct outp_driver *this, const struct outp_text *text, bool draw,
 +      int *width, int *height)
 +{
 +  struct xr_driver_ext *ext = this->ext;
 +  struct xr_font *font = &ext->fonts[text->font];
 +
 +  pango_layout_set_text (font->layout,
 +                         text->string.string, text->string.length);
 +  pango_layout_set_alignment (
 +    font->layout,
 +    (text->justification == OUTP_RIGHT ? PANGO_ALIGN_RIGHT
 +     : text->justification == OUTP_LEFT ? PANGO_ALIGN_LEFT
 +     : PANGO_ALIGN_CENTER));
 +  pango_layout_set_width (font->layout, text->h == INT_MAX ? -1 : text->h);
 +  pango_layout_set_wrap (font->layout, PANGO_WRAP_WORD_CHAR);
 +  /* XXX need to limit number of lines to those that fit in text->v. */
 +
 +  if (draw)
 +    {
 +      int x = text->x;
 +      if (text->justification != OUTP_LEFT && text->h != INT_MAX)
 +        {
 +          int w, h, excess;
 +          pango_layout_get_size (font->layout, &w, &h);
 +          excess = text->h - w;
 +          if (excess > 0)
 +            {
 +              if (text->justification == OUTP_CENTER)
 +                x += excess / 2;
 +              else
 +                x += excess;
 +            }
 +        }
 +      cairo_save (ext->cairo);
 +      cairo_translate (ext->cairo, xr_to_pt (text->x), xr_to_pt (text->y));
 +      pango_cairo_show_layout (ext->cairo, font->layout);
 +      cairo_restore (ext->cairo);
 +    }
 +
 +  if (width != NULL || height != NULL)
 +    {
 +      int w, h;
 +      pango_layout_get_size (font->layout, &w, &h);
 +      if (width != NULL)
 +        *width = w;
 +      if (height != NULL)
 +        *height = h;
 +    }
 +}
 +
 +static void
 +xr_text_metrics (struct outp_driver *this, const struct outp_text *t,
 +                 int *width, int *height)
 +{
 +  text (this, t, false, width, height);
 +}
 +
 +static void
 +xr_text_draw (struct outp_driver *this, const struct outp_text *t)
 +{
 +  text (this, t, true, NULL, NULL);
 +}
 +\f
 +/* Attempts to load FONT, initializing its other members based on
 +   its 'string' member and the information in THIS.  Returns true
 +   if successful, otherwise false. */
 +static bool
 +load_font (struct outp_driver *this, struct xr_font *font)
 +{
 +  struct xr_driver_ext *x = this->ext;
 +  PangoContext *context;
 +  PangoLanguage *language;
 +
 +  font->desc = pango_font_description_from_string (font->string);
 +  if (font->desc == NULL)
 +    {
 +      error (0, 0, _("\"%s\": bad font specification"), font->string);
 +      return false;
 +    }
 +  pango_font_description_set_absolute_size (font->desc, this->font_height);
 +
 +  font->layout = pango_cairo_create_layout (x->cairo);
 +  pango_layout_set_font_description (font->layout, font->desc);
 +
 +  language = pango_language_get_default ();
 +  context = pango_layout_get_context (font->layout);
 +  font->metrics = pango_context_get_metrics (context, font->desc, language);
 +
 +  return true;
 +}
 +
 +/* Frees FONT. */
 +static void
 +free_font (struct xr_font *font)
 +{
 +  free (font->string);
 +  if (font->desc != NULL)
 +    pango_font_description_free (font->desc);
 +  pango_font_metrics_unref (font->metrics);
 +  g_object_unref (font->layout);
 +}
 +
 +/* Cairo driver class. */
 +const struct outp_class cairo_class =
 +{
 +  "cairo",
 +  0,
 +
 +  xr_open_driver,
 +  xr_close_driver,
 +
 +  xr_open_page,
 +  xr_close_page,
 +  NULL,
 +
 +  xr_output_chart,
 +
 +  NULL,
 +
 +  xr_line,
 +  xr_text_metrics,
 +  xr_text_draw,
 +};
index 6065036d0f26b4416048e334d2b5c1b02a98dd81,0000000000000000000000000000000000000000..9becb6f5ed2e200e69474e6059dc8d5bb259ff55
mode 100644,000000..100644
--- /dev/null
@@@ -1,85 -1,0 +1,89 @@@
- void chart_geometry_free (cairo_t *);
 +/* PSPP - a program for statistical analysis.
 +   Copyright (C) 2004, 2009 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 OUTPUT_CHART_PROVIDER_H
 +#define OUTPUT_CHART_PROVIDER_H 1
 +
 +#include <cairo/cairo.h>
 +#include <stdbool.h>
 +#include <stdint.h>
 +#include <output/chart.h>
 +
 +struct chart_colour
 +  {
 +    uint8_t red;
 +    uint8_t green;
 +    uint8_t blue;
 +  };
 +
 +/* The geometry of a chart. */
 +struct chart_geometry
 +  {
 +    int data_top   ;
 +    int data_right ;
 +    int data_bottom;
 +    int data_left  ;
 +
 +    int abscissa_top;
 +
 +    int ordinate_right ;
 +
 +    int title_bottom ;
 +
++    /* Legend. */
 +    int legend_left ;
 +    int legend_right ;
++    const char **dataset;
++    int n_datasets;
 +
 +    /* Default font size for the plot. */
 +    double font_size;
 +
 +    struct chart_colour fill_colour;
 +
 +    /* Stuff Particular to Cartesians (and Boxplots ) */
 +    double ordinate_scale;
 +    double abscissa_scale;
 +    double x_min;
 +    double x_max;
 +    double y_min;
 +    double y_max;
++    bool in_path;
 +  };
 +
 +struct chart_class
 +  {
 +    void (*draw) (const struct chart *, cairo_t *, struct chart_geometry *);
 +    void (*destroy) (struct chart *);
 +  };
 +
 +struct chart
 +  {
 +    const struct chart_class *class;
 +    int ref_cnt;
 +  };
 +
 +void chart_init (struct chart *, const struct chart_class *);
 +
 +void chart_geometry_init (cairo_t *, struct chart_geometry *,
 +                          double width, double length);
++void chart_geometry_free (cairo_t *, struct chart_geometry *);
 +
 +void chart_draw (const struct chart *, cairo_t *, struct chart_geometry *);
 +char *chart_draw_png (const struct chart *, const char *file_name_template,
 +                      int number);
 +
 +#endif /* output/chart-provider.h */
index 1f85d8e485d2303f1cbce08523247d854583a1e9,e324901cc4df1d445f3fc8c7a4b64296bca55bca..e31422bc6bb35799243d68337d41914ae059c430
  
  extern struct som_table_class tab_table_class;
  
 -struct chart *
 -chart_create(void)
 +void
 +chart_init (struct chart *chart, const struct chart_class *class)
  {
 -  struct chart *chart;
 -  struct outp_driver *d;
 -
 -  d = outp_drivers (NULL);
 -  if (d == NULL)
 -    return NULL;
 -
 -  chart = xmalloc (sizeof *chart);
 -  chart->lp = NULL;
 -  d->class->initialise_chart(d, chart);
 -  if (!chart->lp)
 -    {
 -      free (chart);
 -      return NULL;
 -    }
 -
 -  if (pl_openpl_r (chart->lp) < 0)      /* open Plotter */
 -    return NULL;
 -
 -  pl_fspace_r (chart->lp, 0.0, 0.0, 1000.0, 1000.0); /* set coordinate system */
 -  pl_flinewidth_r (chart->lp, 0.25);    /* set line thickness */
 -  pl_pencolorname_r (chart->lp, "black");
 -
 -  pl_erase_r (chart->lp);               /* erase graphics display */
 -  pl_filltype_r(chart->lp,0);
 -
 -  pl_savestate_r(chart->lp);
 -
 -  /* Set default chartetry */
 -  chart->data_top =   900;
 -  chart->data_right = 800;
 -  chart->data_bottom = 120;
 -  chart->data_left = 150;
 -  chart->abscissa_top = 70;
 -  chart->ordinate_right = 120;
 -  chart->title_bottom = 920;
 -  chart->legend_left = 810;
 -  chart->legend_right = 1000;
 -  chart->font_size = 0;
 -  chart->in_path = false;
 -  chart->dataset = NULL;
 -  chart->n_datasets = 0;
 -  strcpy(chart->fill_colour,"red");
 -
 -  /* Get default font size */
 -  if ( !chart->font_size)
 -    chart->font_size = pl_fontsize_r(chart->lp, -1);
 -
 -  /* Draw the data area */
 -  pl_box_r(chart->lp,
 -         chart->data_left, chart->data_bottom,
 -         chart->data_right, chart->data_top);
 +  chart->class = class;
 +  chart->ref_cnt = 1;
 +}
  
 -  return chart;
 +void
 +chart_geometry_init (cairo_t *cr, struct chart_geometry *geom,
 +                     double width, double length)
 +{
 +  /* Set default chartetry. */
 +  geom->data_top = 0.900 * length;
 +  geom->data_right = 0.800 * width;
 +  geom->data_bottom = 0.120 * length;
 +  geom->data_left = 0.150 * width;
 +  geom->abscissa_top = 0.070 * length;
 +  geom->ordinate_right = 0.120 * width;
 +  geom->title_bottom = 0.920 * length;
 +  geom->legend_left = 0.810 * width;
 +  geom->legend_right = width;
 +  geom->font_size = 15.0;
++  geom->in_path = false;
++  geom->dataset = NULL;
++  geom->n_datasets = 0;
 +
 +  geom->fill_colour.red = 255;
 +  geom->fill_colour.green = 0;
 +  geom->fill_colour.blue = 0;
 +
 +  cairo_set_line_width (cr, 1.0);
 +
 +  cairo_rectangle (cr, geom->data_left, geom->data_bottom,
 +                   geom->data_right - geom->data_left,
 +                   geom->data_top - geom->data_bottom);
 +  cairo_stroke (cr);
  }
  
  void
- chart_geometry_free (cairo_t *cr UNUSED)
 -chart_submit(struct chart *chart)
++chart_geometry_free (cairo_t *cr UNUSED, struct chart_geometry *geom)
  {
 -  struct som_entity s;
 -  struct outp_driver *d;
 -
 -  if ( ! chart )
 -     return ;
 -
 -  pl_restorestate_r(chart->lp);
 -
 -  s.class = &tab_table_class;
 -  s.ext = chart;
 -  s.type = SOM_CHART;
 -  som_submit (&s);
+   int i;
 -  if (pl_closepl_r (chart->lp) < 0)     /* close Plotter */
 -    {
 -      fprintf (stderr, "Couldn't close Plotter\n");
 -    }
 -
 -  pl_deletepl_r(chart->lp);
++  for (i = 0 ; i < geom->n_datasets; ++i)
++    free (geom->dataset[i]);
++  free (geom->dataset);
 +}
  
 -  pl_deleteplparams(chart->pl_params);
 +void
 +chart_draw (const struct chart *chart, cairo_t *cr,
 +            struct chart_geometry *geom)
 +{
 +  chart->class->draw (chart, cr, geom);
 +}
  
 -  d = outp_drivers (NULL);
 -  d->class->finalise_chart(d, chart);
 +char *
 +chart_draw_png (const struct chart *chart, const char *file_name_template,
 +                int number)
 +{
 +  const int width = 640;
 +  const int length = 480;
 +
 +  struct chart_geometry geom;
 +  cairo_surface_t *surface;
 +  cairo_status_t status;
 +  const char *number_pos;
 +  char *file_name;
 +  cairo_t *cr;
 +
 +  number_pos = strchr (file_name_template, '#');
 +  if (number_pos != NULL)
 +    file_name = xasprintf ("%.*s%d%s", (int) (number_pos - file_name_template),
 +                           file_name_template, number, number_pos + 1);
 +  else
 +    file_name = xstrdup (file_name_template);
 +
 +  surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, length);
 +  cr = cairo_create (surface);
 +
 +  cairo_translate (cr, 0.0, length);
 +  cairo_scale (cr, 1.0, -1.0);
 +
 +  cairo_save (cr);
 +  cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
 +  cairo_rectangle (cr, 0, 0, width, length);
 +  cairo_fill (cr);
 +  cairo_restore (cr);
 +
 +  cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
 +
 +  chart_geometry_init (cr, &geom, width, length);
 +  chart_draw (chart, cr, &geom);
-   chart_geometry_free (cr);
++  chart_geometry_free (cr, &geom);
 +
 +  status = cairo_surface_write_to_png (surface, file_name);
 +  if (status != CAIRO_STATUS_SUCCESS)
 +    error (0, 0, _("writing output file \"%s\": %s"),
 +           file_name, cairo_status_to_string (status));
 +
 +  cairo_destroy (cr);
 +  cairo_surface_destroy (surface);
 +
 +  return file_name;
 +}
  
 -  for (i = 0 ; i < chart->n_datasets; ++i)
 -    free (chart->dataset[i]);
 -  free (chart->dataset);
  
 -  free(chart);
 +struct chart *
 +chart_ref (const struct chart *chart_)
 +{
 +  struct chart *chart = CONST_CAST (struct chart *, chart_);
 +  chart->ref_cnt++;
 +  return chart;
  }
  
  void
index 74840c30c5254e949d610a839d54047a0b46a093,cb2f346b1b4134a0092cb322e3fe2a7a16177c55..eabcf516e8e1cdc7b84a148646c3aef38de956bc
  #include <assert.h>
  
  #include <output/chart.h>
 -
 +#include <output/chart-provider.h>
  #include <output/charts/plot-chart.h>
 -#include <output/charts/cartesian.h>
  #include <libpspp/compiler.h>
  
- struct dataset
++#include "xalloc.h"
+ /* Start a new vector called NAME */
+ void
 -chart_vector_start (struct chart *ch, const char *name)
++chart_vector_start (cairo_t *cr, struct chart_geometry *geom, const char *name)
  {
-   int n_data;
-   const char *label;
- };
 -  if ( ! ch )
 -    return ;
++  const struct chart_colour *colour;
  
 -  pl_savestate_r (ch->lp);
++  cairo_save (cr);
  
- #define DATASETS 2
 -  pl_colorname_r (ch->lp, data_colour [ch->n_datasets % N_CHART_COLOURS]);
++  colour = &data_colour[geom->n_datasets % N_CHART_COLOURS];
++  cairo_set_source_rgb (cr,
++                        colour->red / 255.0,
++                        colour->green / 255.0,
++                        colour->blue / 255.0);
  
- static const struct dataset dataset[DATASETS] =
-   {
-     { 13, "male"},
-     { 11, "female"},
-   };
 -  ch->n_datasets++;
 -  ch->dataset = xrealloc (ch->dataset, ch->n_datasets * sizeof (*ch->dataset));
++  geom->n_datasets++;
++  geom->dataset = xrealloc (geom->dataset,
++                            geom->n_datasets * sizeof (*geom->dataset));
  
 -  ch->dataset[ch->n_datasets - 1] = strdup (name);
++  geom->dataset[geom->n_datasets - 1] = strdup (name);
+ }
  
  /* Plot a data point */
  void
 -chart_datum (struct chart *ch, int dataset UNUSED, double x, double y)
 +chart_datum (cairo_t *cr, const struct chart_geometry *geom,
 +             int dataset UNUSED, double x, double y)
  {
 -  if ( ! ch )
 -    return ;
 -
 -  {
 -    const double x_pos =
 -      (x - ch->x_min) * ch->abscissa_scale + ch->data_left ;
 +  double x_pos = (x - geom->x_min) * geom->abscissa_scale + geom->data_left;
 +  double y_pos = (y - geom->y_min) * geom->ordinate_scale + geom->data_bottom;
  
 -    const double y_pos =
 -      (y - ch->y_min) * ch->ordinate_scale + ch->data_bottom ;
 -
 -    pl_savestate_r(ch->lp);
 -
 -    pl_fmarker_r(ch->lp, x_pos, y_pos, 6, 15);
 -
 -    pl_restorestate_r(ch->lp);
 -  }
 +  chart_draw_marker (cr, x_pos, y_pos, MARKER_SQUARE, 15);
  }
  
 -chart_vector_end (struct chart *ch)
+ void
 -  pl_endpath_r (ch->lp);
 -  pl_colorname_r (ch->lp, "black");
 -  ch->in_path = false;
 -  pl_restorestate_r (ch->lp);
++chart_vector_end (cairo_t *cr, struct chart_geometry *geom)
+ {
 -chart_vector (struct chart *ch, double x, double y)
++  cairo_stroke (cr);
++  cairo_restore (cr);
++  geom->in_path = false;
+ }
+ /* Plot a data point */
+ void
 -  if ( ! ch )
 -    return ;
 -
 -  {
 -    const double x_pos =
 -      (x - ch->x_min) * ch->abscissa_scale + ch->data_left ;
 -
 -    const double y_pos =
 -      (y - ch->y_min) * ch->ordinate_scale + ch->data_bottom ;
 -
 -    if ( ch->in_path)
 -      pl_fcont_r (ch->lp, x_pos, y_pos);
 -    else
 -      {
 -      pl_fmove_r (ch->lp, x_pos, y_pos);
 -      ch->in_path = true;
 -      }
 -  }
++chart_vector (cairo_t *cr, struct chart_geometry *geom, double x, double y)
+ {
++  const double x_pos =
++    (x - geom->x_min) * geom->abscissa_scale + geom->data_left ;
++
++  const double y_pos =
++    (y - geom->y_min) * geom->ordinate_scale + geom->data_bottom ;
++
++  if (geom->in_path)
++    cairo_line_to (cr, x_pos, y_pos);
++  else
++    {
++      cairo_move_to (cr, x_pos, y_pos);
++      geom->in_path = true;
++    }
+ }
  /* Draw a line with slope SLOPE and intercept INTERCEPT.
     between the points limit1 and limit2.
     If lim_dim is CHART_DIM_Y then the limit{1,2} are on the
index feda1b6fb724d79201c1ee154d47827aa6e8c652,0874b9cc61d4d8f507188a00f07c44ca614176cf..3c21db6efc444c87cca867bbe184867dd1e707fe
@@@ -29,11 -26,13 +29,16 @@@ enum CHART_DI
      CHART_DIM_Y
    };
  
 +struct chart_geometry;
  
 -void chart_vector_start (struct chart *ch, const char *name);
 -void chart_vector (struct chart *ch, double x, double y);
 -void chart_vector_end (struct chart *ch);
++void  chart_vector_start (cairo_t *, struct chart_geometry *,
++                          const char *name);
++void chart_vector_end (cairo_t *, struct chart_geometry *);
++void chart_vector (cairo_t *, struct chart_geometry *, double x, double y);
  /* Plot a data point */
 -void chart_datum (struct chart *ch, int dataset UNUSED, double x, double y);
 +void chart_datum(cairo_t *, const struct chart_geometry *,
 +                 int dataset UNUSED, double x, double y);
  
  /* Draw a line with slope SLOPE and intercept INTERCEPT.
     between the points limit1 and limit2.
index 98e4c866e8a7afeae0b0d2653c2dcdc6103904d2,5641db1213be2cd070d66cffb7daf479823076f6..46884e19dfd3a1d662797be507199dbaa170a2c7
@@@ -253,38 -158,99 +253,84 @@@ chart_write_yscale (cairo_t *cr, struc
    double y;
  
    const double tick_interval =
 -    chart_rounded_tick(smax - smin) / (double) ticks);
 +    chart_rounded_tick ((smax - smin) / (double) ticks);
  
 -  if ( !ch )
 -        return;
 +  geom->y_max = ceil (smax / tick_interval) * tick_interval;
 +  geom->y_min = floor (smin / tick_interval) * tick_interval;
  
 -  ch->y_max = ceil  ( smax / tick_interval ) * tick_interval ;
 -  ch->y_min = floor ( smin / tick_interval ) * tick_interval ;
 +  geom->ordinate_scale =
 +    (fabs (geom->data_top - geom->data_bottom)
 +     / fabs (geom->y_max - geom->y_min));
  
 -  ch->ordinate_scale =
 -    fabs(ch->data_top -  ch->data_bottom) / fabs(ch->y_max - ch->y_min) ;
 -
 -  for(y = ch->y_min ; y <= ch->y_max; y += tick_interval )
 -    {
 -    draw_tick (ch, TICK_ORDINATE,
 -             (y - ch->y_min) * ch->ordinate_scale, "%g", y);
 -    }
 +  for (y = geom->y_min; y <= geom->y_max; y += tick_interval)
 +    draw_tick (cr, geom, TICK_ORDINATE,
 +             (y - geom->y_min) * geom->ordinate_scale, "%g", y);
  }
  
 -
  /* Write the abscissa label */
  void
 -chart_write_xlabel(struct chart *ch, const char *label)
 +chart_write_xlabel (cairo_t *cr, const struct chart_geometry *geom,
 +                    const char *label)
  {
 -  if ( ! ch )
 -    return ;
 -
 -  pl_savestate_r(ch->lp);
 -
 -  pl_move_r(ch->lp,ch->data_left, ch->abscissa_top);
 -  pl_alabel_r(ch->lp,0,'t',label);
 -
 -  pl_restorestate_r(ch->lp);
 -
 +  cairo_move_to (cr, geom->data_left, geom->abscissa_top);
 +  chart_label (cr, 'l', 't', geom->font_size, label);
  }
  
 -
 -
  /* Write the ordinate label */
  void
 -chart_write_ylabel(struct chart *ch, const char *label)
 +chart_write_ylabel (cairo_t *cr, const struct chart_geometry *geom,
 +                    const char *label)
  {
 -  if ( ! ch )
 -    return ;
 -
 -  pl_savestate_r(ch->lp);
 -
 -  pl_move_r(ch->lp, ch->data_bottom, ch->ordinate_right);
 -  pl_textangle_r(ch->lp, 90);
 -  pl_alabel_r(ch->lp, 0, 0, label);
 -
 -  pl_restorestate_r(ch->lp);
 +  cairo_save (cr);
 +  cairo_translate (cr, -geom->data_bottom, -geom->ordinate_right);
 +  cairo_move_to (cr, 0, 0);
 +  cairo_rotate (cr, M_PI / 2.0);
 +  chart_label (cr, 'l', 'x', geom->font_size, label);
 +  cairo_restore (cr);
  }
 -chart_write_legend (struct chart *ch)
+ void
 -  const int vstep = ch->font_size * 2;
++chart_write_legend (cairo_t *cr, const struct chart_geometry *geom)
+ {
+   int i;
 -  const int legend_top = ch->data_top;
++  const int vstep = geom->font_size * 2;
+   const int xpad = 10;
+   const int ypad = 10;
+   const int swatch = 20;
 -    (vstep * ch->n_datasets + 2 * ypad );
++  const int legend_top = geom->data_top;
+   const int legend_bottom = legend_top -
 -  if ( ! ch )
 -    return ;
++    (vstep * geom->n_datasets + 2 * ypad );
 -  pl_savestate_r (ch->lp);
++  cairo_save (cr);
 -  pl_box_r (ch->lp, ch->legend_left, legend_top,
 -          ch->legend_right - xpad, legend_bottom);
 -
 -  for (i = 0 ; i < ch->n_datasets ; ++i )
++  cairo_rectangle (cr, geom->legend_left, legend_top,
++                   geom->legend_right - xpad - geom->legend_left,
++                   legend_bottom - legend_top);
++  cairo_stroke (cr);
 -      const int ypos = vstep * (i + 1);
 -      const int xpos = ch->legend_left + xpad;
 -      pl_move_r (ch->lp, xpos, legend_top - ypos);
 -
 -      pl_savestate_r(ch->lp);
 -       pl_fillcolorname_r (ch->lp, data_colour [ i % N_CHART_COLOURS]);
 -
 -       pl_pentype_r (ch->lp, 1);
 -       pl_filltype_r (ch->lp, 1);
 -       pl_boxrel_r (ch->lp, 0, 0, swatch, swatch);
 -
 -
 -      pl_moverel_r (ch->lp, swatch, 0);
 -      pl_alabel_r (ch->lp, 0, 0, ch->dataset[i]);
 -
 -      pl_restorestate_r (ch->lp);
++  for (i = 0 ; i < geom->n_datasets ; ++i )
+     {
 -  pl_restorestate_r (ch->lp);
++      const int ypos = legend_top - vstep * (i + 1);
++      const int xpos = geom->legend_left + xpad;
++      const struct chart_colour *colour;
++
++      cairo_move_to (cr, xpos, ypos);
++
++      cairo_save (cr);
++      colour = &data_colour [ i % N_CHART_COLOURS];
++      cairo_set_source_rgb (cr,
++                            colour->red / 255.0,
++                            colour->green / 255.0,
++                            colour->blue / 255.0);
++      cairo_rectangle (cr, xpos, ypos, swatch, swatch);
++      cairo_fill_preserve (cr);
++      cairo_stroke (cr);
++      cairo_restore (cr);
++
++      cairo_move_to (cr, xpos + swatch * 1.5, ypos);
++      chart_label (cr, 'l', 'x', geom->font_size, geom->dataset[i]);
+     }
++  cairo_restore (cr);
+ }
index 03788f007507a0e2e0ad55e8c650460991e78a2a,f4cc5bbf06c3f91650c4988b15e76e36f8efc74a..896b630b137e7245f679c15c5437e7b8a47168fe
@@@ -83,14 -63,13 +83,16 @@@ void  chart_write_xscale(cairo_t *, str
  
  
  /* Set the scale for the ordinate */
 -void  chart_write_yscale(struct chart *ch, double smin, double smax, int ticks);
 +void  chart_write_yscale(cairo_t *, struct chart_geometry *,
 +                         double smin, double smax, int ticks);
  
 -void chart_write_xlabel(struct chart *ch, const char *label) ;
 +void chart_write_xlabel(cairo_t *, const struct chart_geometry *,
 +                        const char *label) ;
  
  /* Write the ordinate label */
 -void  chart_write_ylabel(struct chart *ch, const char *label);
 +void  chart_write_ylabel(cairo_t *, const struct chart_geometry *,
 +                         const char *label);
  
 -void chart_write_legend (struct chart *ch);
++void chart_write_legend (cairo_t *, const struct chart_geometry *);
  #endif
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..2094ede545c79edb0c264a2c63c0630cfef1339f
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,148 @@@
++/* PSPP - a program for statistical analysis.
++   Copyright (C) 2009 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/roc-chart.h>
++
++#include <output/chart-provider.h>
++#include <output/charts/cartesian.h>
++#include <output/charts/plot-chart.h>
++#include <data/casereader.h>
++#include <language/stats/roc.h>
++
++#include "xalloc.h"
++
++#include "gettext.h"
++#define _(msgid) gettext (msgid)
++
++struct roc_var
++  {
++    char *name;
++    struct casereader *cutpoint_reader;
++  };
++
++struct roc_chart
++  {
++    struct chart chart;
++    bool reference;
++    struct roc_var *vars;
++    size_t n_vars;
++    size_t allocated_vars;
++  };
++
++static const struct chart_class roc_chart_class;
++
++struct roc_chart *
++roc_chart_create (bool reference)
++{
++  struct roc_chart *rc = xmalloc (sizeof *rc);
++  chart_init (&rc->chart, &roc_chart_class);
++  rc->reference = reference;
++  rc->vars = NULL;
++  rc->n_vars = 0;
++  rc->allocated_vars = 0;
++  return rc;
++}
++
++void
++roc_chart_add_var (struct roc_chart *rc, const char *var_name,
++                   const struct casereader *cutpoint_reader)
++{
++  struct roc_var *rv;
++
++  if (rc->n_vars >= rc->allocated_vars)
++    rc->vars = x2nrealloc (rc->vars, &rc->allocated_vars, sizeof *rc->vars);
++
++  rv = &rc->vars[rc->n_vars++];
++  rv->name = xstrdup (var_name);
++  rv->cutpoint_reader = casereader_clone (cutpoint_reader);
++}
++
++struct chart *
++roc_chart_get_chart (struct roc_chart *rc)
++{
++  return &rc->chart;
++}
++
++static void
++roc_chart_draw (const struct chart *chart, cairo_t *cr,
++                struct chart_geometry *geom)
++{
++  const struct roc_chart *rc = UP_CAST (chart, struct roc_chart, chart);
++  size_t i;
++
++  chart_write_title (cr, geom, _("ROC Curve"));
++  chart_write_xlabel (cr, geom, _("1 - Specificity"));
++  chart_write_ylabel (cr, geom, _("Sensitivity"));
++
++  chart_write_xscale (cr, geom, 0, 1, 5);
++  chart_write_yscale (cr, geom, 0, 1, 5);
++
++  if ( rc->reference )
++    {
++      chart_line (cr, geom, 1.0, 0,
++                0.0, 1.0,
++                CHART_DIM_X);
++    }
++
++  for (i = 0; i < rc->n_vars; ++i)
++    {
++      const struct roc_var *rv = &rc->vars[i];
++      struct casereader *r = casereader_clone (rv->cutpoint_reader);
++      struct ccase *cc;
++
++      chart_vector_start (cr, geom, rv->name);
++      for (; (cc = casereader_read (r)) != NULL; case_unref (cc))
++      {
++        double se = case_data_idx (cc, ROC_TP)->f;
++        double sp = case_data_idx (cc, ROC_TN)->f;
++
++        se /= case_data_idx (cc, ROC_FN)->f + case_data_idx (cc, ROC_TP)->f ;
++        sp /= case_data_idx (cc, ROC_TN)->f + case_data_idx (cc, ROC_FP)->f ;
++
++        chart_vector (cr, geom, 1 - sp, se);
++      }
++      chart_vector_end (cr, geom);
++      casereader_destroy (r);
++    }
++
++  chart_write_legend (cr, geom);
++}
++
++static void
++roc_chart_destroy (struct chart *chart)
++{
++  struct roc_chart *rc = UP_CAST (chart, struct roc_chart, chart);
++  size_t i;
++
++  for (i = 0; i < rc->n_vars; i++)
++    {
++      struct roc_var *rv = &rc->vars[i];
++      free (rv->name);
++      casereader_destroy (rv->cutpoint_reader);
++    }
++  free (rc->vars);
++  free (rc);
++}
++
++static const struct chart_class roc_chart_class =
++  {
++    roc_chart_draw,
++    roc_chart_destroy
++  };
++
++
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..dca842071a667f4fe05c379096977202d5755ebf
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,29 @@@
++/* PSPP - a program for statistical analysis.
++   Copyright (C) 2009 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 OUTPUT_CHARTS_ROC_CHART_H
++#define OUTPUT_CHARTS_ROC_CHART_H 1
++
++#include <stdbool.h>
++
++struct casereader;
++
++struct roc_chart *roc_chart_create (bool reference);
++void roc_chart_add_var (struct roc_chart *, const char *var_name,
++                        const struct casereader *cutpoint_reader);
++struct chart *roc_chart_get_chart (struct roc_chart *);
++
++#endif /* output/charts/roc-chart.h */
Simple merge
Simple merge
index 3d3aed8813c9628b672bd6d853d9385af7caec41,11ec0603d4dfd98ee65954b1fed34af49feb4bc2..0c16bfa8140c0a78f4036c2c2ef2e3eeadb5023a
@@@ -581,12 -584,10 +587,10 @@@ cmptr_value_destroy (struct comparator 
  
  
  static struct comparator *
- value_comparator_create (const struct variable *var, const char *target)
+ value_comparator_create (const struct variable *var, const PsppireDict *dict, const char *target)
  {
-   const struct fmt_spec *fmt;
-   int width ;
    struct value_comparator *vc = xzalloc (sizeof (*vc));
 -  struct comparator *cmptr = (struct comparator *) vc;
 +  struct comparator *cmptr = &vc->parent;
  
    cmptr->flags = 0;
    cmptr->var = var;
Simple merge