From: Ben Pfaff Date: Tue, 11 Aug 2009 21:18:26 +0000 (-0700) Subject: Merge master into output branch. X-Git-Tag: sid-i386-build98~5 X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?p=pspp-builds.git;a=commitdiff_plain;h=cb72db62c20ecab427229110820c5b053d0663c4 Merge master into output branch. --- cb72db62c20ecab427229110820c5b053d0663c4 diff --cc src/language/data-io/list.q index 5f1b3a99,c3f9b088..614fa127 --- a/src/language/data-io/list.q +++ b/src/language/data-io/list.q @@@ -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 (" \n", x->file); + fputs (" \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 (" ", x->file); - html_put_cell_contents (d, TAB_FIX, ss_buffer (s, print->w)); - free (s); - fputs ("\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 (" ", 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 ("\n", x->file); ++ ++ free (s); + } - fputs (" \n", x->file); - } - else - NOT_REACHED (); + fputs (" \n", x->file); + } + else + NOT_REACHED (); + } } + /* Local Variables: mode: c diff --cc src/language/dictionary/split-file.c index 5d3650e3,5d2b42d7..d2f59ccc --- a/src/language/dictionary/split-file.c +++ b/src/language/dictionary/split-file.c @@@ -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); diff --cc src/language/stats/crosstabs.q index 7db55a2c,99fa41d9..5695ed64 --- a/src/language/stats/crosstabs.q +++ b/src/language/stats/crosstabs.q @@@ -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. */ diff --cc src/language/stats/roc.c index 00000000,1d61a55c..1d21a4f6 mode 000000,100644..100644 --- a/src/language/stats/roc.c +++ b/src/language/stats/roc.c @@@ -1,0 -1,1227 +1,1179 @@@ + /* 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 . */ + + #include + ++#include ++ + #include + #include + #include + #include + #include + + #include + #include + #include + #include + #include + #include + #include + + + #include + + #include + #include + -#include -#include ++#include ++#include + + #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; + }; + -#define CUTPOINT 0 -#define TP 1 -#define FN 2 -#define TN 3 -#define FP 4 - - + /* + 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; - const double cp = case_data_idx (cpc, CUTPOINT)->f; ++ 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, - TP, FN); ++ 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, - TN, FP); ++ ROC_TN, ROC_FP); + } + + + + + static void + append_cutpoint (struct casewriter *writer, double cutpoint) + { + struct ccase *cc = case_create (casewriter_get_proto (writer)); + - 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; ++ 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 - be created with width 5, ready to take the values (cutpoint, TP, FN, TN, FP), and the ++ 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. - However on exit from this function, only CUTPOINT entries will be set to their final ++ 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; - subcase_init (&ordering, CUTPOINT, 0, SC_ASCEND); ++ subcase_init (&ordering, ROC_CUTPOINT, 0, SC_ASCEND); + + proto = caseproto_add_width (proto, 0); /* cutpoint */ - 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 */ ++ 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; - struct tab_table *tbl = tab_create (n_cols, n_rows, 0); ++ 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); + - tab_dim (tbl, tab_natural_dimensions, NULL); ++ 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; - struct tab_table *tbl = tab_create (n_cols, n_rows, 0); ++ struct tab_table *tbl = tab_create (n_cols, n_rows); + + tab_title (tbl, _("Case Summary")); + + tab_headers (tbl, 1, 0, 2, 0); + - tab_dim (tbl, tab_natural_dimensions, NULL); ++ 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); + - tbl = tab_create (n_cols, n_rows, 0); ++ 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); + - tab_dim (tbl, tab_natural_dimensions, NULL); ++ 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++) + { - const double se = case_data_idx (cc, TP)->f / ++ const double se = case_data_idx (cc, ROC_TP)->f / + ( - case_data_idx (cc, TP)->f ++ case_data_idx (cc, ROC_TP)->f + + - case_data_idx (cc, FN)->f ++ case_data_idx (cc, ROC_FN)->f + ); + - const double sp = case_data_idx (cc, TN)->f / ++ const double sp = case_data_idx (cc, ROC_TN)->f / + ( - case_data_idx (cc, TN)->f ++ case_data_idx (cc, ROC_TN)->f + + - case_data_idx (cc, FP)->f ++ case_data_idx (cc, ROC_FP)->f + ); + - tab_double (tbl, n_cols - 3, x, 0, case_data_idx (cc, CUTPOINT)->f, ++ 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); + } + + -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); -} - - + static void + output_roc (struct roc_state *rs, const struct cmd_roc *roc) + { + show_summary (roc); + + if ( roc->curve ) - draw_roc (rs, roc); ++ { ++ 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); + } + diff --cc src/language/stats/roc.h index 00000000,00000000..5d63c96f new file mode 100644 --- /dev/null +++ b/src/language/stats/roc.h @@@ -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 . */ ++ ++#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 */ diff --cc src/output/automake.mk index 34aac525,ec92c559..eb9f7b78 --- a/src/output/automake.mk +++ b/src/output/automake.mk @@@ -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 \ diff --cc src/output/cairo.c index a2d5fef9,00000000..b0b4f7d3 mode 100644,000000..100644 --- a/src/output/cairo.c +++ b/src/output/cairo.c @@@ -1,911 -1,0 +1,911 @@@ +/* 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 . */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); + +/* 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; +} + +/* 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); ++ chart_geometry_free (x->cairo, &geom); + cairo_restore (x->cairo); + + outp_close_page (this); +} + +/* 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); +} + +/* 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); +} + +/* 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, +}; diff --cc src/output/chart-provider.h index 6065036d,00000000..9becb6f5 mode 100644,000000..100644 --- a/src/output/chart-provider.h +++ b/src/output/chart-provider.h @@@ -1,85 -1,0 +1,89 @@@ +/* 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 . */ + +#ifndef OUTPUT_CHART_PROVIDER_H +#define OUTPUT_CHART_PROVIDER_H 1 + +#include +#include +#include +#include + +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 *); ++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 */ diff --cc src/output/chart.c index 1f85d8e4,e324901c..e31422bc --- a/src/output/chart.c +++ b/src/output/chart.c @@@ -41,110 -41,99 +41,118 @@@ 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) { + int i; - 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); + - 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 diff --cc src/output/charts/cartesian.c index 74840c30,cb2f346b..eabcf516 --- a/src/output/charts/cartesian.c +++ b/src/output/charts/cartesian.c @@@ -24,37 -21,86 +24,73 @@@ #include #include - +#include #include -#include #include - 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); } + void -chart_vector_end (struct chart *ch) ++chart_vector_end (cairo_t *cr, struct chart_geometry *geom) + { - pl_endpath_r (ch->lp); - pl_colorname_r (ch->lp, "black"); - ch->in_path = false; - pl_restorestate_r (ch->lp); ++ cairo_stroke (cr); ++ cairo_restore (cr); ++ geom->in_path = false; + } + + /* Plot a data point */ + void -chart_vector (struct chart *ch, double x, double y) ++chart_vector (cairo_t *cr, struct chart_geometry *geom, double x, double y) + { - 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; - } - } ++ 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 diff --cc src/output/charts/cartesian.h index feda1b6f,0874b9cc..3c21db6e --- a/src/output/charts/cartesian.h +++ b/src/output/charts/cartesian.h @@@ -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. diff --cc src/output/charts/plot-chart.c index 98e4c866,5641db12..46884e19 --- a/src/output/charts/plot-chart.c +++ b/src/output/charts/plot-chart.c @@@ -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); } + + + void -chart_write_legend (struct chart *ch) ++chart_write_legend (cairo_t *cr, const struct chart_geometry *geom) + { + int i; - const int vstep = ch->font_size * 2; ++ const int vstep = geom->font_size * 2; + const int xpad = 10; + const int ypad = 10; + const int swatch = 20; - const int legend_top = ch->data_top; ++ const int legend_top = geom->data_top; + const int legend_bottom = legend_top - - (vstep * ch->n_datasets + 2 * ypad ); ++ (vstep * geom->n_datasets + 2 * ypad ); + - if ( ! ch ) - return ; ++ cairo_save (cr); + - pl_savestate_r (ch->lp); ++ cairo_rectangle (cr, geom->legend_left, legend_top, ++ geom->legend_right - xpad - geom->legend_left, ++ legend_bottom - legend_top); ++ cairo_stroke (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 ) ++ for (i = 0 ; i < geom->n_datasets ; ++i ) + { - 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); ++ 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]); + } + - pl_restorestate_r (ch->lp); ++ cairo_restore (cr); + } diff --cc src/output/charts/plot-chart.h index 03788f00,f4cc5bbf..896b630b --- a/src/output/charts/plot-chart.h +++ b/src/output/charts/plot-chart.h @@@ -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 diff --cc src/output/charts/roc-chart.c index 00000000,00000000..2094ede5 new file mode 100644 --- /dev/null +++ b/src/output/charts/roc-chart.c @@@ -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 . */ ++ ++#include ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#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 ++ }; ++ ++ diff --cc src/output/charts/roc-chart.h index 00000000,00000000..dca84207 new file mode 100644 --- /dev/null +++ b/src/output/charts/roc-chart.h @@@ -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 . */ ++ ++#ifndef OUTPUT_CHARTS_ROC_CHART_H ++#define OUTPUT_CHARTS_ROC_CHART_H 1 ++ ++#include ++ ++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 */ diff --cc src/ui/gui/find-dialog.c index 3d3aed88,11ec0603..0c16bfa8 --- a/src/ui/gui/find-dialog.c +++ b/src/ui/gui/find-dialog.c @@@ -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;