From 02ef5fef5288b80a4822e1006f6cb2b1369a55bd Mon Sep 17 00:00:00 2001 From: John Darrington Date: Fri, 29 Oct 2004 06:02:44 +0000 Subject: [PATCH] Added framework for charts --- src/Makefile.am | 10 +- src/barchart.c | 253 +++++++++++++++++++++++++++++++++++++ src/box-whisker.c | 218 ++++++++++++++++++++++++++++++++ src/cartesian.c | 313 ++++++++++++++++++++++++++++++++++++++++++++++ src/chart.c | 173 +++++++++++++++++++++++++ src/chart.h | 179 ++++++++++++++++++++++++++ src/frequencies.q | 54 +++++++- src/histogram.c | 258 ++++++++++++++++++++++++++++++++++++++ src/piechart.c | 212 +++++++++++++++++++++++++++++++ 9 files changed, 1665 insertions(+), 5 deletions(-) create mode 100644 src/barchart.c create mode 100644 src/box-whisker.c create mode 100644 src/cartesian.c create mode 100644 src/chart.c create mode 100644 src/chart.h create mode 100644 src/histogram.c create mode 100644 src/piechart.c diff --git a/src/Makefile.am b/src/Makefile.am index c4ad149a..3a857474 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -71,10 +71,18 @@ split-file.c str.c str.h subclist.c subclist.h \ sysfile-info.c tab.c tab.h temporary.c stat.h \ title.c val.h val-labs.c value-labels.c value-labels.h \ var-labs.c var.h vars-atr.c vars-prs.c vector.c version.c version.h \ -vfm.c vfm.h vfmP.h weight.c +vfm.c vfm.h vfmP.h weight.c \ +barchart.c \ +box-whisker.c \ +cartesian.c \ +chart.c \ +chart.h \ +histogram.c \ +piechart.c pspp_LDADD = ../lib/julcal/libjulcal.a \ ../lib/misc/libmisc.a \ + -lplot \ @LIBINTL@ version.c: diff --git a/src/barchart.c b/src/barchart.c new file mode 100644 index 00000000..e47a541a --- /dev/null +++ b/src/barchart.c @@ -0,0 +1,253 @@ +/* PSPP - computes sample statistics. + Copyright (C) 2004 Free Software Foundation, Inc. + Written by John Darrington + + 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 2 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. */ + + +#include +#include +#include +#include +#include "chart.h" + +#define CATAGORIES 6 +#define SUB_CATAGORIES 3 + + + +static const double x_min = 0; +static const double x_max = 15.0; + +static const char *cat_labels[] = + { + "Age", + "Intelligence", + "Wealth", + "Emotional", + "cat 5", + "cat 6", + "cat 7", + "cat 8", + "cat 9", + "cat 10", + "cat 11" + }; + + + + +/* Subcatagories */ +static const double data1[] = +{ + 28,83, + 34, + 29,13, + 9,4, + 3,3, + 2,0, + 1,0, + 0, + 1,1 +}; + + +static const double data2[] = +{ + 45,13, + 9,4, + 3,43, + 2,0, + 1,20, + 0,0, + 1,1, + 0,0 +}; + +static const double data3[] = + { + 23,18, + 0, 45,23, 9, 40, 24,4, 8 + }; + + +static const char subcat_name[]="Gender"; + + +struct subcat { + const double *data; + char *label; +}; + +static const struct subcat sub_catagory[SUB_CATAGORIES] = + { + {data1, "male"}, + {data2, "female"}, + {data3, "47xxy"} + }; + + + +static const double y_min = 0; +static const double y_max = 120.0; +static const double y_tick = 20.0; + + + +static void write_legend(struct chart *chart) ; + + +void +draw_barchart(struct chart *ch, const char *title, + const char *xlabel, const char *ylabel, enum bar_opts opt) +{ + + double d; + int i; + + double interval_size = fabs(ch->data_right - ch->data_left) / ( CATAGORIES ); + + double bar_width = interval_size / 1.1 ; + + if ( opt != BAR_STACKED ) + bar_width /= SUB_CATAGORIES; + + double ordinate_scale = fabs(ch->data_top - ch->data_bottom) / fabs(y_max - y_min) ; + + /* Move to data bottom-left */ + pl_move_r(ch->lp, ch->data_left, ch->data_bottom); + + pl_savestate_r(ch->lp); + pl_filltype_r(ch->lp,1); + + /* Draw the data */ + for (i = 0 ; i < CATAGORIES ; ++i ) + { + int sc; + double ystart=0.0; + double x = i * interval_size; + + pl_savestate_r(ch->lp); + + draw_tick (ch, TICK_ABSCISSA, x + (interval_size/2 ), + cat_labels[i]); + + for(sc = 0 ; sc < SUB_CATAGORIES ; ++sc ) + { + + pl_savestate_r(ch->lp); + pl_fillcolorname_r(ch->lp,data_colour[sc]); + + switch ( opt ) + { + case BAR_GROUPED: + pl_fboxrel_r(ch->lp, + x + (sc * bar_width ), 0, + x + (sc + 1) * bar_width, + sub_catagory[sc].data[i] * ordinate_scale ); + break; + + + case BAR_STACKED: + + pl_fboxrel_r(ch->lp, + x, ystart, + x + bar_width, + ystart + sub_catagory[sc].data[i] * ordinate_scale ); + + ystart += sub_catagory[sc].data[i] * ordinate_scale ; + + break; + + default: + break; + } + pl_restorestate_r(ch->lp); + } + + pl_restorestate_r(ch->lp); + } + pl_restorestate_r(ch->lp); + + for ( d = y_min; d <= y_max ; d += y_tick ) + { + + draw_tick (ch, TICK_ORDINATE, + (d - y_min ) * ordinate_scale, "%g", d); + + } + + /* Write the abscissa label */ + pl_move_r(ch->lp,ch->data_left, ch->abscissa_top); + pl_alabel_r(ch->lp,0,'t',xlabel); + + + /* Write the ordinate label */ + 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,ylabel); + pl_restorestate_r(ch->lp); + + + chart_write_title(ch, title); + + write_legend(ch); + + +} + + + + + +static void +write_legend(struct chart *chart) +{ + int sc; + + pl_savestate_r(chart->lp); + + pl_filltype_r(chart->lp,1); + + pl_move_r(chart->lp, chart->legend_left, + chart->data_bottom + chart->font_size * SUB_CATAGORIES * 1.5); + + pl_alabel_r(chart->lp,0,'b',subcat_name); + + for (sc = 0 ; sc < SUB_CATAGORIES ; ++sc ) + { + pl_fmove_r(chart->lp, + chart->legend_left, + chart->data_bottom + chart->font_size * sc * 1.5); + + pl_savestate_r(chart->lp); + pl_fillcolorname_r(chart->lp,data_colour[sc]); + pl_fboxrel_r (chart->lp, + 0,0, + chart->font_size, chart->font_size); + pl_restorestate_r(chart->lp); + + pl_fmove_r(chart->lp, + chart->legend_left + chart->font_size * 1.5, + chart->data_bottom + chart->font_size * sc * 1.5); + + pl_alabel_r(chart->lp,'l','b',sub_catagory[sc].label); + } + + + pl_restorestate_r(chart->lp); +} diff --git a/src/box-whisker.c b/src/box-whisker.c new file mode 100644 index 00000000..fa52476e --- /dev/null +++ b/src/box-whisker.c @@ -0,0 +1,218 @@ +/* PSPP - computes sample statistics. + Copyright (C) 2004 Free Software Foundation, Inc. + Written by John Darrington + + 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 2 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. */ + + +#include "chart.h" +#include + +/* Draw a box-and-whiskers plot +*/ + +struct data_stats +{ + double ptile0 ; + double ptile25 ; + double median ; + double ptile75 ; + + double ptile100; + + double outlier ; +}; + + +const struct data_stats stats1 = { + 40, + 45, + 54, + 60, + 70, + + 33 +}; + +const struct data_stats stats2 = { + 30, + 40, + 45, + 54, + 60, + + + 72 +}; + + + + + +static const double y_min = 25; +static const double y_max = 75; +static const double y_tick = 10; + + + +#define min(A,B) ((A>B)?B:A) + + +void draw_box_and_whiskers(struct chart *ch, + double box_centre, const struct data_stats *s, + const char *name); + + +static double ordinate_scale; + +void +draw_box_whisker_chart(struct chart *ch, const char *title) +{ + double d; + + ordinate_scale = fabs(ch->data_top - ch->data_bottom) / fabs(y_max - y_min) ; + + + chart_write_title(ch, title); + + + + /* Move to data bottom-left */ + pl_move_r(ch->lp, + ch->data_left, ch->data_bottom); + + for ( d = y_min; d <= y_max ; d += y_tick ) + { + draw_tick (ch, TICK_ORDINATE, (d - y_min ) * ordinate_scale, "%g", d); + } + + draw_box_and_whiskers(ch, + ch->data_left + 1.0/4.0 * (ch->data_right - ch->data_left) , + &stats1,"Stats1" + ); + + draw_box_and_whiskers(ch, + ch->data_left + 3.0/4.0 * (ch->data_right - ch->data_left), + &stats2,"Stats2" + ); + + +} + + +void +draw_box_and_whiskers(struct chart *ch, + double box_centre, const struct data_stats *s, + const char *name) +{ + + const double box_width = (ch->data_right - ch->data_left) / 4.0; + + const double box_left = box_centre - box_width / 2.0; + + const double box_right = box_centre + box_width / 2.0; + + + const double box_bottom = + ch->data_bottom + ( s->ptile25 - y_min ) * ordinate_scale; + + + const double box_top = + ch->data_bottom + ( s->ptile75 - y_min ) * ordinate_scale; + + + const double iq_range = s->ptile75 - s->ptile25; + + const double bottom_whisker = + ch->data_bottom + (min(s->ptile0,s->ptile25 + iq_range*1.5) - y_min ) * + ordinate_scale; + + const double top_whisker = + ch->data_bottom + (min(s->ptile100,s->ptile75 + iq_range*1.5) - y_min ) * + ordinate_scale; + + pl_savestate_r(ch->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, + box_left, + box_bottom, + box_right, + box_top); + + pl_restorestate_r(ch->lp); + + + + /* Draw the median */ + pl_savestate_r(ch->lp); + pl_linewidth_r(ch->lp,5); + pl_fline_r(ch->lp, + box_left, + ch->data_bottom + ( s->median - y_min ) * ordinate_scale, + box_right, + ch->data_bottom + ( s->median - y_min ) * ordinate_scale); + pl_restorestate_r(ch->lp); + + + /* Draw the bottom whisker */ + pl_fline_r(ch->lp, + box_left, + bottom_whisker, + box_right, + bottom_whisker); + + /* Draw top whisker */ + pl_fline_r(ch->lp, + box_left, + top_whisker, + box_right, + top_whisker); + + + /* Draw centre line. + (bottom half) */ + pl_fline_r(ch->lp, + box_centre, bottom_whisker, + box_centre, box_bottom); + + /* (top half) */ + pl_fline_r(ch->lp, + box_centre, top_whisker, + box_centre, box_top); + + + /* Draw an outlier */ + pl_fcircle_r(ch->lp, + box_centre, + ch->data_bottom + (s->outlier - y_min ) * ordinate_scale, + 5); + + pl_moverel_r(ch->lp, 10,0); + pl_alabel_r(ch->lp,'l','c',"123"); + + + /* Draw tick mark on x axis */ + draw_tick(ch, TICK_ABSCISSA, box_centre - ch->data_left, name); + + pl_restorestate_r(ch->lp); + +} + diff --git a/src/cartesian.c b/src/cartesian.c new file mode 100644 index 00000000..7fc74678 --- /dev/null +++ b/src/cartesian.c @@ -0,0 +1,313 @@ +/* PSPP - computes sample statistics. + Copyright (C) 2004 Free Software Foundation, Inc. + Written by John Darrington + + 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 2 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. */ + + +#include +#include "chart.h" +#include + + + +static const double y_min = 15; +static const double y_max = 120.0; +static const double y_tick = 20.0; + + + +static const double x_min = -11.0; +static const double x_max = 19.0; +static const double x_tick = 5.0; + + +struct datum +{ + double x; + double y; +}; + + +static const struct datum data1[]= + { + { -8.0, 29 }, + { -3.7, 45 }, + { -3.3, 67 }, + { -0.8, 89 }, + { -0.2, 93 }, + { 1.0, 100}, + { 2.3, 103}, + { 4.0, 103.4}, + { 5.2, 104}, + { 5.9, 106}, + { 10.3, 106}, + { 13.8, 108}, + { 15.8, 109}, + }; + + + + +static const struct datum data2[]= + { + { -9.1, 20 }, + { -8.2, 17 }, + { -5.0, 19 }, + { -3.7, 25 }, + { -1.6, 49 }, + { -1.3, 61 }, + { -1.1, 81 }, + { 3.5, 91}, + { 5.4, 93}, + { 9.3, 94}, + { 14.3, 92} + }; + + + + +struct dataset +{ + const struct datum *data; + int n_data; + char *label; +}; + + + +#define DATASETS 2 + +static const struct dataset dataset[DATASETS] = + { + {data1, 13, "male"}, + {data2, 11, "female"}, + }; + + + +typedef void (*plot_func) (struct chart *ch, const struct dataset *dataset); + + +void plot_line(struct chart *ch, const struct dataset *dataset); + +void plot_scatter(struct chart *ch, const struct dataset *dataset); + + + +static void +write_legend(struct chart *chart, const char *heading, int n); + +void draw_cartesian(struct chart *ch, const char *title, + const char *xlabel, const char *ylabel, plot_func pf); + + + +void +draw_scatterplot(struct chart *ch, const char *title, + const char *xlabel, const char *ylabel) +{ + draw_cartesian(ch, title, xlabel, ylabel, plot_scatter); +} + + +void +draw_lineplot(struct chart *ch, const char *title, + const char *xlabel, const char *ylabel) +{ + draw_cartesian(ch, title, xlabel, ylabel, plot_scatter); +} + + +void +draw_cartesian(struct chart *ch, const char *title, + const char *xlabel, const char *ylabel, plot_func pf) +{ + double x; + double y; + + + int d; + + + const double ordinate_scale = + fabs(ch->data_top - ch->data_bottom) + / fabs(y_max - y_min) ; + + + const double abscissa_scale = + fabs(ch->data_right - ch->data_left) + / + fabs(x_max - x_min); + + + /* Move to data bottom-left */ + pl_move_r(ch->lp, ch->data_left, ch->data_bottom); + + pl_savestate_r(ch->lp); + + + for(x = x_tick * ceil(x_min / x_tick ) ; + x < x_max; + x += x_tick ) + draw_tick (ch, TICK_ABSCISSA, (x - x_min) * abscissa_scale, "%g", x); + + for(y = y_tick * ceil(y_min / y_tick ) ; + y < y_max; + y += y_tick ) + draw_tick (ch, TICK_ORDINATE, (y - y_min) * ordinate_scale, "%g", y); + + pl_savestate_r(ch->lp); + + for (d = 0 ; d < DATASETS ; ++d ) + { + pl_pencolorname_r(ch->lp,data_colour[d]); + pf(ch, &dataset[d]); + } + + pl_restorestate_r(ch->lp); + + /* Write the abscissa label */ + pl_move_r(ch->lp,ch->data_left, ch->abscissa_top); + pl_alabel_r(ch->lp,0,'t',xlabel); + + + /* Write the ordinate label */ + 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,ylabel); + pl_restorestate_r(ch->lp); + + + chart_write_title(ch, title); + + write_legend(ch,"Key:",DATASETS); + + pl_restorestate_r(ch->lp); + +} + + + + +static void +write_legend(struct chart *chart, const char *heading, + int n) +{ + int ds; + + pl_savestate_r(chart->lp); + + pl_filltype_r(chart->lp,1); + + pl_move_r(chart->lp, chart->legend_left, + chart->data_bottom + chart->font_size * n * 1.5); + + pl_alabel_r(chart->lp,0,'b',heading); + + for (ds = 0 ; ds < n ; ++ds ) + { + pl_fmove_r(chart->lp, + chart->legend_left, + chart->data_bottom + chart->font_size * ds * 1.5); + + pl_savestate_r(chart->lp); + pl_fillcolorname_r(chart->lp,data_colour[ds]); + pl_fboxrel_r (chart->lp, + 0,0, + chart->font_size, chart->font_size); + pl_restorestate_r(chart->lp); + + pl_fmove_r(chart->lp, + chart->legend_left + chart->font_size * 1.5, + chart->data_bottom + chart->font_size * ds * 1.5); + + pl_alabel_r(chart->lp,'l','b',dataset[ds].label); + } + + + pl_restorestate_r(chart->lp); +} + + + +void +plot_line(struct chart *ch, const struct dataset *dataset) +{ + int i; + + const struct datum *data = dataset->data; + + const double ordinate_scale = + fabs(ch->data_top - ch->data_bottom) + / fabs(y_max - y_min) ; + + + const double abscissa_scale = + fabs(ch->data_right - ch->data_left) + / + fabs(x_max - x_min); + + + for( i = 0 ; i < dataset->n_data ; ++i ) + { + const double x = + (data[i].x - x_min) * abscissa_scale + ch->data_left ; + const double y = + (data[i].y - y_min) * ordinate_scale + ch->data_bottom; + + if (i == 0 ) + pl_move_r(ch->lp, x, y ); + else + pl_fcont_r(ch->lp, x, y); + } + pl_endpath_r(ch->lp); + +} + + + + +void +plot_scatter(struct chart *ch, const struct dataset *dataset) +{ + int i; + + const struct datum *data = dataset->data; + + const double ordinate_scale = + fabs(ch->data_top - ch->data_bottom) + / fabs(y_max - y_min) ; + + + const double abscissa_scale = + fabs(ch->data_right - ch->data_left) + / + fabs(x_max - x_min); + + + for( i = 0 ; i < dataset->n_data ; ++i ) + { + const double x = + (data[i].x - x_min) * abscissa_scale + ch->data_left ; + const double y = + (data[i].y - y_min) * ordinate_scale + ch->data_bottom; + + pl_fmarker_r(ch->lp, x, y, 6, 15); + } + +} diff --git a/src/chart.c b/src/chart.c new file mode 100644 index 00000000..abf4e70b --- /dev/null +++ b/src/chart.c @@ -0,0 +1,173 @@ +/* PSPP - computes sample statistics. + Copyright (C) 2004 Free Software Foundation, Inc. + Written by John Darrington + + 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 2 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. */ + + +#include +#include +#include +#include +#include +#include +#include + +#include "chart.h" + + +const char *data_colour[] = { + "brown", + "red", + "orange", + "yellow", + "green", + "blue", + "violet", + "grey", + "pink" +}; + + + +int +chart_initialise(struct chart *chart) +{ + + chart->pl_params = pl_newplparams(); + + chart->lp = pl_newpl_r ("X",0,stdout,stderr,chart->pl_params); + + if (pl_openpl_r (chart->lp) < 0) /* open Plotter */ + return 1; + + pl_fspace_r (chart->lp, 0.0, 0.0, 1000.0, 1000.0); /* set coordinate system */ + pl_flinewidth_r (chart->lp, 0.25); /* set line thickness */ + pl_pencolorname_r (chart->lp, "black"); + + pl_erase_r (chart->lp); /* erase graphics display */ + pl_filltype_r(chart->lp,0); + + + + pl_savestate_r(chart->lp); + + /* Set default chartetry */ + chart->data_top = 900; + chart->data_right = 800; + chart->data_bottom = 120; + chart->data_left = 150; + chart->abscissa_top = 70; + chart->ordinate_right = 120; + chart->title_bottom = 920; + chart->legend_left = 810; + chart->legend_right = 1000; + chart->font_size = 0; + strcpy(chart->fill_colour,"red"); + + + /* Get default font size */ + if ( !chart->font_size) + chart->font_size = pl_fontsize_r(chart->lp, -1); + + /* Draw the data area */ + pl_box_r(chart->lp, + chart->data_left, chart->data_bottom, + chart->data_right, chart->data_top); + + return 0; + +} + + + +/* Draw a tick mark at position + 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, ...) +{ + const int tickSize = 10; + + pl_savestate_r(chart->lp); + + pl_move_r(chart->lp, chart->data_left, chart->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); + else + assert(0); + + 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); + } + + pl_restorestate_r(chart->lp); +} + + + + +void +chart_write_title(struct chart *chart, const char *title) +{ + /* Write the title */ + 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); + pl_alabel_r(chart->lp,0,0,title); + pl_restorestate_r(chart->lp); +} + + + +void +chart_finalise(struct chart *chart) +{ + pl_restorestate_r(chart->lp); + + if (pl_closepl_r (chart->lp) < 0) /* close Plotter */ + { + fprintf (stderr, "Couldn't close Plotter\n"); + } + + + pl_deletepl_r(chart->lp); + + pl_deleteplparams(chart->pl_params); + +} + diff --git a/src/chart.h b/src/chart.h new file mode 100644 index 00000000..47c6c7d8 --- /dev/null +++ b/src/chart.h @@ -0,0 +1,179 @@ +/* PSPP - computes sample statistics. + Copyright (C) 2004 Free Software Foundation, Inc. + Written by John Darrington + + 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 2 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. */ + + +#ifndef CHART_H +#define CHART_H + +#include +#include +#include "var.h" + + +/* Array of standard colour names */ +extern const char *data_colour[]; + + +struct chart { + + plPlotter *lp ; + plPlotterParams *pl_params; + + /* 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 ; + + 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]; + +}; + + +int chart_initialise(struct chart *ch); + +void chart_finalise(struct chart *ch); + + + +void chart_write_title(struct chart *ch, + const char *title); + +enum tick_orientation { + TICK_ABSCISSA=0, + TICK_ORDINATE +}; + +void draw_tick(struct chart *ch, enum tick_orientation orientation, + double position, const char *label, ...); + + + +enum bar_opts { + BAR_GROUPED = 0, + BAR_STACKED, + BAR_RANGE +}; + + +void draw_barchart(struct chart *ch, const char *title, + const char *xlabel, const char *ylabel, enum bar_opts opt); + +void draw_box_whisker_chart(struct chart *ch, const char *title); + + + +struct normal_curve +{ + double N ; + double mean ; + double stddev ; +}; + + +void draw_histogram(struct chart *ch, + const struct variable *v, + const char *title, + struct normal_curve *norm, + int show_normal); + + + +void draw_piechart(struct chart *ch, const struct variable *v); + +void draw_scatterplot(struct chart *ch, const char *title, + const char *xlabel, const char *ylabel); + + +void draw_lineplot(struct chart *ch, const char *title, + const char *xlabel, const char *ylabel); + + + +#endif + +#if 0 +The anatomy of a chart is as follows. + ++-------------------------------------------------------------+ +| +----------------------------------+ | +| | | | +| | Title | | +| | | | +| +----------------------------------+ | +|+----------++----------------------------------++-----------+| +|| || || || +|| || || || +|| || || || +|| || || || +|| || || || +|| || || || +|| || || || +|| || || || +|| || || || +|| || || || +|| Ordinate || Data || Legend || +|| || || || +|| || || || +|| || || || +|| || || || +|| || || || +|| || || || +|| || || || +|| || || || +|| || || || +|| || || || +|+----------++----------------------------------++-----------+| -- +| +----------------------------------+ | - ^ data_bottom +| | Abscissa | | ^ | +| | | | | abscissa_top +| +----------------------------------+ | v v ++-------------------------------------------------------------+ ---- + +ordinate_right || | +| | || | +|<--------->| || | +| | || | +| data_left | || | +|<---------->| || | +| || | +| data_right || | +|<--------------------------------------------->|| | +| legend_left | | +|<---------------------------------------------->| | +| legend_right | +|<---------------------------------------------------------->| + +#endif diff --git a/src/frequencies.q b/src/frequencies.q index a6b82f6f..9eb17172 100644 --- a/src/frequencies.q +++ b/src/frequencies.q @@ -47,6 +47,7 @@ #include "var.h" #include "vfm.h" #include "settings.h" +#include "chart.h" #include "debug-print.h" @@ -63,6 +64,9 @@ barchart(ba_)=:minimum(d:min), :maximum(d:max), scale:freq(*n:freq,"%s>0")/percent(*n:pcnt,"%s>0"); + piechart(pie_)=:minimum(d:min), + :maximum(d:max), + missing:missing/!nomissing; histogram(hi_)=:minimum(d:min), :maximum(d:max), scale:freq(*n:freq,"%s>0")/percent(*n:pcnt,"%s>0"), @@ -148,6 +152,7 @@ enum GFT_NONE, /* Don't draw graphs. */ GFT_BAR, /* Draw bar charts. */ GFT_HIST, /* Draw histograms. */ + GFT_PIE, /* Draw piechart */ GFT_HBAR /* Draw bar charts or histograms at our discretion. */ }; @@ -155,7 +160,8 @@ enum static struct cmd_frequencies cmd; /* Summary of the barchart, histogram, and hbar subcommands. */ -static int chart; /* NONE/BAR/HIST/HBAR. */ +/* FIXME: These should not be mututally exclusive */ +static int chart; /* NONE/BAR/HIST/HBAR/PIE. */ static double min, max; /* Minimum, maximum on y axis. */ static int format; /* FREQ/PERCENT: Scaling of y axis. */ static double scale, incr; /* FIXME */ @@ -174,6 +180,8 @@ static struct pool *gen_pool; /* General mode. */ static void determine_charts (void); +static void calc_stats (struct variable * v, double d[frq_n_stats]); + static void precalc (void *); static int calc (struct ccase *, void *); static void postcalc (void *); @@ -267,7 +275,8 @@ internal_cmd_frequencies (void) static void determine_charts (void) { - int count = (!!cmd.sbc_histogram) + (!!cmd.sbc_barchart) + (!!cmd.sbc_hbar); + int count = (!!cmd.sbc_histogram) + (!!cmd.sbc_barchart) + + (!!cmd.sbc_hbar) + (!!cmd.sbc_piechart); if (!count) { @@ -285,6 +294,8 @@ determine_charts (void) chart = GFT_HIST; else if (cmd.sbc_barchart) chart = GFT_BAR; + else if (cmd.sbc_piechart) + chart = GFT_PIE; else chart = GFT_HBAR; @@ -328,7 +339,7 @@ determine_charts (void) format = FRQ_PERCENT; scale = cmd.ba_pcnt; } - if (cmd.hi_norm) + if (cmd.hi_norm != FRQ_NONORMAL ) normal = 1; if (cmd.hi_incr == FRQ_INCREMENT) incr = cmd.hi_inc; @@ -385,9 +396,10 @@ calc (struct ccase *c, void *aux UNUSED) { case FRQM_GENERAL: { + /* General mode. */ struct freq **fpp = (struct freq **) hsh_probe (ft->data, val); - + if (*fpp != NULL) (*fpp)->c += weight; else @@ -504,7 +516,40 @@ postcalc (void *aux UNUSED) if (n_stats) dump_statistics (v, !dumped_freq_tab); + + if ( chart == GFT_HIST) + { + struct chart ch; + double d[frq_n_stats]; + struct frequencies_proc *frq = &v->p.frq; + + struct normal_curve norm; + norm.N = frq->tab.total_cases ; + + calc_stats(v,d); + norm.mean = d[frq_mean]; + norm.stddev = d[frq_stddev]; + + chart_initialise(&ch); + draw_histogram(&ch, v_variables[i], "HISTOGRAM",&norm,normal); + chart_finalise(&ch); + } + + + if ( chart == GFT_PIE) + { + struct chart ch; + + chart_initialise(&ch); + + draw_piechart(&ch, v_variables[i]); + + chart_finalise(&ch); + } + + cleanup_freq_tab (v); + } } @@ -1102,6 +1147,7 @@ dump_full (struct variable * v) tab_title (t, 1, "%s: %s", v->name, v->label ? v->label : ""); tab_submit (t); + } /* Sets the widths of all the columns and heights of all the rows in diff --git a/src/histogram.c b/src/histogram.c new file mode 100644 index 00000000..d4403416 --- /dev/null +++ b/src/histogram.c @@ -0,0 +1,258 @@ +/* PSPP - computes sample statistics. + Copyright (C) 2004 Free Software Foundation, Inc. + Written by John Darrington + + 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 2 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, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + 02111-1307, USA. */ + +#include +#include +#include +#include +#include "hash.h" + +#include "var.h" +#include "chart.h" + +/* Number of bins in which to divide data */ +#define BINS 15 + +/* The approximate no of ticks on the y axis */ +#define YTICKS 10 + +#define M_PI ( 22.0 / 7.0 ) + + +static double gaussian(double x, double mu, double sigma ) ; + + +static double +gaussian(double x, double mu, double sigma ) +{ + return (exp( - (( x - mu )* (x - mu) / (2.0 * sigma * sigma) )) + / ( sigma * sqrt( M_PI * 2.0) )) ; +} + + +/* Adjust tick to be a sensible value */ +void adjust_tick(double *tick); + + +/* Write the legend of the chart */ +static void +write_legend(struct chart *ch, struct normal_curve *norm) +{ + char buf[100]; + pl_savestate_r(ch->lp); + + sprintf(buf,"N = %.2f",norm->N); + pl_move_r(ch->lp, ch->legend_left, ch->data_bottom); + pl_alabel_r(ch->lp,0,'b',buf); + + sprintf(buf,"Mean = %.1f",norm->mean); + pl_fmove_r(ch->lp,ch->legend_left,ch->data_bottom + ch->font_size * 1.5); + pl_alabel_r(ch->lp,0,'b',buf); + + sprintf(buf,"Std. Dev = %.2f",norm->stddev); + pl_fmove_r(ch->lp,ch->legend_left,ch->data_bottom + ch->font_size * 1.5 * 2); + pl_alabel_r(ch->lp,0,'b',buf); + + pl_restorestate_r(ch->lp); +} + + + + +/* Draw a histogram. + If show_normal is non zero then superimpose a normal curve +*/ +void +draw_histogram(struct chart *ch, + const struct variable *var, + const char *title, + struct normal_curve *norm, + int show_normal) +{ + + double d; + int count; + + double x_min = DBL_MAX; + double x_max = -DBL_MAX; + double y_min = DBL_MAX; + double y_max = -DBL_MAX; + + double y_tick ; + + + double ordinate_values[BINS]; + + const struct freq_tab *frq_tab = &var->p.frq.tab ; + + struct hsh_iterator hi; + struct hsh_table *fh = frq_tab->data; + struct freq *frq; + + double interval_size = fabs(ch->data_right - ch->data_left) / ( BINS ); + + /* Find out the extremes of the x value + FIXME: These don't need to be calculated here, since the + calling routine should know them */ + + for ( frq = hsh_first(fh,&hi); frq != 0; frq = hsh_next(fh,&hi) ) + { + if ( frq->v.f < x_min ) x_min = frq->v.f ; + if ( frq->v.f > x_max ) x_max = frq->v.f ; + } + + double x_interval = fabs(x_max - x_min) / ( BINS - 1); + + + double abscissa_scale = + fabs( (ch->data_right - ch->data_left) / (x_max - x_min) ) ; + + + /* Find out the bin values */ + for ( count = 0, d = x_min; d <= x_max ; d += x_interval, ++count ) + { + + double y = 0; + + for ( frq = hsh_first(fh,&hi); frq != 0; frq = hsh_next(fh,&hi) ) + { + if ( frq->v.f >= d && frq->v.f < d + x_interval ) + y += frq->c; + } + + ordinate_values[count] = y ; + + if ( y > y_max ) y_max = y ; + if ( y < y_min ) y_min = y; + } + + y_tick = ( y_max - y_min ) / (double) (YTICKS - 1) ; + + adjust_tick(&y_tick); + + y_min = floor( y_min / y_tick ) * y_tick ; + y_max = ceil( y_max / y_tick ) * y_tick ; + + double ordinate_scale = + fabs(ch->data_top - ch->data_bottom) / fabs(y_max - y_min) ; + + + /* Move to data bottom-left */ + pl_move_r(ch->lp, ch->data_left, ch->data_bottom); + + pl_savestate_r(ch->lp); + pl_fillcolorname_r(ch->lp, ch->fill_colour); + pl_filltype_r(ch->lp,1); + + /* Draw the histogram */ + for ( count = 0, d = x_min; d <= x_max ; d += x_interval, ++count ) + { + const double x = count * interval_size ; + pl_savestate_r(ch->lp); + + draw_tick (ch, TICK_ABSCISSA, x + (interval_size / 2.0 ), "%.1f",d); + + pl_fboxrel_r(ch->lp, x, 0, x + interval_size, + ordinate_values[count] * ordinate_scale ); + + pl_restorestate_r(ch->lp); + + } + pl_restorestate_r(ch->lp); + + /* Put the y axis on */ + for ( d = y_min; d <= y_max ; d += y_tick ) + { + draw_tick (ch, TICK_ORDINATE, (d - y_min ) * ordinate_scale, "%g", d); + } + + /* Write the abscissa label */ + pl_move_r(ch->lp,ch->data_left, ch->abscissa_top); + pl_alabel_r(ch->lp,0,'t',var->label ? var->label : var->name); + + + /* Write the ordinate label */ + 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,"Frequency"); + + pl_restorestate_r(ch->lp); + + + chart_write_title(ch, title); + + + /* Write the legend */ + write_legend(ch,norm); + + + if ( show_normal ) + { + /* Draw the normal curve */ + double d; + + 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) + { + const double x = (d - ch->data_left - interval_size / 2.0 ) / + abscissa_scale + x_min ; + + pl_fcont_r(ch->lp, d, + ch->data_bottom + + norm->N * gaussian(x, norm->mean, norm->stddev) + * ordinate_scale); + + } + pl_endpath_r(ch->lp); + } + +} + + + +double +log10(double x) +{ + return log(x) / log(10.0) ; +} + + +/* Adjust tick to be a sensible value */ +void +adjust_tick(double *tick) +{ + int i; + const double standard_ticks[] = {1, 2, 5}; + + const double factor = pow(10,ceil(log10(standard_ticks[0] / *tick))) ; + + for (i = 2 ; i >=0 ; --i) + { + if ( *tick > standard_ticks[i] / factor ) + { + *tick = standard_ticks[i] / factor ; + break; + } + } + + } + diff --git a/src/piechart.c b/src/piechart.c new file mode 100644 index 00000000..29e0c791 --- /dev/null +++ b/src/piechart.c @@ -0,0 +1,212 @@ +/* PSPP - draws pie charts of sample statistics + +Copyright (C) 2004 Free Software Foundation, Inc. +Written by John Darrington + +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 2 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, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +02111-1307, USA. */ + + +#include "chart.h" +#include +#include +#include +#include +#include "value-labels.h" + + +/* Pie charts of course need to know Pi :) */ +#define M_PI ( 22.0 / 7.0 ) + + +#define min(A,B) ((A>B)?B:A) + + +/* Draw a single slice of the pie */ +void +draw_segment(struct chart *ch, + double centre_x, double centre_y, + double radius, + double start_angle, double segment_angle, + const char *colour) ; + + +/* Draw a pie chart */ +void +draw_piechart(struct chart *ch, const struct variable *var) +{ + int i; + + const struct freq_tab *frq_tab = &var->p.frq.tab ; + + const int n_data = frq_tab->n_valid; + const double left_label = ch->data_left + + (ch->data_right - ch->data_left)/10.0; + + const double right_label = ch->data_right - + (ch->data_right - ch->data_left)/10.0; + + const double centre_x = (ch->data_right + ch->data_left ) / 2.0 ; + const double centre_y = (ch->data_top + ch->data_bottom ) / 2.0 ; + + const double radius = min( + 5.0 / 12.0 * (ch->data_top - ch->data_bottom), + 1.0 / 4.0 * (ch->data_right - ch->data_left) + ); + + + chart_write_title(ch, var->label ? var->label: var->name); + + + for (i = 0 ; i < n_data ; ++i ) + { + static double angle=0.0; + const struct freq frq = frq_tab->valid[i]; + + const double segment_angle = + frq.c / frq_tab->valid_cases * 2 * M_PI ; + + char *label = val_labs_find (var->val_labs, frq.v ); + if ( !label ) + { + static char l[20]; + snprintf(l,20,"%g",frq.v.f); + label = l; + } + + const double label_x = centre_x - + radius * sin(angle + segment_angle/2.0); + + const double label_y = centre_y + + radius * cos(angle + segment_angle/2.0); + + /* Fill the segment */ + draw_segment(ch, + centre_x, centre_y, radius, + angle, segment_angle, + data_colour[i]); + + /* 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,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,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); + +} + + + +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 */ +void +fill_segment(struct chart *ch, + double x0, double y0, + double radius, + double start_angle, double segment_angle) +{ + + const double start_x = x0 - radius * sin(start_angle); + const double start_y = y0 + radius * cos(start_angle); + + const double stop_x = + x0 - radius * sin(start_angle + segment_angle); + + const double stop_y = + y0 + radius * cos(start_angle + segment_angle); + + assert(segment_angle <= 2 * M_PI); + assert(segment_angle >= 0); + + 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, + segment_angle / 2.0 ); + } + else + { + pl_move_r(ch->lp, x0, y0); + + pl_cont_r(ch->lp, stop_x, stop_y); + pl_cont_r(ch->lp, start_x, start_y); + + pl_arc_r(ch->lp, + x0, y0, + stop_x, stop_y, + start_x, start_y + ); + + pl_endpath_r(ch->lp); + } +} + + + +/* Draw a single slice of the pie */ +void +draw_segment(struct chart *ch, + double x0, double y0, + double radius, + double start_angle, double segment_angle, + const char *colour) +{ + 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(ch->lp); + pl_colorname_r(ch->lp, colour); + + pl_pentype_r(ch->lp,1); + pl_filltype_r(ch->lp,1); + + fill_segment(ch, x0, y0, radius, start_angle, segment_angle); + pl_restorestate_r(ch->lp); + + /* Draw line dividing segments */ + pl_pentype_r(ch->lp, 1); + pl_fline_r(ch->lp, x0, y0, start_x, start_y); + + + pl_restorestate_r(ch->lp); +} -- 2.30.2