From a2a92dd38cf37e50ccfd56bedd4da21bc56dfe0f Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Thu, 2 Jul 2009 16:59:08 -0700 Subject: [PATCH] output: Refactor implementation of charts. This commit addresses a weakness of the charts implementation in PSPP, in that charts can only be sent to a single output device. This is because the chart code calls into a single output driver to obtain a libplot plotting context and then passes that back into the calling code, which draws on it and passes it back to the output driver. This commit refactors the code so that, instead, a chart is an ADT that is reponsible for knowing how to draw itself on a plotting device. Then the function for outputting a chart applies this function to each output device. This is more complicated than necessary: we could just loop over the set of output devices and draw a chart on each one in turn. But this level of complication is being introduced now so that later we can keep around charts in memory as long as necessary for the GUI output engine to export them on user demand. This commit introduces a couple of regressions: * libplot is now required, not optional. * Box-whisker plots are disabled. This regressions will be fixed by later commits. --- src/language/stats/examine.q | 190 +++++++++++++++++++-------- src/language/stats/frequencies.q | 13 +- src/output/ascii.c | 47 +++---- src/output/cairo.c | 37 +----- src/output/chart-provider.h | 74 +++++++++++ src/output/chart.c | 203 ++++++++++++++++------------- src/output/chart.h | 82 ++---------- src/output/charts/automake.mk | 4 - src/output/charts/cartesian.c | 62 ++++----- src/output/charts/cartesian.h | 14 +- src/output/charts/piechart.c | 188 +++++++++++++++----------- src/output/charts/piechart.h | 5 +- src/output/charts/plot-chart.c | 190 ++++++++++++--------------- src/output/charts/plot-chart.h | 23 ++-- src/output/charts/plot-hist.c | 217 ++++++++++++++++++------------- src/output/charts/plot-hist.h | 27 ++-- src/output/html.c | 52 ++++---- src/output/manager.c | 2 +- src/output/manager.h | 3 +- src/output/output.h | 4 +- src/output/postscript.c | 160 ++++++++++++----------- 21 files changed, 854 insertions(+), 743 deletions(-) create mode 100644 src/output/chart-provider.h diff --git a/src/language/stats/examine.q b/src/language/stats/examine.q index f3fec17b..f0d831ba 100644 --- a/src/language/stats/examine.q +++ b/src/language/stats/examine.q @@ -48,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -320,26 +321,32 @@ cmd_examine (struct lexer *lexer, struct dataset *ds) }; +struct np_plot_chart + { + struct chart chart; + char *label; + struct casereader *data; + + /* Copied directly from struct np. */ + double y_min, y_max; + double dns_min, dns_max; + + /* Calculated. */ + double slope, intercept; + double y_first, y_last; + double x_lower, x_upper; + double slack; + }; + +static const struct chart_class np_plot_chart_class; +static const struct chart_class dnp_plot_chart_class; + /* Plot the normal and detrended normal plots for RESULT. Label the plots with LABEL */ static void np_plot (struct np *np, const char *label) { - double yfirst = 0, ylast = 0; - - double x_lower; - double x_upper; - double slack; - - /* Normal Plot */ - struct chart *np_chart; - - /* Detrended Normal Plot */ - struct chart *dnp_chart; - - /* The slope and intercept of the ideal normal probability line */ - const double slope = 1.0 / np->stddev; - const double intercept = -np->mean / np->stddev; + struct np_plot_chart *np_plot, *dnp_plot; if ( np->n < 1.0 ) { @@ -347,56 +354,118 @@ np_plot (struct np *np, const char *label) return ; } - np_chart = chart_create (); - dnp_chart = chart_create (); - - if ( !np_chart || ! dnp_chart ) - return ; + np_plot = xmalloc (sizeof *np_plot); + chart_init (&np_plot->chart, &np_plot_chart_class); + np_plot->label = xstrdup (label); + np_plot->data = casewriter_make_reader (np->writer); + np_plot->y_min = np->y_min; + np_plot->y_max = np->y_max; + np_plot->dns_min = np->dns_min; + np_plot->dns_max = np->dns_max; - chart_write_title (np_chart, _("Normal Q-Q Plot of %s"), label); - chart_write_xlabel (np_chart, _("Observed Value")); - chart_write_ylabel (np_chart, _("Expected Normal")); + /* Slope and intercept of the ideal normal probability line. */ + np_plot->slope = 1.0 / np->stddev; + np_plot->intercept = -np->mean / np->stddev; - chart_write_title (dnp_chart, _("Detrended Normal Q-Q Plot of %s"), - label); - chart_write_xlabel (dnp_chart, _("Observed Value")); - chart_write_ylabel (dnp_chart, _("Dev from Normal")); - - yfirst = gsl_cdf_ugaussian_Pinv (1 / (np->n + 1)); - ylast = gsl_cdf_ugaussian_Pinv (np->n / (np->n + 1)); + np_plot->y_first = gsl_cdf_ugaussian_Pinv (1 / (np->n + 1)); + np_plot->y_last = gsl_cdf_ugaussian_Pinv (np->n / (np->n + 1)); /* Need to make sure that both the scatter plot and the ideal fit into the plot */ - x_lower = MIN (np->y_min, (yfirst - intercept) / slope) ; - x_upper = MAX (np->y_max, (ylast - intercept) / slope) ; - slack = (x_upper - x_lower) * 0.05 ; + np_plot->x_lower = MIN ( + np->y_min, (np_plot->y_first - np_plot->intercept) / np_plot->slope); + np_plot->x_upper = MAX ( + np->y_max, (np_plot->y_last - np_plot->intercept) / np_plot->slope) ; + np_plot->slack = (np_plot->x_upper - np_plot->x_lower) * 0.05 ; + + dnp_plot = xmemdup (np_plot, sizeof *np_plot); + chart_init (&dnp_plot->chart, &dnp_plot_chart_class); + dnp_plot->label = xstrdup (dnp_plot->label); + dnp_plot->data = casereader_clone (dnp_plot->data); + + chart_submit (&np_plot->chart); + chart_submit (&dnp_plot->chart); +} - chart_write_xscale (np_chart, x_lower - slack, x_upper + slack, 5); - chart_write_xscale (dnp_chart, np->y_min, np->y_max, 5); +static void +np_plot_chart_draw (const struct chart *chart, plPlotter *lp) +{ + const struct np_plot_chart *plot = (struct np_plot_chart *) chart; + struct chart_geometry geom; + struct casereader *data; + struct ccase *c; - chart_write_yscale (np_chart, yfirst, ylast, 5); - chart_write_yscale (dnp_chart, np->dns_min, np->dns_max, 5); + chart_geometry_init (lp, &geom); + chart_write_title (lp, &geom, _("Normal Q-Q Plot of %s"), plot->label); + chart_write_xlabel (lp, &geom, _("Observed Value")); + chart_write_ylabel (lp, &geom, _("Expected Normal")); + chart_write_xscale (lp, &geom, + plot->x_lower - plot->slack, + plot->x_upper + plot->slack, 5); + chart_write_yscale (lp, &geom, plot->y_first, plot->y_last, 5); + + data = casereader_clone (plot->data); + for (; (c = casereader_read (data)) != NULL; case_unref (c)) + chart_datum (lp, &geom, 0, + case_data_idx (c, NP_IDX_Y)->f, + case_data_idx (c, NP_IDX_NS)->f); + casereader_destroy (data); + + chart_line (lp, &geom, plot->slope, plot->intercept, + plot->y_first, plot->y_last, CHART_DIM_Y); + + chart_geometry_free (lp); +} - { - struct casereader *reader = casewriter_make_reader (np->writer); - struct ccase *c; - while ((c = casereader_read (reader)) != NULL) - { - chart_datum (np_chart, 0, case_data_idx (c, NP_IDX_Y)->f, case_data_idx (c, NP_IDX_NS)->f); - chart_datum (dnp_chart, 0, case_data_idx (c, NP_IDX_Y)->f, case_data_idx (c, NP_IDX_DNS)->f); +static void +dnp_plot_chart_draw (const struct chart *chart, plPlotter *lp) +{ + const struct np_plot_chart *plot = (struct np_plot_chart *) chart; + struct chart_geometry geom; + struct casereader *data; + struct ccase *c; - case_unref (c); - } - casereader_destroy (reader); - } + chart_geometry_init (lp, &geom); + chart_write_title (lp, &geom, _("Detrended Normal Q-Q Plot of %s"), + plot->label); + chart_write_xlabel (lp, &geom, _("Observed Value")); + chart_write_ylabel (lp, &geom, _("Dev from Normal")); + chart_write_xscale (lp, &geom, plot->y_min, plot->y_max, 5); + chart_write_yscale (lp, &geom, plot->dns_min, plot->dns_max, 5); + + data = casereader_clone (plot->data); + for (; (c = casereader_read (data)) != NULL; case_unref (c)) + chart_datum (lp, &geom, 0, case_data_idx (c, NP_IDX_Y)->f, + case_data_idx (c, NP_IDX_DNS)->f); + casereader_destroy (data); - chart_line (dnp_chart, 0, 0, np->y_min, np->y_max , CHART_DIM_X); - chart_line (np_chart, slope, intercept, yfirst, ylast , CHART_DIM_Y); + chart_line (lp, &geom, 0, 0, plot->y_min, plot->y_max, CHART_DIM_X); - chart_submit (np_chart); - chart_submit (dnp_chart); + chart_geometry_free (lp); } +static void +np_plot_chart_destroy (struct chart *chart) +{ + struct np_plot_chart *plot = (struct np_plot_chart *) chart; + + casereader_destroy (plot->data); + free (plot->label); + free (plot); +} + +static const struct chart_class np_plot_chart_class = + { + np_plot_chart_draw, + np_plot_chart_destroy + }; + +static const struct chart_class dnp_plot_chart_class = + { + dnp_plot_chart_draw, + np_plot_chart_destroy + }; + static void show_npplot (const struct variable **dependent_var, @@ -448,8 +517,16 @@ show_histogram (const struct variable **dependent_var, struct string str; const struct factor_result *result = ll_data (ll, struct factor_result, ll); + struct histogram *histogram; double mean, var, n; + histogram = (struct histogram *) result->metrics[v].histogram; + if (histogram == NULL) + { + /* Probably all values are SYSMIS. */ + continue; + } + ds_init_empty (&str); ds_put_format (&str, "%s ", var_get_name (dependent_var[v])); @@ -457,9 +534,8 @@ show_histogram (const struct variable **dependent_var, moments1_calculate ((struct moments1 *) result->metrics[v].moments, &n, &mean, &var, NULL, NULL); - histogram_plot ((struct histogram *) result->metrics[v].histogram, - ds_cstr (&str), - n, mean, sqrt (var), false); + chart_submit (histogram_chart_create (histogram, ds_cstr (&str), + n, mean, sqrt (var), false)); ds_destroy (&str); } @@ -473,6 +549,7 @@ 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) @@ -550,6 +627,7 @@ show_boxplot_groups (const struct variable **dependent_var, chart_submit (ch); } +#endif } @@ -561,6 +639,7 @@ show_boxplot_variables (const struct variable **dependent_var, ) { +#if 0 int v; struct ll *ll; const struct ll_list *result_list = &fctr->result_list; @@ -630,6 +709,7 @@ show_boxplot_variables (const struct variable **dependent_var, chart_submit (ch); } +#endif } diff --git a/src/language/stats/frequencies.q b/src/language/stats/frequencies.q index 91ae6728..77b1d5f0 100644 --- a/src/language/stats/frequencies.q +++ b/src/language/stats/frequencies.q @@ -610,11 +610,12 @@ postcalc (const struct dataset *ds) hist = freq_tab_to_hist (ft,v); - histogram_plot (hist, var_to_string(v), + chart_submit (histogram_chart_create ( + hist, var_to_string(v), vf->tab.valid_cases, d[frq_mean], d[frq_stddev], - normal); + normal)); statistic_destroy ((struct statistic *)hist); } @@ -1532,14 +1533,12 @@ do_piechart(const struct variable *var, const struct freq_tab *frq_tab) slices = freq_tab_to_slice_array(frq_tab, var, &n_slices); - piechart_plot(var_to_string(var), slices, n_slices); + chart_submit (piechart_create (var_to_string(var), slices, n_slices)); for (i = 0 ; i < n_slices ; ++i ) - { - ds_destroy (&slices[i].label); - } + ds_destroy (&slices[i].label); - free(slices); + free (slices); } diff --git a/src/output/ascii.c b/src/output/ascii.c index 5a33a661..c01e36cc 100644 --- a/src/output/ascii.c +++ b/src/output/ascii.c @@ -29,11 +29,12 @@ #include #include #include +#include +#include +#include -#include "chart.h" #include "error.h" #include "minmax.h" -#include "output.h" #include "xalloc.h" #include "gettext.h" @@ -174,7 +175,7 @@ ascii_open_driver (const char *name, int types, struct substring options) x->page_number = 0; x->lines = NULL; x->line_cap = 0; - x->chart_cnt = 0; + x->chart_cnt = 1; if (!outp_parse_options (this->name, options, handle_option, this)) goto error; @@ -609,15 +610,6 @@ ascii_line (struct outp_driver *this, } } -static void -ascii_submit (struct outp_driver *this UNUSED, struct som_entity *s) -{ - extern struct som_table_class tab_table_class; - - assert (s->class == &tab_table_class); - assert (s->type == SOM_CHART); -} - static void text_draw (struct outp_driver *this, enum outp_font font, @@ -872,19 +864,24 @@ ascii_flush (struct outp_driver *this) } static void -ascii_chart_initialise (struct outp_driver *this, struct chart *ch) +ascii_output_chart (struct outp_driver *this, const struct chart *chart) { struct ascii_driver_ext *x = this->ext; struct outp_text t; + char *file_name; + plPlotter *lp; char *text; if (x->chart_type == NULL) return; - /* Initialize chart. */ - chart_init_separate (ch, x->chart_type, x->chart_file_name, ++x->chart_cnt); - if (ch->file_name == NULL) + /* Draw chart in separate file. */ + if (!chart_create_file (x->chart_type, x->chart_file_name, x->chart_cnt, + NULL, &file_name, &lp)) return; + x->chart_cnt++; + chart_draw (chart, lp); + pl_deletepl_r (lp); /* Mention chart in output. First advance current position. */ @@ -901,7 +898,7 @@ ascii_chart_initialise (struct outp_driver *this, struct chart *ch) } /* Then write the text. */ - text = xasprintf ("See %s for a chart.", ch->file_name); + text = xasprintf ("See %s for a chart.", file_name); t.font = OUTP_FIXED; t.justification = OUTP_LEFT; t.string = ss_cstr (text); @@ -912,17 +909,10 @@ ascii_chart_initialise (struct outp_driver *this, struct chart *ch) ascii_text_draw (this, &t); this->cp_y++; + free (file_name); free (text); } -static void -ascii_chart_finalise (struct outp_driver *this, struct chart *ch) -{ - struct ascii_driver_ext *x = this->ext; - if (x->chart_type != NULL) - chart_finalise_separate (ch); -} - const struct outp_class ascii_class = { "ascii", @@ -935,12 +925,11 @@ const struct outp_class ascii_class = ascii_close_page, ascii_flush, - ascii_submit, + ascii_output_chart, + + NULL, /* submit */ ascii_line, ascii_text_metrics, ascii_text_draw, - - ascii_chart_initialise, - ascii_chart_finalise }; diff --git a/src/output/cairo.c b/src/output/cairo.c index f99f7f1d..93842328 100644 --- a/src/output/cairo.c +++ b/src/output/cairo.c @@ -502,18 +502,6 @@ xr_close_page (struct outp_driver *this) struct xr_driver_ext *x = this->ext; cairo_show_page (x->cairo); } - -static void -xr_submit (struct outp_driver *this UNUSED, struct som_entity *s) -{ - switch (s->type) - { - case SOM_CHART: - break; - default: - NOT_REACHED (); - } -} /* Draws a line from (x0,y0) to (x1,y1). */ static void @@ -840,24 +828,6 @@ xr_text_draw (struct outp_driver *this, const struct outp_text *t) text (this, t, true, NULL, NULL); } -static void -xr_chart_initialise (struct outp_driver *this UNUSED, struct chart *ch UNUSED) -{ -#ifdef NO_CHARTS - ch->lp = NULL; -#else - /* XXX libplot doesn't support Cairo yet. */ -#endif -} - -static void -xr_chart_finalise (struct outp_driver *this UNUSED, struct chart *ch UNUSED) -{ -#ifndef NO_CHARTS - /* XXX libplot doesn't support Cairo yet. */ -#endif -} - /* Attempts to load FONT, initializing its other members based on its 'string' member and the information in THIS. Returns true if successful, otherwise false. */ @@ -910,12 +880,11 @@ const struct outp_class cairo_class = xr_close_page, NULL, - xr_submit, + NULL, + + NULL, xr_line, xr_text_metrics, xr_text_draw, - - xr_chart_initialise, - xr_chart_finalise }; diff --git a/src/output/chart-provider.h b/src/output/chart-provider.h new file mode 100644 index 00000000..4b36c55a --- /dev/null +++ b/src/output/chart-provider.h @@ -0,0 +1,74 @@ +/* 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 + +struct chart_class + { + void (*draw) (const struct chart *, plPlotter *); + void (*destroy) (struct chart *); + }; + +struct chart + { + const struct chart_class *class; + int ref_cnt; + }; + +void chart_init (struct chart *, const struct chart_class *); +bool chart_create_file (const char *type, const char *file_name_tmpl, + int number, plPlotterParams *, + char **file_namep, plPlotter **lpp); + +/* 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 ; + + int legend_left ; + int legend_right ; + + /* Default font size for the plot (if zero, then use plotter default) */ + int font_size; + + char fill_colour[10]; + + /* Stuff Particular to Cartesians (and Boxplots ) */ + double ordinate_scale; + double abscissa_scale; + double x_min; + double x_max; + double y_min; + double y_max; + }; + +void chart_geometry_init (plPlotter *, struct chart_geometry *); +void chart_geometry_free (plPlotter *); + +#endif /* output/chart-provider.h */ diff --git a/src/output/chart.c b/src/output/chart.c index 49819e9f..b096a7d2 100644 --- a/src/output/chart.c +++ b/src/output/chart.c @@ -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,18 +17,18 @@ #include #include +#include #include #include #include #include +#include #include #include #include #include -#include - #include #include #include @@ -41,119 +41,140 @@ 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); + chart->class = class; + chart->ref_cnt = 1; +} - /* 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; - strcpy(chart->fill_colour,"red"); +void +chart_geometry_init (plPlotter *lp, struct chart_geometry *geom) +{ + /* Start output page. */ + pl_openpl_r (lp); + + /* Set coordinate system. */ + pl_fspace_r (lp, 0.0, 0.0, 1000.0, 1000.0); + + /* Set line thickness. */ + pl_flinewidth_r (lp, 0.25); + pl_pencolorname_r (lp, "black"); + + /* Erase graphics display. */ + pl_erase_r (lp); + + pl_filltype_r (lp, 0); + pl_savestate_r(lp); + + /* Set default chartetry. */ + geom->data_top = 900; + geom->data_right = 800; + geom->data_bottom = 120; + geom->data_left = 150; + geom->abscissa_top = 70; + geom->ordinate_right = 120; + geom->title_bottom = 920; + geom->legend_left = 810; + geom->legend_right = 1000; + geom->font_size = 0; + strcpy (geom->fill_colour, "red"); /* Get default font size */ - if ( !chart->font_size) - chart->font_size = pl_fontsize_r(chart->lp, -1); + if (!geom->font_size) + geom->font_size = pl_fontsize_r (lp, -1); /* Draw the data area */ - pl_box_r(chart->lp, - chart->data_left, chart->data_bottom, - chart->data_right, chart->data_top); - - return chart; + pl_box_r (lp, + geom->data_left, geom->data_bottom, + geom->data_right, geom->data_top); } void -chart_submit(struct chart *chart) +chart_geometry_free (plPlotter *lp) { - struct som_entity s; - struct outp_driver *d; - - if ( ! chart ) - return ; + if (pl_closepl_r (lp) < 0) + fprintf (stderr, "Couldn't close Plotter\n"); +} - pl_restorestate_r(chart->lp); +void +chart_draw (const struct chart *chart, plPlotter *lp) +{ + chart->class->draw (chart, lp); +} - s.class = &tab_table_class; - s.ext = chart; - s.type = SOM_CHART; - som_submit (&s); +struct chart * +chart_ref (const struct chart *chart_) +{ + struct chart *chart = (struct chart *) chart_; + chart->ref_cnt++; + return chart; +} - if (pl_closepl_r (chart->lp) < 0) /* close Plotter */ - { - fprintf (stderr, "Couldn't close Plotter\n"); - } +void +chart_unref (struct chart *chart) +{ + assert (chart->ref_cnt > 0); + if (--chart->ref_cnt == 0) + chart->class->destroy (chart); +} - pl_deletepl_r(chart->lp); +void +chart_submit (struct chart *chart) +{ + struct outp_driver *d; - pl_deleteplparams(chart->pl_params); + for (d = outp_drivers (NULL); d; d = outp_drivers (d)) + if (d->class->output_chart != NULL) + d->class->output_chart (d, chart); - d = outp_drivers (NULL); - d->class->finalise_chart(d, chart); - free(chart); + chart_unref (chart); } -void -chart_init_separate (struct chart *ch, const char *type, - const char *file_name_tmpl, int number) +bool +chart_create_file (const char *type, const char *file_name_tmpl, int number, + plPlotterParams *params, char **file_namep, plPlotter **lpp) { - FILE *fp; + char *file_name = NULL; + FILE *fp = NULL; int number_pos; + plPlotter *lp; number_pos = strchr (file_name_tmpl, '#') - file_name_tmpl; - ch->file_name = xasprintf ("%.*s%d%s", - number_pos, file_name_tmpl, - number, - file_name_tmpl + number_pos + 1); - fp = fopen (ch->file_name, "wb"); + file_name = xasprintf ("%.*s%d%s", number_pos, file_name_tmpl, + number, file_name_tmpl + number_pos + 1); + + fp = fopen (file_name, "wb"); if (fp == NULL) { - error (0, errno, _("creating \"%s\""), ch->file_name); - free (ch->file_name); - ch->file_name = NULL; - return; + error (0, errno, _("creating \"%s\""), file_name); + goto error; } - ch->pl_params = pl_newplparams (); - ch->lp = pl_newpl_r (type, 0, fp, stderr, ch->pl_params); -} + if (params != NULL) + lp = pl_newpl_r (type, 0, fp, stderr, params); + else + { + params = pl_newplparams (); + lp = pl_newpl_r (type, 0, fp, stderr, params); + pl_deleteplparams (params); + } + if (lp == NULL) + goto error; -void -chart_finalise_separate (struct chart *ch) -{ - free (ch->file_name); + *file_namep = file_name; + *lpp = lp; + return true; + +error: + if (fp != NULL) + { + fclose (fp); + if (file_name != NULL) + unlink (file_name); + } + free (file_name); + *file_namep = NULL; + *lpp = NULL; + return false; } diff --git a/src/output/chart.h b/src/output/chart.h index 9586eb1e..93762dbd 100644 --- a/src/output/chart.h +++ b/src/output/chart.h @@ -1,5 +1,5 @@ /* PSPP - a program for statistical analysis. - Copyright (C) 2004 Free Software Foundation, Inc. + 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 @@ -14,82 +14,20 @@ 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 "manager.h" -#include "output.h" - -#include "xalloc.h" - -#ifndef CHART_H -#define CHART_H +#ifndef OUTPUT_CHART_H +#define OUTPUT_CHART_H 1 #ifndef NO_CHARTS +#include /* Required by . */ #include #endif -struct chart { - -#ifndef NO_CHARTS - plPlotter *lp ; - plPlotterParams *pl_params; -#else - void *lp; -#endif - char *file_name; - FILE *file; - - /* The geometry of the chart - See diagram at the foot of this file. - */ - - int data_top ; - int data_right ; - int data_bottom; - int data_left ; - - int abscissa_top; - - int ordinate_right ; +struct chart; - int title_bottom ; +void chart_draw (const struct chart *, plPlotter *); +struct chart *chart_ref (const struct chart *); +void chart_unref (struct chart *); - int legend_left ; - int legend_right ; +void chart_submit (struct chart *); - - /* Default font size for the plot (if zero, then use plotter default) */ - int font_size; - - char fill_colour[10]; - - /* Stuff Particular to Cartesians (and Boxplots ) */ - double ordinate_scale; - double abscissa_scale; - double x_min; - double x_max; - double y_min; - double y_max; -}; - - - -struct chart * chart_create(void); -void chart_submit(struct chart *ch); - -/* Helper functions for output drivers that put each chart into a - separate file. */ -void chart_init_separate (struct chart *, const char *type, - const char *file_name_tmpl, int number); - -void chart_finalise_separate (struct chart *); - -#endif +#endif /* output/chart.h */ diff --git a/src/output/charts/automake.mk b/src/output/charts/automake.mk index ab0ff510..e260f328 100644 --- a/src/output/charts/automake.mk +++ b/src/output/charts/automake.mk @@ -3,10 +3,6 @@ noinst_LTLIBRARIES += src/output/charts/libcharts.la chart_sources = \ - src/output/charts/barchart.c \ - src/output/charts/barchart.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/piechart.c \ diff --git a/src/output/charts/cartesian.c b/src/output/charts/cartesian.c index 75c49aa8..ffa6e3c5 100644 --- a/src/output/charts/cartesian.c +++ b/src/output/charts/cartesian.c @@ -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,17 +17,16 @@ #include +#include + #include #include #include - +#include #include -#include #include - - struct dataset { int n_data; @@ -46,24 +45,15 @@ static const struct dataset dataset[DATASETS] = /* Plot a data point */ void -chart_datum(struct chart *ch, int dataset UNUSED, double x, double y) +chart_datum (plPlotter *lp, 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); - } + pl_savestate_r (lp); + pl_fmarker_r (lp, x_pos, y_pos, 6, 15); + pl_restorestate_r (lp); } /* Draw a line with slope SLOPE and intercept INTERCEPT. @@ -72,20 +62,17 @@ chart_datum(struct chart *ch, int dataset UNUSED, double x, double y) y axis otherwise the x axis */ void -chart_line(struct chart *ch, double slope, double intercept, +chart_line(plPlotter *lp, const struct chart_geometry *geom, + double slope, double intercept, double limit1, double limit2, enum CHART_DIM lim_dim) { double x1, y1; - double x2, y2 ; - - if ( ! ch ) - return ; - + double x2, y2; if ( lim_dim == CHART_DIM_Y ) { - x1 = ( limit1 - intercept ) / slope ; - x2 = ( limit2 - intercept ) / slope ; + x1 = ( limit1 - intercept ) / slope; + x2 = ( limit2 - intercept ) / slope; y1 = limit1; y2 = limit2; } @@ -97,15 +84,12 @@ chart_line(struct chart *ch, double slope, double intercept, y2 = slope * x2 + intercept; } - y1 = (y1 - ch->y_min) * ch->ordinate_scale + ch->data_bottom ; - y2 = (y2 - ch->y_min) * ch->ordinate_scale + ch->data_bottom ; - x1 = (x1 - ch->x_min) * ch->abscissa_scale + ch->data_left ; - x2 = (x2 - ch->x_min) * ch->abscissa_scale + ch->data_left ; - - pl_savestate_r(ch->lp); - - pl_fline_r(ch->lp, x1, y1, x2, y2); - - pl_restorestate_r(ch->lp); + y1 = (y1 - geom->y_min) * geom->ordinate_scale + geom->data_bottom; + y2 = (y2 - geom->y_min) * geom->ordinate_scale + geom->data_bottom; + x1 = (x1 - geom->x_min) * geom->abscissa_scale + geom->data_left; + x2 = (x2 - geom->x_min) * geom->abscissa_scale + geom->data_left; + pl_savestate_r (lp); + pl_fline_r (lp, x1, y1, x2, y2); + pl_restorestate_r (lp); } diff --git a/src/output/charts/cartesian.h b/src/output/charts/cartesian.h index 40e3f714..15fb8840 100644 --- a/src/output/charts/cartesian.h +++ b/src/output/charts/cartesian.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 @@ -19,6 +19,10 @@ #ifndef CARTESIAN_H #define CARTESIAN_H +#include +#include + +#include enum CHART_DIM { @@ -26,17 +30,19 @@ enum CHART_DIM CHART_DIM_Y }; - +struct chart_geometry; /* Plot a data point */ -void chart_datum(struct chart *ch, int dataset UNUSED, double x, double y); +void chart_datum(plPlotter *, 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. If lim_dim is CHART_DIM_Y then the limit{1,2} are on the y axis otherwise the x axis */ -void chart_line(struct chart *ch, double slope, double intercept, +void chart_line(plPlotter *, const struct chart_geometry *, + double slope, double intercept, double limit1, double limit2, enum CHART_DIM lim_dim); diff --git a/src/output/charts/piechart.c b/src/output/charts/piechart.c index 97910010..4b7b1400 100644 --- a/src/output/charts/piechart.c +++ b/src/output/charts/piechart.c @@ -17,25 +17,34 @@ #include -#include +#include + #include +#include #include #include #include - -#include -#include - -#include -#include #include +#include +#include +#include #include "minmax.h" +struct piechart + { + struct chart chart; + char *title; + struct slice *slices; + int n_slices; + }; + +static const struct chart_class piechart_class; + /* Draw a single slice of the pie */ static void -draw_segment(struct chart *ch, +draw_segment(plPlotter *, double centre_x, double centre_y, double radius, double start_angle, double segment_angle, @@ -43,42 +52,64 @@ draw_segment(struct chart *ch, -/* Draw a piechart */ -void -piechart_plot(const char *title, const struct slice *slices, int n_slices) +/* Creates and returns a chart that will render a piechart with + the given TITLE and the N_SLICES described in SLICES. */ +struct chart * +piechart_create (const char *title, const struct slice *slices, int n_slices) { + struct piechart *pie; int i; - double total_magnitude=0; - struct chart *ch = chart_create(); + pie = xmalloc (sizeof *pie); + chart_init (&pie->chart, &piechart_class); + pie->title = xstrdup (title); + pie->slices = xnmalloc (n_slices, sizeof *pie->slices); + for (i = 0; i < n_slices; i++) + { + const struct slice *src = &slices[i]; + struct slice *dst = &pie->slices[i]; - const double left_label = ch->data_left + - (ch->data_right - ch->data_left)/10.0; + ds_init_string (&dst->label, &src->label); + dst->magnitude = src->magnitude; + } + pie->n_slices = n_slices; + return &pie->chart; +} - const double right_label = ch->data_right - - (ch->data_right - ch->data_left)/10.0; +static void +piechart_draw (const struct chart *chart, plPlotter *lp) +{ + struct piechart *pie = (struct piechart *) chart; + struct chart_geometry geom; + double total_magnitude; + double left_label, right_label; + double centre_x, centre_y; + double radius; + double angle; + int i; - const double centre_x = (ch->data_right + ch->data_left ) / 2.0 ; - const double centre_y = (ch->data_top + ch->data_bottom ) / 2.0 ; + chart_geometry_init (lp, &geom); - const double radius = MIN( - 5.0 / 12.0 * (ch->data_top - ch->data_bottom), - 1.0 / 4.0 * (ch->data_right - ch->data_left) - ); + left_label = geom.data_left + (geom.data_right - geom.data_left)/10.0; + right_label = geom.data_right - (geom.data_right - geom.data_left)/10.0; - double angle; + centre_x = (geom.data_right + geom.data_left) / 2.0 ; + centre_y = (geom.data_top + geom.data_bottom) / 2.0 ; + radius = MIN (5.0 / 12.0 * (geom.data_top - geom.data_bottom), + 1.0 / 4.0 * (geom.data_right - geom.data_left)); - chart_write_title(ch, "%s", title); + chart_write_title (lp, &geom, "%s", pie->title); - for (i = 0 ; i < n_slices ; ++i ) - total_magnitude += slices[i].magnitude; + total_magnitude = 0.0; + for (i = 0; i < pie->n_slices; i++) + total_magnitude += pie->slices[i].magnitude; angle = 0.0; - for (i = 0 ; i < n_slices ; ++i ) + for (i = 0; i < pie->n_slices ; ++i ) { const double segment_angle = - slices[i].magnitude / total_magnitude * 2 * M_PI ; + pie->slices[i].magnitude / total_magnitude * 2 * M_PI ; const double label_x = centre_x - radius * sin(angle + segment_angle/2.0); @@ -87,50 +118,38 @@ piechart_plot(const char *title, const struct slice *slices, int n_slices) radius * cos(angle + segment_angle/2.0); /* Fill the segment */ - draw_segment(ch, - centre_x, centre_y, radius, - angle, segment_angle, - data_colour[i % N_CHART_COLOURS]); + draw_segment (lp, + centre_x, centre_y, radius, + angle, segment_angle, + data_colour[i % N_CHART_COLOURS]); /* Now add the labels */ if ( label_x < centre_x ) { - pl_line_r(ch->lp, label_x, label_y, - left_label, label_y ); - pl_moverel_r(ch->lp,0,5); - pl_alabel_r (ch->lp, 0, 0, ds_cstr (&slices[i].label)); + pl_line_r (lp, label_x, label_y, left_label, label_y ); + pl_moverel_r (lp, 0, 5); + pl_alabel_r (lp, 0, 0, ds_cstr (&pie->slices[i].label)); } else { - pl_line_r(ch->lp, - label_x, label_y, - right_label, label_y - ); - pl_moverel_r(ch->lp,0,5); - pl_alabel_r (ch->lp, 'r', 0, ds_cstr (&slices[i].label)); + pl_line_r (lp, label_x, label_y, right_label, label_y); + pl_moverel_r (lp, 0, 5); + pl_alabel_r (lp, 'r', 0, ds_cstr (&pie->slices[i].label)); } angle += segment_angle; - } /* Draw an outline to the pie */ - pl_filltype_r(ch->lp,0); - pl_fcircle_r (ch->lp, centre_x, centre_y, radius); + pl_filltype_r (lp,0); + pl_fcircle_r (lp, centre_x, centre_y, radius); - chart_submit(ch); + chart_geometry_free (lp); } -static void -fill_segment(struct chart *ch, - double x0, double y0, - double radius, - double start_angle, double segment_angle) ; - - /* Fill a segment with the current fill colour */ static void -fill_segment(struct chart *ch, +fill_segment(plPlotter *lp, double x0, double y0, double radius, double start_angle, double segment_angle) @@ -151,32 +170,30 @@ fill_segment(struct chart *ch, if ( segment_angle > M_PI ) { /* Then we must draw it in two halves */ - fill_segment(ch, x0, y0, radius, start_angle, segment_angle / 2.0 ); - fill_segment(ch, x0, y0, radius, start_angle + segment_angle / 2.0, + fill_segment(lp, x0, y0, radius, start_angle, segment_angle / 2.0 ); + fill_segment(lp, x0, y0, radius, start_angle + segment_angle / 2.0, segment_angle / 2.0 ); } else { - pl_move_r(ch->lp, x0, y0); + pl_move_r(lp, x0, y0); - pl_cont_r(ch->lp, stop_x, stop_y); - pl_cont_r(ch->lp, start_x, start_y); + pl_cont_r(lp, stop_x, stop_y); + pl_cont_r(lp, start_x, start_y); - pl_arc_r(ch->lp, + pl_arc_r(lp, x0, y0, stop_x, stop_y, start_x, start_y ); - pl_endpath_r(ch->lp); + pl_endpath_r(lp); } } - - /* Draw a single slice of the pie */ static void -draw_segment(struct chart *ch, +draw_segment(plPlotter *lp, double x0, double y0, double radius, double start_angle, double segment_angle, @@ -185,22 +202,43 @@ draw_segment(struct chart *ch, const double start_x = x0 - radius * sin(start_angle); const double start_y = y0 + radius * cos(start_angle); - pl_savestate_r(ch->lp); + pl_savestate_r(lp); - pl_savestate_r(ch->lp); - pl_colorname_r(ch->lp, colour); + pl_savestate_r(lp); + pl_colorname_r(lp, colour); - pl_pentype_r(ch->lp,1); - pl_filltype_r(ch->lp,1); + pl_pentype_r(lp,1); + pl_filltype_r(lp,1); - fill_segment(ch, x0, y0, radius, start_angle, segment_angle); - pl_restorestate_r(ch->lp); + fill_segment(lp, x0, y0, radius, start_angle, segment_angle); + pl_restorestate_r(lp); /* Draw line dividing segments */ - pl_pentype_r(ch->lp, 1); - pl_fline_r(ch->lp, x0, y0, start_x, start_y); + pl_pentype_r(lp, 1); + pl_fline_r(lp, x0, y0, start_x, start_y); + + pl_restorestate_r(lp); +} - pl_restorestate_r(ch->lp); +static void +piechart_destroy (struct chart *chart) +{ + struct piechart *pie = (struct piechart *) chart; + int i; + + free (pie->title); + for (i = 0; i < pie->n_slices; i++) + { + struct slice *slice = &pie->slices[i]; + ds_destroy (&slice->label); + } + free (pie->slices); + free (pie); } +static const struct chart_class piechart_class = + { + piechart_draw, + piechart_destroy + }; diff --git a/src/output/charts/piechart.h b/src/output/charts/piechart.h index 288a44e6..39a0c2d5 100644 --- a/src/output/charts/piechart.h +++ b/src/output/charts/piechart.h @@ -24,9 +24,8 @@ struct slice { double magnitude; }; -/* Draw a piechart */ -void piechart_plot(const char *title, - const struct slice *slices, int n_slices); +struct chart *piechart_create (const char *title, + const struct slice *, int n_slices); #endif diff --git a/src/output/charts/plot-chart.c b/src/output/charts/plot-chart.c index 3b4f1b37..75f21b66 100644 --- a/src/output/charts/plot-chart.c +++ b/src/output/charts/plot-chart.c @@ -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 @@ -16,6 +16,8 @@ #include +#include + #include #include #include @@ -25,14 +27,10 @@ #include #include -#include - -#include - - - -#include #include +#include +#include +#include #include #include @@ -57,156 +55,130 @@ const char *const data_colour[N_CHART_COLOURS] = If label is non zero, then print it at the tick mark */ void -draw_tick(struct chart *chart, - enum tick_orientation orientation, - double position, - const char *label, ...) +draw_tick (plPlotter *lp, const struct chart_geometry *geom, + enum tick_orientation orientation, + double position, + const char *label, ...) { const int tickSize = 10; - assert(chart); - - pl_savestate_r(chart->lp); - - pl_move_r(chart->lp, chart->data_left, chart->data_bottom); + pl_savestate_r (lp); + pl_move_r (lp, geom->data_left, geom->data_bottom); - if ( orientation == TICK_ABSCISSA ) - pl_flinerel_r(chart->lp, position, 0, position, -tickSize); - else if (orientation == TICK_ORDINATE ) - pl_flinerel_r(chart->lp, 0, position, -tickSize, position); + if (orientation == TICK_ABSCISSA) + pl_flinerel_r (lp, position, 0, position, -tickSize); + else if (orientation == TICK_ORDINATE) + pl_flinerel_r (lp, 0, position, -tickSize, position); else NOT_REACHED (); - if ( label ) { - char buf[10]; - va_list ap; - va_start(ap,label); - vsnprintf(buf,10,label,ap); - - if ( orientation == TICK_ABSCISSA ) - pl_alabel_r(chart->lp, 'c','t', buf); - else if (orientation == TICK_ORDINATE ) - { - if ( fabs(position) < DBL_EPSILON ) - pl_moverel_r(chart->lp, 0, 10); - - pl_alabel_r(chart->lp, 'r','c', buf); - } - - va_end(ap); - } + if (label != NULL) + { + va_list ap; + char *s; + + va_start (ap, label); + s = xvasprintf (label, ap); + if (orientation == TICK_ABSCISSA) + pl_alabel_r (lp, 'c', 't', s); + else if (orientation == TICK_ORDINATE) + { + if (fabs (position) < DBL_EPSILON) + pl_moverel_r (lp, 0, 10); + pl_alabel_r (lp, 'r', 'c', s); + } + free (s); + va_end (ap); + } - pl_restorestate_r(chart->lp); + pl_restorestate_r (lp); } /* Write the title on a chart*/ void -chart_write_title(struct chart *chart, const char *title, ...) +chart_write_title (plPlotter *lp, const struct chart_geometry *geom, + const char *title, ...) { va_list ap; - char buf[100]; + char *s; - if ( ! chart ) - return ; + pl_savestate_r (lp); + pl_ffontsize_r (lp, geom->font_size * 1.5); + pl_move_r (lp, geom->data_left, geom->title_bottom); - pl_savestate_r(chart->lp); - pl_ffontsize_r(chart->lp,chart->font_size * 1.5); - pl_move_r(chart->lp,chart->data_left, chart->title_bottom); + va_start(ap, title); + s = xvasprintf (title, ap); + pl_alabel_r (lp, 0, 0, s); + free (s); + va_end (ap); - va_start(ap,title); - vsnprintf(buf,100,title,ap); - pl_alabel_r(chart->lp,0,0,buf); - va_end(ap); - - pl_restorestate_r(chart->lp); + pl_restorestate_r (lp); } /* Set the scale for the abscissa */ void -chart_write_xscale(struct chart *ch, double min, double max, int ticks) +chart_write_xscale (plPlotter *lp, struct chart_geometry *geom, + double min, double max, int ticks) { double x; const double tick_interval = - chart_rounded_tick( (max - min) / (double) ticks); - - assert ( ch ); + chart_rounded_tick ((max - min) / (double) ticks); + geom->x_max = ceil (max / tick_interval) * tick_interval; + geom->x_min = floor (min / tick_interval) * tick_interval; + geom->abscissa_scale = fabs(geom->data_right - geom->data_left) / + fabs(geom->x_max - geom->x_min); - ch->x_max = ceil( max / tick_interval ) * tick_interval ; - ch->x_min = floor ( min / tick_interval ) * tick_interval ; - - - ch->abscissa_scale = fabs(ch->data_right - ch->data_left) / - fabs(ch->x_max - ch->x_min); - - for(x = ch->x_min ; x <= ch->x_max; x += tick_interval ) - { - draw_tick (ch, TICK_ABSCISSA, - (x - ch->x_min) * ch->abscissa_scale, "%g", x); - } - + for (x = geom->x_min; x <= geom->x_max; x += tick_interval) + draw_tick (lp, geom, TICK_ABSCISSA, + (x - geom->x_min) * geom->abscissa_scale, "%g", x); } /* Set the scale for the ordinate */ void -chart_write_yscale(struct chart *ch, double smin, double smax, int ticks) +chart_write_yscale (plPlotter *lp, struct chart_geometry *geom, + double smin, double smax, int ticks) { double y; const double tick_interval = - chart_rounded_tick( (smax - smin) / (double) ticks); - - if ( !ch ) - return; + chart_rounded_tick ((smax - smin) / (double) ticks); - ch->y_max = ceil ( smax / tick_interval ) * tick_interval ; - ch->y_min = floor ( smin / tick_interval ) * tick_interval ; + geom->y_max = ceil (smax / tick_interval) * tick_interval; + geom->y_min = floor (smin / tick_interval) * tick_interval; - 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)); - 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 (lp, 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 (plPlotter *lp, 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); - + pl_savestate_r (lp); + pl_move_r (lp, geom->data_left, geom->abscissa_top); + pl_alabel_r (lp, 0, 't', label); + pl_restorestate_r (lp); } - - /* Write the ordinate label */ void -chart_write_ylabel(struct chart *ch, const char *label) +chart_write_ylabel (plPlotter *lp, 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); + pl_savestate_r (lp); + pl_move_r (lp, geom->data_bottom, geom->ordinate_right); + pl_textangle_r (lp, 90); + pl_alabel_r (lp, 0, 0, label); + pl_restorestate_r(lp); } diff --git a/src/output/charts/plot-chart.h b/src/output/charts/plot-chart.h index 9a4df520..59d385bb 100644 --- a/src/output/charts/plot-chart.h +++ b/src/output/charts/plot-chart.h @@ -45,32 +45,39 @@ enum tick_orientation TICK_ORDINATE }; +struct chart_geometry; + /* Draw a tick mark at position If label is non zero, then print it at the tick mark */ -void draw_tick(struct chart *chart, +void draw_tick(plPlotter *, const struct chart_geometry *, enum tick_orientation orientation, double position, const char *label, ...) - PRINTF_FORMAT (4, 5); + PRINTF_FORMAT (5, 6); /* Write the title on a chart*/ -void chart_write_title(struct chart *chart, const char *title, ...) - PRINTF_FORMAT (2, 3); +void chart_write_title(plPlotter *, const struct chart_geometry *, + const char *title, ...) + PRINTF_FORMAT (3, 4); /* Set the scale for the abscissa */ -void chart_write_xscale(struct chart *ch, double min, double max, int ticks); +void chart_write_xscale(plPlotter *, struct chart_geometry *, + double min, double max, int ticks); /* Set the scale for the ordinate */ -void chart_write_yscale(struct chart *ch, double smin, double smax, int ticks); +void chart_write_yscale(plPlotter *, struct chart_geometry *, + double smin, double smax, int ticks); -void chart_write_xlabel(struct chart *ch, const char *label) ; +void chart_write_xlabel(plPlotter *, 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(plPlotter *, const struct chart_geometry *, + const char *label); #endif diff --git a/src/output/charts/plot-hist.c b/src/output/charts/plot-hist.c index b90d57ac..ab3b988d 100644 --- a/src/output/charts/plot-hist.c +++ b/src/output/charts/plot-hist.c @@ -26,6 +26,7 @@ #include #include +#include #include #include @@ -36,160 +37,192 @@ #include "gettext.h" #define _(msgid) gettext (msgid) +static const struct chart_class histogram_chart_class; + /* Write the legend of the chart */ static void -histogram_write_legend (struct chart *ch, double n, double mean, double stddev) +histogram_write_legend (plPlotter *lp, const struct chart_geometry *geom, + double n, double mean, double stddev) { - double y; - char buf[100]; - - if (!ch) - return ; - - y = ch->data_bottom; - pl_savestate_r (ch->lp); + double y = geom->data_bottom; + pl_savestate_r (lp); if (n != SYSMIS) { - sprintf (buf, "N = %.2f", n); - pl_move_r (ch->lp, ch->legend_left, y); - pl_alabel_r (ch->lp, 0, 'b', buf); - y += ch->font_size * 1.5; + char *buf = xasprintf ("N = %.2f", n); + pl_move_r (lp, geom->legend_left, y); + pl_alabel_r (lp, 0, 'b', buf); + y += geom->font_size * 1.5; + free (buf); } if (mean != SYSMIS) { - sprintf (buf, "Mean = %.1f", mean); - pl_fmove_r (ch->lp,ch->legend_left, y); - pl_alabel_r (ch->lp, 0, 'b', buf); - y += ch->font_size * 1.5; + char *buf = xasprintf ("Mean = %.1f", mean); + pl_fmove_r (lp,geom->legend_left, y); + pl_alabel_r (lp, 0, 'b', buf); + y += geom->font_size * 1.5; + free (buf); } if (stddev != SYSMIS) { - sprintf (buf, "Std. Dev = %.2f", stddev); - pl_fmove_r (ch->lp, ch->legend_left, y); - pl_alabel_r (ch->lp, 0, 'b', buf); + char *buf = xasprintf ("Std. Dev = %.2f", stddev); + pl_fmove_r (lp, geom->legend_left, y); + pl_alabel_r (lp, 0, 'b', buf); + free (buf); } - pl_restorestate_r (ch->lp); + pl_restorestate_r (lp); } -static void hist_draw_bar (struct chart *ch, const struct histogram *hist, int bar); - - static void -hist_draw_bar (struct chart *ch, const struct histogram *hist, int bar) +hist_draw_bar (plPlotter *lp, const struct chart_geometry *geom, + const gsl_histogram *h, int bar) { - if (!ch) - return ; + double upper; + double lower; + double height; - { - double upper; - double lower; - double height; + const size_t bins = gsl_histogram_bins (h); + const double x_pos = (geom->data_right - geom->data_left) * bar / (double) bins ; + const double width = (geom->data_right - geom->data_left) / (double) bins ; - const size_t bins = gsl_histogram_bins (hist->gsl_hist); - const double x_pos = (ch->data_right - ch->data_left) * bar / (double) bins ; - const double width = (ch->data_right - ch->data_left) / (double) bins ; + assert ( 0 == gsl_histogram_get_range (h, bar, &lower, &upper)); - assert ( 0 == gsl_histogram_get_range (hist->gsl_hist, bar, &lower, &upper)); + assert ( upper >= lower); - assert ( upper >= lower); + height = gsl_histogram_get (h, bar) * + (geom->data_top - geom->data_bottom) / gsl_histogram_max_val (h); - height = gsl_histogram_get (hist->gsl_hist, bar) * - (ch->data_top - ch->data_bottom) / gsl_histogram_max_val (hist->gsl_hist); + pl_savestate_r (lp); + pl_move_r (lp,geom->data_left, geom->data_bottom); + pl_fillcolorname_r (lp, geom->fill_colour); + pl_filltype_r (lp,1); - pl_savestate_r (ch->lp); - pl_move_r (ch->lp,ch->data_left, ch->data_bottom); - pl_fillcolorname_r (ch->lp, ch->fill_colour); - pl_filltype_r (ch->lp,1); + pl_fboxrel_r (lp, + x_pos, 0, + x_pos + width, height); - pl_fboxrel_r (ch->lp, - x_pos, 0, - x_pos + width, height); + pl_restorestate_r (lp); - pl_restorestate_r (ch->lp); - - draw_tick (ch, TICK_ABSCISSA, - x_pos + width / 2.0, "%g", (upper + lower) / 2.0); - } + draw_tick (lp, geom, TICK_ABSCISSA, + x_pos + width / 2.0, "%g", (upper + lower) / 2.0); } - +struct histogram_chart + { + struct chart chart; + gsl_histogram *gsl_hist; + char *label; + double n; + double mean; + double stddev; + bool show_normal; + }; /* Plots a histogram of the data in HIST with the given LABEL. Labels the histogram with each of N, MEAN, and STDDEV that is not SYSMIS. If all three are not SYSMIS and SHOW_NORMAL is true, also draws a normal curve on the histogram. */ -void -histogram_plot (const struct histogram *hist, - const char *label, - double n, double mean, double stddev, - bool show_normal) +struct chart * +histogram_chart_create (const struct histogram *hist, const char *label, + double n, double mean, double stddev, + bool show_normal) +{ + struct histogram_chart *h; + + h = xmalloc (sizeof *h); + chart_init (&h->chart, &histogram_chart_class); + h->gsl_hist = hist->gsl_hist ? gsl_histogram_clone (hist->gsl_hist) : NULL; + h->label = xstrdup (label); + h->n = n; + h->mean = mean; + h->stddev = stddev; + h->show_normal = show_normal; + return &h->chart; +} + +static void +histogram_chart_draw (const struct chart *chart, plPlotter *lp) { + struct histogram_chart *h = (struct histogram_chart *) chart; + struct chart_geometry geom; int i; int bins; - struct chart *ch = chart_create (); + chart_geometry_init (lp, &geom); - chart_write_title (ch, _("HISTOGRAM")); + chart_write_title (lp, &geom, _("HISTOGRAM")); - chart_write_ylabel (ch, _("Frequency")); - chart_write_xlabel (ch, label); + chart_write_ylabel (lp, &geom, _("Frequency")); + chart_write_xlabel (lp, &geom, h->label); - if ( ! hist ) /* If this happens, probably all values are SYSMIS */ + if (h->gsl_hist == NULL) { - chart_submit (ch); + /* Probably all values are SYSMIS. */ return; } - else - { - bins = gsl_histogram_bins (hist->gsl_hist); - } - chart_write_yscale (ch, 0, gsl_histogram_max_val (hist->gsl_hist), 5); + bins = gsl_histogram_bins (h->gsl_hist); + + chart_write_yscale (lp, &geom, 0, gsl_histogram_max_val (h->gsl_hist), 5); - for ( i = 0 ; i < bins ; ++i ) - hist_draw_bar (ch, hist, i); + for (i = 0; i < bins; i++) + hist_draw_bar (lp, &geom, h->gsl_hist, i); - histogram_write_legend (ch, n, mean, stddev); + histogram_write_legend (lp, &geom, h->n, h->mean, h->stddev); - if (show_normal && n != SYSMIS && mean != SYSMIS && stddev != SYSMIS) + if (h->show_normal + && h->n != SYSMIS && h->mean != SYSMIS && h->stddev != SYSMIS) { /* Draw the normal curve */ + double d; + double x_min, x_max, not_used; + double abscissa_scale; + double ordinate_scale; + double range; - double d ; - double x_min, x_max, not_used ; - double abscissa_scale ; - double ordinate_scale ; - double range ; - - gsl_histogram_get_range (hist->gsl_hist, 0, &x_min, ¬_used); + gsl_histogram_get_range (h->gsl_hist, 0, &x_min, ¬_used); range = not_used - x_min; - gsl_histogram_get_range (hist->gsl_hist, bins - 1, ¬_used, &x_max); + gsl_histogram_get_range (h->gsl_hist, bins - 1, ¬_used, &x_max); - abscissa_scale = (ch->data_right - ch->data_left) / (x_max - x_min); - ordinate_scale = (ch->data_top - ch->data_bottom) / - gsl_histogram_max_val (hist->gsl_hist) ; + abscissa_scale = (geom.data_right - geom.data_left) / (x_max - x_min); + ordinate_scale = (geom.data_top - geom.data_bottom) / + gsl_histogram_max_val (h->gsl_hist); - pl_move_r (ch->lp, ch->data_left, ch->data_bottom); - for ( d = ch->data_left; - d <= ch->data_right ; - d += (ch->data_right - ch->data_left) / 100.0) + pl_move_r (lp, geom.data_left, geom.data_bottom); + for (d = geom.data_left; + d <= geom.data_right; + d += (geom.data_right - geom.data_left) / 100.0) { - const double x = (d - ch->data_left) / abscissa_scale + x_min ; - const double y = n * range * - gsl_ran_gaussian_pdf (x - mean, stddev); + const double x = (d - geom.data_left) / abscissa_scale + x_min; + const double y = h->n * range * + gsl_ran_gaussian_pdf (x - h->mean, h->stddev); - pl_fcont_r (ch->lp, d, ch->data_bottom + y * ordinate_scale); + pl_fcont_r (lp, d, geom.data_bottom + y * ordinate_scale); } - pl_endpath_r (ch->lp); + pl_endpath_r (lp); } - chart_submit (ch); + chart_geometry_free (lp); } +static void +histogram_chart_destroy (struct chart *chart) +{ + struct histogram_chart *h = (struct histogram_chart *) chart; + if (h->gsl_hist != NULL) + gsl_histogram_free (h->gsl_hist); + free (h->label); + free (h); +} + +static const struct chart_class histogram_chart_class = + { + histogram_chart_draw, + histogram_chart_destroy + }; diff --git a/src/output/charts/plot-hist.h b/src/output/charts/plot-hist.h index 74b10bd3..1e5b59ed 100644 --- a/src/output/charts/plot-hist.h +++ b/src/output/charts/plot-hist.h @@ -14,23 +14,22 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#ifndef PLOT_HIST_H -#define PLOT_HIST_H +#ifndef OUTPUT_PLOT_HIST_H +#define OUTPUT_PLOT_HIST_H #include struct chart; -struct moments1; struct histogram; -/* Plots a histogram of the data in HIST with the given LABEL. - Labels the histogram with each of N, MEAN, and STDDEV that is - not SYSMIS. If all three are not SYSMIS and SHOW_NORMAL is - true, also draws a normal curve on the histogram. */ -void -histogram_plot (const struct histogram *hist, - const char *label, - double n, double mean, double stddev, - bool show_normal); - -#endif +/* Creates and returns a new chart that depicts a histogram of + the data in HIST with the given LABEL. Labels the histogram + with each of N, MEAN, and STDDEV that is not SYSMIS. If all + three are not SYSMIS and SHOW_NORMAL is true, also draws a + normal curve on the histogram. */ +struct chart *histogram_chart_create (const struct histogram *hist, + const char *label, + double n, double mean, double stddev, + bool show_normal); + +#endif /* output/plot-hist.h */ diff --git a/src/output/html.c b/src/output/html.c index bfe0850d..8c1a6530 100644 --- a/src/output/html.c +++ b/src/output/html.c @@ -27,12 +27,13 @@ #include #include #include -#include "error.h" -#include "output.h" -#include "manager.h" -#include "table.h" +#include +#include +#include +#include #include +#include "error.h" #include "xalloc.h" #include "gettext.h" @@ -63,7 +64,7 @@ html_open_driver (const char *name, int types, struct substring options) x->file_name = xstrdup ("pspp.html"); x->chart_file_name = xstrdup ("pspp-#.png"); x->file = NULL; - x->chart_cnt = 0; + x->chart_cnt = 1; outp_parse_options (name, options, handle_option, this); @@ -203,11 +204,30 @@ handle_option (void *this_, const char *key, const struct string *val) static void output_tab_table (struct outp_driver *, struct tab_table *); +static void +html_output_chart (struct outp_driver *this, const struct chart *chart) +{ + struct html_driver_ext *x = this->ext; + char *file_name; + plPlotter *lp; + + /* Draw chart in separate file. */ + if (!chart_create_file ("png", x->chart_file_name, x->chart_cnt, + NULL, &file_name, &lp)) + return; + x->chart_cnt++; + chart_draw (chart, lp); + pl_deletepl_r (lp); + + link_image (x->file, file_name); + + free (file_name); +} + static void html_submit (struct outp_driver *this, struct som_entity *s) { extern struct som_table_class tab_table_class; - struct html_driver_ext *x = this->ext; assert (s->class == &tab_table_class ) ; @@ -216,9 +236,6 @@ html_submit (struct outp_driver *this, struct som_entity *s) case SOM_TABLE: output_tab_table ( this, (struct tab_table *) s->ext); break; - case SOM_CHART: - link_image (x->file, ((struct chart *)s->ext)->file_name); - break; default: NOT_REACHED (); } @@ -365,19 +382,6 @@ output_tab_table (struct outp_driver *this, struct tab_table *t) fputs ("\n\n", x->file); } -static void -html_initialise_chart (struct outp_driver *this UNUSED, struct chart *ch) -{ - struct html_driver_ext *x = this->ext; - chart_init_separate (ch, "png", x->chart_file_name, ++x->chart_cnt); -} - -static void -html_finalise_chart(struct outp_driver *d UNUSED, struct chart *ch) -{ - chart_finalise_separate (ch); -} - /* HTML driver class. */ @@ -393,11 +397,11 @@ const struct outp_class html_class = NULL, NULL, + html_output_chart, + html_submit, NULL, NULL, NULL, - html_initialise_chart, - html_finalise_chart }; diff --git a/src/output/manager.c b/src/output/manager.c index f418cd0d..9b901393 100644 --- a/src/output/manager.c +++ b/src/output/manager.c @@ -219,7 +219,7 @@ output_entity (struct outp_driver *d, struct som_entity *t) void *r; outp_open_page (d); - if (d->class->special || t->type == SOM_CHART) + if (d->class->special) { d->class->submit (d, t); return; diff --git a/src/output/manager.h b/src/output/manager.h index 8ed104ba..e7276982 100644 --- a/src/output/manager.h +++ b/src/output/manager.h @@ -37,8 +37,7 @@ enum som_type { - SOM_TABLE, - SOM_CHART + SOM_TABLE } ; /* Entity (Table or Chart) . */ diff --git a/src/output/output.h b/src/output/output.h index 09dddc77..ec9ae87d 100644 --- a/src/output/output.h +++ b/src/output/output.h @@ -74,6 +74,8 @@ struct outp_class void (*flush) (struct outp_driver *); + void (*output_chart) (struct outp_driver *, const struct chart *); + /* special != 0 only. */ void (*submit) (struct outp_driver *, struct som_entity *); @@ -84,8 +86,6 @@ struct outp_class void (*text_metrics) (struct outp_driver *, const struct outp_text *, int *width, int *height); void (*text_draw) (struct outp_driver *, const struct outp_text *); - void (*initialise_chart)(struct outp_driver *, struct chart *); - void (*finalise_chart)(struct outp_driver *, struct chart *); }; /* Device types. */ diff --git a/src/output/postscript.c b/src/output/postscript.c index 1fdc8d69..bec23de6 100644 --- a/src/output/postscript.c +++ b/src/output/postscript.c @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -32,15 +33,13 @@ #include #include #include +#include +#include +#include +#include +#include -#include - -#include "afm.h" -#include "chart.h" #include "error.h" -#include "manager.h" -#include "output.h" - #include "intprops.h" #include "minmax.h" #include "xalloc.h" @@ -105,6 +104,8 @@ struct ps_driver_ext struct font *fonts[OUTP_FONT_CNT]; int last_font; /* Index of last font set with setfont. */ + + int doc_num; /* %%DocumentNumber counter. */ }; /* Transform logical y-ordinate Y into a page ordinate. */ @@ -151,6 +152,7 @@ ps_open_driver (const char *name, int types, struct substring options) x->line_width = PSUS / 144; for (i = 0; i < OUTP_FONT_CNT; i++) x->fonts[i] = NULL; + x->doc_num = 0; outp_parse_options (this->name, options, handle_option, this); @@ -601,13 +603,82 @@ ps_close_page (struct outp_driver *this) x->file); } +static void +ps_output_chart (struct outp_driver *this, const struct chart *chart) +{ + struct ps_driver_ext *x = this->ext; + plPlotterParams *params; + int x_origin, y_origin; + char buf[BUFSIZ]; + char *page_size; + plPlotter *lp; + FILE *file; + int size; + + /* Create temporary file for chart. */ + file = tmpfile (); + if (file == NULL) + { + error (0, errno, _("failed to create temporary file")); + return; + } + + /* Create plotter for chart. */ + size = this->width < this->length ? this->width : this->length; + x_origin = x->left_margin + (size - this->width) / 2; + y_origin = x->bottom_margin + (size - this->length) / 2; + page_size = xasprintf ("a,xsize=%.3f,ysize=%.3f,xorigin=%.3f,yorigin=%.3f", + (double) size / PSUS, (double) size / PSUS, + (double) x_origin / PSUS, (double) y_origin / PSUS); + + params = pl_newplparams (); + pl_setplparam (params, "PAGESIZE", page_size); + free (page_size); + lp = pl_newpl_r ("ps", 0, file, stderr, params); + pl_deleteplparams (params); + + if (lp == NULL) + { + fclose (file); + return; + } + + /* Draw chart and free plotter. */ + chart_draw (chart, lp); + pl_deletepl_r (lp); + + /* Write prologue for chart. */ + outp_eject_page (this); + fprintf (x->file, + "/sp save def\n" + "%d %d translate 1000 dup scale\n" + "userdict begin\n" + "/showpage { } def\n" + "0 setgray 0 setlinecap 1 setlinewidth\n" + "0 setlinejoin 10 setmiterlimit [ ] 0 setdash newpath clear\n" + "%%%%BeginDocument: %d\n", + -x->left_margin, -x->bottom_margin, + x->doc_num++); + + /* Copy chart into output file. */ + rewind (file); + while (fwrite (buf, 1, fread (buf, 1, sizeof buf, file), x->file)) + continue; + fclose (file); + + /* Write epilogue for chart. */ + fputs ("%%EndDocument\n" + "end\n" + "sp restore\n", + x->file); + outp_close_page (this); +} + static void ps_submit (struct outp_driver *this UNUSED, struct som_entity *s) { switch (s->type) { - case SOM_CHART: - break; default: NOT_REACHED (); } @@ -1087,72 +1158,6 @@ ps_text_draw (struct outp_driver *this, const struct outp_text *t) text (this, t, true, NULL, NULL); } -static void -ps_chart_initialise (struct outp_driver *this UNUSED, struct chart *ch) -{ -#ifdef NO_CHARTS - ch->lp = NULL; -#else - struct ps_driver_ext *x = this->ext; - char page_size[128]; - int size; - int x_origin, y_origin; - - ch->file = tmpfile (); - if (ch->file == NULL) - { - ch->lp = NULL; - return; - } - - size = this->width < this->length ? this->width : this->length; - x_origin = x->left_margin + (size - this->width) / 2; - y_origin = x->bottom_margin + (size - this->length) / 2; - - snprintf (page_size, sizeof page_size, - "a,xsize=%.3f,ysize=%.3f,xorigin=%.3f,yorigin=%.3f", - (double) size / PSUS, (double) size / PSUS, - (double) x_origin / PSUS, (double) y_origin / PSUS); - - ch->pl_params = pl_newplparams (); - pl_setplparam (ch->pl_params, "PAGESIZE", page_size); - ch->lp = pl_newpl_r ("ps", NULL, ch->file, stderr, ch->pl_params); -#endif -} - -static void -ps_chart_finalise (struct outp_driver *this UNUSED, struct chart *ch UNUSED) -{ -#ifndef NO_CHARTS - struct ps_driver_ext *x = this->ext; - char buf[BUFSIZ]; - static int doc_num = 0; - - outp_eject_page (this); - fprintf (x->file, - "/sp save def\n" - "%d %d translate 1000 dup scale\n" - "userdict begin\n" - "/showpage { } def\n" - "0 setgray 0 setlinecap 1 setlinewidth\n" - "0 setlinejoin 10 setmiterlimit [ ] 0 setdash newpath clear\n" - "%%%%BeginDocument: %d\n", - -x->left_margin, -x->bottom_margin, - doc_num++); - - rewind (ch->file); - while (fwrite (buf, 1, fread (buf, 1, sizeof buf, ch->file), x->file)) - continue; - fclose (ch->file); - - fputs ("%%EndDocument\n" - "end\n" - "sp restore\n", - x->file); - outp_close_page (this); -#endif -} - static void embed_font (struct outp_driver *this, struct font *font); static void reencode_font (struct outp_driver *this, struct font *font); @@ -1440,12 +1445,11 @@ const struct outp_class postscript_class = ps_close_page, NULL, + ps_output_chart, + ps_submit, ps_line, ps_text_metrics, ps_text_draw, - - ps_chart_initialise, - ps_chart_finalise }; -- 2.30.2