From ccbfcb82b48b6faf8cd8f8f5a635c9f53f05fd98 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Fri, 3 Jul 2009 22:33:01 -0700 Subject: [PATCH] output: Make box-whisker plots work again. --- src/language/stats/examine.q | 154 ++++++++---------------- src/output/charts/automake.mk | 2 + src/output/charts/box-whisker.c | 207 +++++++++++++++++++++++--------- src/output/charts/box-whisker.h | 15 +-- 4 files changed, 204 insertions(+), 174 deletions(-) diff --git a/src/language/stats/examine.q b/src/language/stats/examine.q index f0d831ba..e39dc8d3 100644 --- a/src/language/stats/examine.q +++ b/src/language/stats/examine.q @@ -549,30 +549,22 @@ show_boxplot_groups (const struct variable **dependent_var, int n_dep_var, const struct xfactor *fctr) { -#if 0 int v; for (v = 0; v < n_dep_var; ++v) { - struct ll *ll; - int f = 0; - struct chart *ch = chart_create (); + const struct factor_result *result; + struct boxplot *boxplot; double y_min = DBL_MAX; double y_max = -DBL_MAX; + char *title; - for (ll = ll_head (&fctr->result_list); - ll != ll_null (&fctr->result_list); - ll = ll_next (ll)) + ll_for_each (result, struct factor_result, ll, &fctr->result_list) { + struct factor_metrics *metrics = &result->metrics[v]; + const struct ll_list *max_list = extrema_list (metrics->maxima); + const struct ll_list *min_list = extrema_list (metrics->minima); const struct extremum *max, *min; - const struct factor_result *result = - ll_data (ll, struct factor_result, ll); - - const struct ll_list *max_list = - extrema_list (result->metrics[v].maxima); - - const struct ll_list *min_list = - extrema_list (result->metrics[v].minima); if ( ll_is_empty (max_list)) { @@ -580,54 +572,37 @@ show_boxplot_groups (const struct variable **dependent_var, continue; } - max = (const struct extremum *) - ll_data (ll_head(max_list), struct extremum, ll); - - min = (const struct extremum *) - ll_data (ll_head (min_list), struct extremum, ll); + max = ll_data (ll_head(max_list), struct extremum, ll); + min = ll_data (ll_head (min_list), struct extremum, ll); y_max = MAX (y_max, max->value); y_min = MIN (y_min, min->value); } - boxplot_draw_yscale (ch, y_max, y_min); - - if ( fctr->indep_var[0]) - chart_write_title (ch, _("Boxplot of %s vs. %s"), + if (fctr->indep_var[0]) + title = xasprintf (_("Boxplot of %s vs. %s"), var_to_string (dependent_var[v]), - var_to_string (fctr->indep_var[0]) ); + var_to_string (fctr->indep_var[0])); else - chart_write_title (ch, _("Boxplot of %s"), - var_to_string (dependent_var[v])); + title = xasprintf (_("Boxplot of %s"), + var_to_string (dependent_var[v])); + boxplot = boxplot_create (y_min, y_max, title); + free (title); - for (ll = ll_head (&fctr->result_list); - ll != ll_null (&fctr->result_list); - ll = ll_next (ll)) + ll_for_each (result, struct factor_result, ll, &fctr->result_list) { - const struct factor_result *result = - ll_data (ll, struct factor_result, ll); - - struct string str; - const double box_width = (ch->data_right - ch->data_left) - / (ll_count (&fctr->result_list) * 2.0 ) ; - - const double box_centre = (f++ * 2 + 1) * box_width + ch->data_left; - - ds_init_empty (&str); + struct factor_metrics *metrics = &result->metrics[v]; + struct string str = DS_EMPTY_INITIALIZER; factor_to_string_concise (fctr, result, &str); - - boxplot_draw_boxplot (ch, - box_centre, box_width, - (const struct box_whisker *) - result->metrics[v].box_whisker, - ds_cstr (&str)); - + boxplot_add_box (boxplot, + (struct box_whisker *) metrics->box_whisker, + ds_cstr (&str)); + metrics->box_whisker = NULL; ds_destroy (&str); } - chart_submit (ch); + chart_submit (boxplot_get_chart (boxplot)); } -#endif } @@ -639,77 +614,44 @@ show_boxplot_variables (const struct variable **dependent_var, ) { -#if 0 + const struct factor_result *result; int v; - struct ll *ll; - const struct ll_list *result_list = &fctr->result_list; - - for (ll = ll_head (result_list); - ll != ll_null (result_list); - ll = ll_next (ll)) + ll_for_each (result, struct factor_result, ll, &fctr->result_list) { struct string title; - struct chart *ch = chart_create (); double y_min = DBL_MAX; double y_max = -DBL_MAX; - - const struct factor_result *result = - ll_data (ll, struct factor_result, ll); - - const double box_width = (ch->data_right - ch->data_left) - / (n_dep_var * 2.0 ) ; + struct boxplot *boxplot; for (v = 0; v < n_dep_var; ++v) { - const struct ll *max_ll = - ll_head (extrema_list (result->metrics[v].maxima)); - const struct ll *min_ll = - ll_head (extrema_list (result->metrics[v].minima)); - - const struct extremum *max = - (const struct extremum *) ll_data (max_ll, struct extremum, ll); - - const struct extremum *min = - (const struct extremum *) ll_data (min_ll, struct extremum, ll); + const struct factor_metrics *metrics = &result->metrics[v]; + const struct ll *max_ll = ll_head (extrema_list (metrics->maxima)); + const struct ll *min_ll = ll_head (extrema_list (metrics->minima)); + const struct extremum *max = ll_data (max_ll, struct extremum, ll); + const struct extremum *min = ll_data (min_ll, struct extremum, ll); y_max = MAX (y_max, max->value); y_min = MIN (y_min, min->value); } - - boxplot_draw_yscale (ch, y_max, y_min); - ds_init_empty (&title); factor_to_string (fctr, result, &title); - -#if 0 - ds_put_format (&title, "%s = ", var_get_name (fctr->indep_var[0])); - var_append_value_name (fctr->indep_var[0], &result->value[0], &title); -#endif - - chart_write_title (ch, "%s", ds_cstr (&title)); + boxplot = boxplot_create (y_min, y_max, ds_cstr (&title)); ds_destroy (&title); for (v = 0; v < n_dep_var; ++v) { - struct string str; - const double box_centre = (v * 2 + 1) * box_width + ch->data_left; - - ds_init_empty (&str); - ds_init_cstr (&str, var_get_name (dependent_var[v])); - - boxplot_draw_boxplot (ch, - box_centre, box_width, - (const struct box_whisker *) result->metrics[v].box_whisker, - ds_cstr (&str)); - - ds_destroy (&str); + struct factor_metrics *metrics = &result->metrics[v]; + boxplot_add_box (boxplot, + (struct box_whisker *) metrics->box_whisker, + var_get_name (dependent_var[v])); + metrics->box_whisker = NULL; } - chart_submit (ch); + chart_submit (boxplot_get_chart (boxplot)); } -#endif } @@ -757,16 +699,14 @@ output_examine (const struct dictionary *dict) if ( cmd.sbc_percentiles) show_percentiles (dependent_vars, n_dependent_vars, factor); - if (cmd.a_plot[XMN_PLT_BOXPLOT] && - cmd.cmp == XMN_GROUPS) - show_boxplot_groups (dependent_vars, n_dependent_vars, factor); - - - if (cmd.a_plot[XMN_PLT_BOXPLOT] && - cmd.cmp == XMN_VARIABLES) - show_boxplot_variables (dependent_vars, n_dependent_vars, - factor); - + if (cmd.a_plot[XMN_PLT_BOXPLOT]) + { + if (cmd.cmp == XMN_GROUPS) + show_boxplot_groups (dependent_vars, n_dependent_vars, factor); + else if (cmd.cmp == XMN_VARIABLES) + show_boxplot_variables (dependent_vars, n_dependent_vars, factor); + } + if (cmd.a_plot[XMN_PLT_HISTOGRAM]) show_histogram (dependent_vars, n_dependent_vars, factor); diff --git a/src/output/charts/automake.mk b/src/output/charts/automake.mk index e260f328..b2a65636 100644 --- a/src/output/charts/automake.mk +++ b/src/output/charts/automake.mk @@ -3,6 +3,8 @@ noinst_LTLIBRARIES += src/output/charts/libcharts.la chart_sources = \ + 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/piechart.c \ diff --git a/src/output/charts/box-whisker.c b/src/output/charts/box-whisker.c index 33c445b0..301d762b 100644 --- a/src/output/charts/box-whisker.c +++ b/src/output/charts/box-whisker.c @@ -17,49 +17,99 @@ #include +#include + #include #include -#include - -#include -#include -#include +#include #include #include +#include +#include +#include /* Draw a box-and-whiskers plot */ +struct box + { + struct box_whisker *bw; + char *label; + }; + +struct boxplot + { + struct chart chart; + double y_min; + double y_max; + char *title; + struct box *boxes; + size_t n_boxes, boxes_allocated; + }; + +static const struct chart_class boxplot_chart_class; + +struct boxplot * +boxplot_create (double y_min, double y_max, const char *title) +{ + struct boxplot *boxplot = xmalloc (sizeof *boxplot); + chart_init (&boxplot->chart, &boxplot_chart_class); + boxplot->y_min = y_min; + boxplot->y_max = y_max; + boxplot->title = xstrdup (title); + boxplot->boxes = NULL; + boxplot->n_boxes = boxplot->boxes_allocated = 0; + return boxplot; +} + +void +boxplot_add_box (struct boxplot *boxplot, + struct box_whisker *bw, const char *label) +{ + struct box *box; + if (boxplot->n_boxes >= boxplot->boxes_allocated) + boxplot->boxes = x2nrealloc (boxplot->boxes, &boxplot->boxes_allocated, + sizeof *boxplot->boxes); + box = &boxplot->boxes[boxplot->n_boxes++]; + box->bw = bw; + box->label = xstrdup (label); +} + +struct chart * +boxplot_get_chart (struct boxplot *boxplot) +{ + return &boxplot->chart; +} + /* Draw an OUTLIER on the plot CH * at CENTRELINE */ static void -draw_case (struct chart *ch, double centreline, +draw_case (plPlotter *lp, const struct chart_geometry *geom, double centreline, const struct outlier *outlier) { #define MARKER_CIRCLE 4 #define MARKER_STAR 3 - pl_fmarker_r(ch->lp, + pl_fmarker_r(lp, centreline, - ch->data_bottom + (outlier->value - ch->y_min) * ch->ordinate_scale, + geom->data_bottom + (outlier->value - geom->y_min) * geom->ordinate_scale, outlier->extreme ? MARKER_STAR : MARKER_CIRCLE, 20); - pl_moverel_r(ch->lp, 10,0); + pl_moverel_r(lp, 10,0); - pl_alabel_r(ch->lp, 'l', 'c', ds_cstr (&outlier->label)); + pl_alabel_r(lp, 'l', 'c', ds_cstr (&outlier->label)); } - -void -boxplot_draw_boxplot (struct chart *ch, - double box_centre, - double box_width, - const struct box_whisker *bw, - const char *name) +static void +boxplot_draw_box (plPlotter *lp, const struct chart_geometry *geom, + double box_centre, + double box_width, + const struct box_whisker *bw, + const char *name) { double whisker[2]; double hinge[3]; @@ -79,48 +129,48 @@ boxplot_draw_boxplot (struct chart *ch, box_whisker_whiskers (bw, whisker); box_whisker_hinges (bw, hinge); - box_bottom = ch->data_bottom + (hinge[0] - ch->y_min ) * ch->ordinate_scale; + box_bottom = geom->data_bottom + (hinge[0] - geom->y_min ) * geom->ordinate_scale; - box_top = ch->data_bottom + (hinge[2] - ch->y_min ) * ch->ordinate_scale; + box_top = geom->data_bottom + (hinge[2] - geom->y_min ) * geom->ordinate_scale; - bottom_whisker = ch->data_bottom + (whisker[0] - ch->y_min) * - ch->ordinate_scale; + bottom_whisker = geom->data_bottom + (whisker[0] - geom->y_min) * + geom->ordinate_scale; - top_whisker = ch->data_bottom + (whisker[1] - ch->y_min) * ch->ordinate_scale; + top_whisker = geom->data_bottom + (whisker[1] - geom->y_min) * geom->ordinate_scale; - pl_savestate_r(ch->lp); + pl_savestate_r(lp); /* Draw the box */ - pl_savestate_r (ch->lp); - pl_fillcolorname_r (ch->lp, ch->fill_colour); - pl_filltype_r (ch->lp,1); - pl_fbox_r (ch->lp, + pl_savestate_r (lp); + pl_fillcolorname_r (lp, geom->fill_colour); + pl_filltype_r (lp,1); + pl_fbox_r (lp, box_left, box_bottom, box_right, box_top); - pl_restorestate_r (ch->lp); + pl_restorestate_r (lp); /* Draw the median */ - pl_savestate_r (ch->lp); - pl_linewidth_r (ch->lp, 5); - pl_fline_r (ch->lp, + pl_savestate_r (lp); + pl_linewidth_r (lp, 5); + pl_fline_r (lp, box_left, - ch->data_bottom + (hinge[1] - ch->y_min) * ch->ordinate_scale, + geom->data_bottom + (hinge[1] - geom->y_min) * geom->ordinate_scale, box_right, - ch->data_bottom + (hinge[1] - ch->y_min) * ch->ordinate_scale); - pl_restorestate_r (ch->lp); + geom->data_bottom + (hinge[1] - geom->y_min) * geom->ordinate_scale); + pl_restorestate_r (lp); /* Draw the bottom whisker */ - pl_fline_r (ch->lp, + pl_fline_r (lp, box_left, bottom_whisker, box_right, bottom_whisker); /* Draw top whisker */ - pl_fline_r (ch->lp, + pl_fline_r (lp, box_left, top_whisker, box_right, @@ -129,12 +179,12 @@ boxplot_draw_boxplot (struct chart *ch, /* Draw centre line. (bottom half) */ - pl_fline_r (ch->lp, + pl_fline_r (lp, box_centre, bottom_whisker, box_centre, box_bottom); /* (top half) */ - pl_fline_r (ch->lp, + pl_fline_r (lp, box_centre, top_whisker, box_centre, box_top); @@ -143,42 +193,85 @@ boxplot_draw_boxplot (struct chart *ch, ll != ll_null (outliers); ll = ll_next (ll)) { const struct outlier *outlier = ll_data (ll, struct outlier, ll); - draw_case (ch, box_centre, outlier); + draw_case (lp, geom, box_centre, outlier); } /* Draw tick mark on x axis */ - draw_tick(ch, TICK_ABSCISSA, box_centre - ch->data_left, "%s", name); + draw_tick(lp, geom, TICK_ABSCISSA, box_centre - geom->data_left, "%s", name); - pl_restorestate_r(ch->lp); + pl_restorestate_r(lp); } -void -boxplot_draw_yscale (struct chart *ch, double y_max, double y_min) +static void +boxplot_draw_yscale (plPlotter *lp, struct chart_geometry *geom, + double y_max, double y_min) { double y_tick; double d; - if ( !ch ) - return ; - - ch->y_max = y_max; - ch->y_min = y_min; + geom->y_max = y_max; + geom->y_min = y_min; - y_tick = chart_rounded_tick (fabs(ch->y_max - ch->y_min) / 5.0); + y_tick = chart_rounded_tick (fabs (geom->y_max - geom->y_min) / 5.0); - ch->y_min = (ceil( ch->y_min / y_tick ) - 1.0 ) * y_tick; + geom->y_min = (ceil (geom->y_min / y_tick) - 1.0) * y_tick; - ch->y_max = ( floor( ch->y_max / y_tick ) + 1.0 ) * y_tick; + geom->y_max = (floor (geom->y_max / y_tick) + 1.0) * y_tick; - ch->ordinate_scale = fabs(ch->data_top - ch->data_bottom) - / fabs(ch->y_max - ch->y_min) ; + geom->ordinate_scale = (fabs (geom->data_top - geom->data_bottom) + / fabs (geom->y_max - geom->y_min)); /* Move to data bottom-left */ - pl_move_r(ch->lp, - ch->data_left, ch->data_bottom); + pl_move_r (lp, geom->data_left, geom->data_bottom); + + for (d = geom->y_min; d <= geom->y_max; d += y_tick) + draw_tick (lp, geom, TICK_ORDINATE, + (d - geom->y_min) * geom->ordinate_scale, "%g", d); +} + +static void +boxplot_chart_draw (const struct chart *chart, plPlotter *lp) +{ + const struct boxplot *boxplot = (struct boxplot *) chart; + struct chart_geometry geom; + double box_width; + size_t i; - for ( d = ch->y_min; d <= ch->y_max ; d += y_tick ) + chart_geometry_init (lp, &geom); + boxplot_draw_yscale (lp, &geom, boxplot->y_max, boxplot->y_min); + chart_write_title (lp, &geom, "%s", boxplot->title); + + box_width = (geom.data_right - geom.data_left) / boxplot->n_boxes / 2.0; + for (i = 0; i < boxplot->n_boxes; i++) + { + const struct box *box = &boxplot->boxes[i]; + const double box_centre = (i * 2 + 1) * box_width + geom.data_left; + boxplot_draw_box (lp, &geom, box_centre, box_width, box->bw, box->label); + } + + chart_geometry_free (lp); +} + +static void +boxplot_chart_destroy (struct chart *chart) +{ + struct boxplot *boxplot = (struct boxplot *) chart; + size_t i; + + free (boxplot->title); + for (i = 0; i < boxplot->n_boxes; i++) { - draw_tick (ch, TICK_ORDINATE, (d - ch->y_min ) * ch->ordinate_scale, "%g", d); + struct box *box = &boxplot->boxes[i]; + struct statistic *statistic = &box->bw->parent.parent; + statistic->destroy (statistic); + free (box->label); } + free (boxplot->boxes); + free (boxplot); } + +static const struct chart_class boxplot_chart_class = + { + boxplot_chart_draw, + boxplot_chart_destroy + }; diff --git a/src/output/charts/box-whisker.h b/src/output/charts/box-whisker.h index 7b2c4b8f..67c1e128 100644 --- a/src/output/charts/box-whisker.h +++ b/src/output/charts/box-whisker.h @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 2004 Free Software Foundation, Inc. + 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 @@ -17,16 +17,11 @@ #ifndef BOX_WHISKER_H #define BOX_WHISKER_H -struct chart ; struct box_whisker; -void boxplot_draw_boxplot (struct chart *ch, - double box_centre, - double box_width, - const struct box_whisker *w, - const char *name); - - -void boxplot_draw_yscale (struct chart *ch , double y_max, double y_min); +struct boxplot *boxplot_create (double y_min, double y_max, const char *title); +void boxplot_add_box (struct boxplot *, + struct box_whisker *, const char *label); +struct chart *boxplot_get_chart (struct boxplot *); #endif -- 2.30.2