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