//reports.cpp, Copyright (c) 2006-2008 R.Lackner
//
//    This file is part of RLPlot.
//
//    RLPlot 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.
//
//    RLPlot 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 RLPlot; if not, write to the Free Software
//    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// Create statistical reports
//

#include "rlplot.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <ctype.h>
#include "TheDialog.h"

extern char TmpTxt[];
extern Default defs;
extern GraphObj *LastOpenGO;

#define _PREC 1.0e-12

//prototypes: WinSpec.cpp
void *CreateDlgWnd(char *title, int x, int y, int width, int height, tag_DlgObj *d, DWORD flags);

static int curr_id, cbSymLineStr;
static fRECT dBounds;
static TextDEF txtdef1, txtdef2;
static double linsp1, linsp2;
static char SymLineStr[40];

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// init report variables
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static void rep_init()
{
	curr_id = 1;		defs.cUnits = defs.dUnits;
	txtdef1.ColTxt = txtdef2.ColTxt = 0x0L;
	txtdef1.ColBg = txtdef2.ColBg = 0x00ffffffL;
	txtdef1.fSize = defs.GetSize(SIZE_TEXT);
	txtdef2.fSize = txtdef1.fSize *1.2;
	txtdef1.RotBL = txtdef2.RotBL = 0.0;
	txtdef1.RotCHAR = txtdef2.RotCHAR = 0.0;
	txtdef1.iSize = txtdef2.iSize = 0;
	txtdef1.Align = txtdef2.Align = TXA_HLEFT | TXA_VTOP;
	txtdef1.Mode = txtdef2.Mode = TXM_TRANSPARENT;
	txtdef1.Style = txtdef2.Style = TXS_NORMAL;
	txtdef1.Font = txtdef2.Font = FONT_HELVETICA;
	txtdef1.text = txtdef2.text = 0L;
#ifdef _WINDOWS
	linsp1 = txtdef1.fSize*1.2;	linsp2 = txtdef1.fSize*1.5;
#else
	linsp1 = txtdef1.fSize*1.7;	linsp2 = txtdef1.fSize*2.5;
#endif
#ifdef USE_WIN_SECURE
	cbSymLineStr = sprintf_s(SymLineStr, 40, "Line= %g 1 0x0 0x0\n", defs.GetSize(SIZE_SYM_LINE)); 
#else
	cbSymLineStr = sprintf(SymLineStr, "Line= %g 1 0x0 0x0\n", defs.GetSize(SIZE_SYM_LINE)); 
#endif
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// create a text label for a report
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static char* mk_label(double x, double y, bool moveable, int align, TextDEF *td, char*text)
{
	int csize, pos = 0;
	char *res;

	if(!(res = (char*)malloc(csize = 1000)))return 0L;
	res[pos++] = '\n';				res[pos++] = '[';
	add_int_to_buff(&res, &pos, &csize, curr_id++, false, 0);
	add_to_buff(&res, &pos, &csize, "=Label]\nPos=", 12);
	add_dbl_to_buff(&res, &pos, &csize, x, true);
	add_dbl_to_buff(&res, &pos, &csize, y, true);
	res[pos++] = '\n';
	if(moveable) add_to_buff(&res, &pos, &csize, "moveable= 1\n", 12);
	add_to_buff(&res, &pos, &csize, "TxtDef= 0x0 0x00ffffff", 22);
	add_dbl_to_buff(&res, &pos, &csize, td->fSize, true);
	add_dbl_to_buff(&res, &pos, &csize, td->RotBL, true);
	add_dbl_to_buff(&res, &pos, &csize, td->RotCHAR, true);
	add_int_to_buff(&res, &pos, &csize, align, true, 0);
	add_to_buff(&res, &pos, &csize, " 1 0 0 \"", 8);
	add_to_buff(&res, &pos, &csize, text, 0);
	add_to_buff(&res, &pos, &csize, "\"\n", 2);
	return res;
}
static void rep_DrawText(GraphObj *parent, double x, double y, bool moveable, int align, TextDEF *td, char*text)
{
	char *txt_obj;

	if(txt_obj = mk_label(x, y,	moveable, align, td, text)) {
		OpenGraph(parent, 0L, (unsigned char*)txt_obj, false);
		free(txt_obj);
		}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// draw a rectangle
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static char* mk_rect(double x1, double y1, double x2, double y2, DWORD lcol, DWORD fcol)
{
	int csize, pos = 0;
	char *res;

	if(!(res = (char*)malloc(csize = 1000)))return 0L;
	res[pos++] = '\n';				res[pos++] = '[';
	add_int_to_buff(&res, &pos, &csize, curr_id++, false, 0);
	add_to_buff(&res, &pos, &csize, "=rectangle]\np1=", 0);
	add_dbl_to_buff(&res, &pos, &csize, x1, true);
	add_dbl_to_buff(&res, &pos, &csize, y1, true);
	add_to_buff(&res, &pos, &csize, "\np2=", 0);
	add_dbl_to_buff(&res, &pos, &csize, x2, true);
	add_dbl_to_buff(&res, &pos, &csize, y2, true);
	add_to_buff(&res, &pos, &csize, "\nLine= 0 1", 0);
	add_hex_to_buff(&res, &pos, &csize, lcol, true);
	add_to_buff(&res, &pos, &csize, " 0x0\nFillLine= 0 1 0x0 0x0\nFill= 0", 0);
	add_hex_to_buff(&res, &pos, &csize, fcol, true);
	add_to_buff(&res, &pos, &csize, " 1 0x0 0x00ffffff\n", 0);
	return res;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// print values to string
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static int dbl_to_str1(char *dest, int size, char* fmt, double val)
{
#ifdef USE_WIN_SECURE
	return sprintf_s(dest, size, fmt, val);
#else
	return sprintf(dest, fmt, val);
#endif
}

static int dbl_to_str2(char *dest, int size, char* fmt, double val1, double val2)
{
#ifdef USE_WIN_SECURE
	return sprintf_s(dest, size, fmt, val1, val2);
#else
	return sprintf(dest, fmt, val1, val2);
#endif
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// create general information on report page
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static void mk_header(Page *page, char* desc, DataObj *data)
{
	time_t ti = time(0L);
	char label[80];
	double rpos;
	int cb;

	if(!page) return;
	rpos = page->GetSize(SIZE_GRECT_RIGHT) - txtdef1.fSize*5.0;
	rep_DrawText(page, txtdef1.fSize*5.0, page->GetSize(SIZE_GRECT_TOP)+txtdef2.fSize*6.0,
		false, TXA_HLEFT, &txtdef2, desc); 
#ifdef USE_WIN_SECURE
	ctime_s(label, 32, &ti);
#else
	rlp_strcpy(label, 25, ctime(&ti));
#endif
	label[24] = 0;
	rep_DrawText(page, rpos, page->GetSize(SIZE_GRECT_TOP)+txtdef1.fSize*5.0,
		false, TXA_HRIGHT, &txtdef1, label);
	cb = rlp_strcpy(label, 80, "RLPlot ");		cb += rlp_strcpy(label+cb, 80-cb, SZ_VERSION);
	rep_DrawText(page, rpos, page->GetSize(SIZE_GRECT_BOTTOM)-txtdef1.fSize*6.0,
		false, TXA_HRIGHT, &txtdef1, label);
	if(data && data->Command(CMD_GETFILENAME, TmpTxt, 0L)) {
		rpos = page->GetSize(SIZE_GRECT_LEFT) + txtdef1.fSize*5.0;
		rep_DrawText(page, rpos, page->GetSize(SIZE_GRECT_BOTTOM)-txtdef1.fSize*6.0,
			false, TXA_HLEFT, &txtdef1, TmpTxt);
		}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// create horizontal ruler
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static void mk_hr(GraphObj *parent, double x1, double x2, double y)
{
	int csize, pos = 0;
	char *res;

	if(!(res = (char*)malloc(csize = 100)))return;
	res[pos++] = '\n';				res[pos++] = '[';
	add_int_to_buff(&res, &pos, &csize, curr_id++, false, 0);
	add_to_buff(&res, &pos, &csize, "=polyline]\nData= (2){", 21);
	add_dbl_to_buff(&res, &pos, &csize, x1, false);
	add_dbl_to_buff(&res, &pos, &csize, y, true);
	add_dbl_to_buff(&res, &pos, &csize, x2, true);
	add_dbl_to_buff(&res, &pos, &csize, y, true);
	add_to_buff(&res, &pos, &csize, "}\nLine=", 7);
	add_dbl_to_buff(&res, &pos, &csize, txtdef1.fSize/20.0, true);
	add_dbl_to_buff(&res, &pos, &csize, txtdef1.fSize, true);
	add_to_buff(&res, &pos, &csize, " 0x0 0x0\n", 9);
	OpenGraph(parent, 0L, (unsigned char*)res, false);
	free(res);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// create a means report
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static double mk_mean_report(GraphObj *parent, double x, double y, double *da, int n, double ci, char *name)
{
	static char *mean_fmts[] = {"Mean = %g", "Std.Dev. = %g", "N = %g", "Std.Err. = %g", 0L,
		"Kurtosis = %g", "Skewness = %g"};
	char desc[80];
	int i, cb;
	double v, t, res[10];

	if(name && name[0]) {
		cb = rlp_strcpy(desc, 40, "<b>");			cb += rlp_strcpy(desc+cb, 40-cb, name);
		cb += rlp_strcpy(desc+cb, 40-cb, ":</b>");
		rep_DrawText(parent, x, y, false, TXA_HLEFT, &txtdef1, desc);
		y += linsp1;		x += (txtdef1.fSize*3.0);
		}
	cb = dbl_to_str1(desc, 80, "%g%%%% C.I. = %%g", ci*100.0);
	mean_fmts[4] = (char*)malloc(cb+2);
	rlp_strcpy(mean_fmts[4], cb+1, desc);			t = distinv(t_dist, n-1, 1, 1.0-ci, 2.0);
	v = d_variance(n, da, &res[0], 0L);				res[2] = (double)n;
	res[1] = sqrt(v);								res[3] = res[1] / sqrt(res[2]);
	res[4] = res[3] *t;								res[5] = d_kurt(n, da);
	res[6] = d_skew(n, da);
	for(i = 0; i < 7; i++) {
		dbl_to_str1(desc, 80, mean_fmts[i], res[i]);
		rep_DrawText(parent, x, y, false, TXA_HLEFT, &txtdef1, desc);
		y += (i==2 ? linsp1/0.9 : linsp1/1.2);
		}
	free(mean_fmts[4]);								mean_fmts[4] = 0L;
	return y;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// create a median report
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static double mk_median_report(GraphObj *parent, double x, double y, double *da, int n, double ci, char *name)
{
	static char *mean_fmts[] = {"Median = %g", "25%% = %g", "75%% = %g", "N = %g", "Min. = %g", "Max. = %g" };
	char desc[80];
	int i, cb;
	double res[6];

	if(!da || !parent || !n) return y;
	if(name && name[0]) {
		cb = rlp_strcpy(desc, 40, "<b>");			cb += rlp_strcpy(desc+cb, 40-cb, name);
		cb += rlp_strcpy(desc+cb, 40-cb, ":</b>");
		rep_DrawText(parent, x, y, false, TXA_HLEFT, &txtdef1, desc);
		y += linsp1; x += (txtdef1.fSize*3.0);
		}
	d_quartile(n, da, &res[1], &res[0], &res[2]);
	res[4] = res[5] = *da;
	for(i = 1; i < n; i++) {
		if(da[i] > res[5]) res[5] = da[i];			if(da[i] < res[4]) res[4] = da[i];
		}
	res[3] = (double)n;
	for(i = 0; i < 6; i++) {
		dbl_to_str1(desc, 80, mean_fmts[i], res[i]);
		rep_DrawText(parent, x, y, false, TXA_HLEFT, &txtdef1, desc);
		y += linsp1/1.2;
		}
	return y;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// create report table for anova ...
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static double mk_table(GraphObj *parent, double x, double y, int type, double **dda)
{
	char *cheaders[] = {"<i>df</i>", "<i>SS</i>", "<i>MS</i>", "<i>F</i>", "<i>P</i>"};
	char *rheaders1[] = {"Source of variation", type == 2 ? (char*)"Explained by regression":
		(char*)"Among groups", type == 2 ? (char*)"Unexplained":(char*)"Within groups", "Total"};
	char *rheaders2[] = {"Source of variation", "Between rows", "Between columns", "Interaction",
		"Within subgroups (error)", "Total"};
	char *rheaders3[] = {"Source of variation", "Between columns", "Between rows", "Error", "Total"};
	char *cfmt[8], **rheaders;
	int i, j, nl, nc[8];
	double posc[8], cinc;

#ifdef _WINDOWS
	cinc = txtdef1.fSize;
#else
	cinc = txtdef1.fSize *1.3;
#endif
	cfmt[0] = "%.0lf";		cfmt[3] = "%0.3lf";			cfmt[4] = "%0.4lf";
	switch(type) {
	case 1:	case 2:
		rheaders = rheaders1;
		nl = 3;	nc[0] = 5;	nc[1] = 3;	nc[2] = 2;
		posc[0] = x + cinc*14.0;		posc[1] = posc[0] + cinc*5.0;
		posc[2] = posc[1] + cinc*6.0;	posc[3] = posc[2] + cinc*6.0;
		posc[4] = posc[3] + cinc*6.0;
		cfmt[1] = GetNumFormat(floor(log10(dda[2][1])-3.0));	
		cfmt[2] = GetNumFormat(floor(log10(dda[0][2]+dda[1][2])-3.0));
		break;
	case 3:
		rheaders = rheaders2;
		nl = 5;	nc[0] = nc[1] = nc[2] = 5;	nc[3] = 3;	nc[4] = 2;
		posc[0] = x + cinc*14.0;		posc[1] = posc[0] + cinc*5.0;
		posc[2] = posc[1] + cinc*6.0;	posc[3] = posc[2] + cinc*6.0;
		posc[4] = posc[3] + cinc*6.0;
		cfmt[1] = GetNumFormat(floor(log10(dda[2][1])-3.0));	
		cfmt[2] = GetNumFormat(floor(log10(dda[0][2]+dda[0][1])-3.0));
		break;
	case 4:
		rheaders = rheaders3;
		nl = 4;	nc[0] = nc[1] = 5;	nc[2] = 3;	nc[3] = 2;
		posc[0] = x + cinc*14.0;		posc[1] = posc[0] + cinc*5.0;
		posc[2] = posc[1] + cinc*6.0;	posc[3] = posc[2] + cinc*6.0;
		posc[4] = posc[3] + cinc*6.0;
		cfmt[1] = GetNumFormat(floor(log10(dda[3][1])-4.0));	
		cfmt[2] = GetNumFormat(floor(log10(dda[0][2]+dda[1][2]+dda[2][2])-4.0));
		break;
	default: return y;
		}
	if(type == 1 || type == 2 || type == 3 || type == 4) {
		rep_DrawText(parent, x, y, false, TXA_HLEFT, &txtdef1, rheaders[0]);
		for(i = 0; i < 5; i++) {
			rep_DrawText(parent, posc[i], y, false, TXA_HRIGHT, &txtdef1, cheaders[i]);
			if(i) posc[i] += linsp1;
			}
		mk_hr(parent, x, posc[4], y + linsp1);			y += linsp2;
		}
	for(i = 0; i < nl; i++) {
		rep_DrawText(parent, x, y, false, TXA_HLEFT, &txtdef1, rheaders[i+1]);
		for(j = 0; j < nc[i]; j++) {
			if(j == 4 && dda[i][j] > 0.0 && dda[i][j] < 0.0001) rlp_strcpy(TmpTxt, 10, "< 0.0001");
#ifdef USE_WIN_SECURE
			else sprintf_s(TmpTxt, 20, cfmt[j], dda[i][j]);
#else
			else sprintf(TmpTxt, cfmt[j], dda[i][j]);
#endif
			rep_DrawText(parent, posc[j], y, false, TXA_HRIGHT, &txtdef1, TmpTxt);
			}
		if(i < (nl-2)) y += linsp1;
		else {
			mk_hr(parent, x, posc[4], y + linsp1);		y += linsp2;
			}
		}
	return y;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// create a boxplot for a report
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static char* mk_boxplot(int style, double *x, double *y, double *by1, double *by2, double *wy1, double *wy2, int *ny, int n, 
	char *s_nam, char *b_nam, char *w_nam)
{
	int i, csize, pos, first_s, first_b, first_w, first_l;
	char *res;
	double size;

	if(!(res = (char*)malloc(csize = 2000)))return 0L;
	if(n < 20) size = defs.GetSize(SIZE_SYMBOL);
	else size = defs.GetSize(SIZE_SYMBOL)/2.0 + 20.0 * defs.GetSize(SIZE_SYMBOL)/(2.0 * n);
	first_b = curr_id;
	for(i = pos = 0; i < n && res; i++) {
		add_to_buff(&res, &pos, &csize, "\n[", 2);		add_int_to_buff(&res, &pos, &csize, curr_id++, false, 0);
		add_to_buff(&res, &pos, &csize, "=Box]\nType= 256\nHigh=", 21);
		if(style == 1) {
			add_dbl_to_buff(&res, &pos, &csize, by2[i], true);
			add_dbl_to_buff(&res, &pos, &csize, y[i], true);
			add_to_buff(&res, &pos, &csize,"\nLow=", 5);
			add_dbl_to_buff(&res, &pos, &csize, by1[i], true);
			add_dbl_to_buff(&res, &pos, &csize,y[i], true);
			}
		else {
			add_dbl_to_buff(&res, &pos, &csize, x ? x[i] : (double)(i+1), true);
			add_dbl_to_buff(&res, &pos, &csize, by2[i], true);
			add_to_buff(&res, &pos, &csize,"\nLow=", 5);
			add_dbl_to_buff(&res, &pos, &csize, x ? x[i] : (double)(i+1), true);
			add_dbl_to_buff(&res, &pos, &csize, by1[i], true);
			}
		add_to_buff(&res, &pos, &csize,"\nSize= 60\nName= \"", 17);
		add_to_buff(&res, &pos, &csize, b_nam, 0);
		add_to_buff(&res, &pos, &csize, "\"\n", 2);
		}
	first_w = curr_id;
	for(i = 0; i < n && res; i++) {
		add_to_buff(&res, &pos, &csize, "\n[", 2);		add_int_to_buff(&res, &pos, &csize, curr_id++, false, 0);
		add_to_buff(&res, &pos, &csize, "=Whisker]\nHigh=", 15);
		if(style == 1) {
			add_dbl_to_buff(&res, &pos, &csize, wy2[i], true);
			add_dbl_to_buff(&res, &pos, &csize, y[i], true);
			add_to_buff(&res, &pos, &csize,"\nLow=", 5);
			add_dbl_to_buff(&res, &pos, &csize, wy1[i], true);
			add_dbl_to_buff(&res, &pos, &csize,y[i], true);
			}
		else {
			add_dbl_to_buff(&res, &pos, &csize, x ? x[i] : (double)(i+1), true);
			add_dbl_to_buff(&res, &pos, &csize, wy2[i], true);
			add_to_buff(&res, &pos, &csize,"\nLow=", 5);
			add_dbl_to_buff(&res, &pos, &csize, x ? x[i] : (double)(i+1), true);
			add_dbl_to_buff(&res, &pos, &csize, wy1[i], true);
			}
		add_to_buff(&res, &pos, &csize, "\nDesc= \"", 8);
		add_to_buff(&res, &pos, &csize, w_nam, 0);
		add_to_buff(&res, &pos, &csize, "\"\n", 2);
		}
	first_s = curr_id;
	for(i = 0; i < n && res; i++) {
		add_to_buff(&res, &pos, &csize, "\n[", 2);		add_int_to_buff(&res, &pos, &csize, curr_id++, false, 0);
		add_to_buff(&res, &pos, &csize, "=Symbol]\nType= 10\nPos=", 22);
		add_dbl_to_buff(&res, &pos, &csize, x ? x[i] : (double)(i+1), true);
		add_dbl_to_buff(&res, &pos, &csize, y[i], true);
		add_to_buff(&res, &pos, &csize, "\nSize=", 6);	add_dbl_to_buff(&res, &pos, &csize, size, true);
		add_to_buff(&res, &pos, &csize, "\n", 1);
		add_to_buff(&res, &pos, &csize, SymLineStr, cbSymLineStr);
		add_to_buff(&res, &pos, &csize, "FillCol= 0x00ffffff\n", 20);
		if(s_nam) {
			add_to_buff(&res, &pos, &csize, "Name=\"", 6);
			add_to_buff(&res, &pos, &csize, s_nam, 0);	add_to_buff(&res, &pos, &csize, "\"\n", 2);
			}
		}
	first_l = curr_id;
	for(i = 0; i < n && res; i++) {
		add_to_buff(&res, &pos, &csize, "\n[", 2);
		add_int_to_buff(&res, &pos, &csize, curr_id++, false, 0);
		add_to_buff(&res, &pos, &csize, "=Label]\nPos=", 12);
		if(style == 1) {
			add_dbl_to_buff(&res, &pos, &csize, wy2[i], true);
			add_dbl_to_buff(&res, &pos, &csize, y[i], true);
			add_to_buff(&res, &pos, &csize, "\nDist=", 6);
			add_dbl_to_buff(&res, &pos, &csize, txtdef1.fSize/2.0, true);
			add_to_buff(&res, &pos, &csize, " 0", 2);
			}
		else {
			add_dbl_to_buff(&res, &pos, &csize, x ? x[i] : (double)(i+1), true);
			add_dbl_to_buff(&res, &pos, &csize, wy2[i], true);
			add_to_buff(&res, &pos, &csize, "\nDist= 0", 8);
			add_dbl_to_buff(&res, &pos, &csize, -txtdef1.fSize/4.0, true);
			}
		add_to_buff(&res, &pos, &csize, "\nFlags= 0x00000011\nTxtDef= 0x00000000 0x00ffffff", 48);
		add_dbl_to_buff(&res, &pos, &csize, txtdef1.fSize, true);
		add_dbl_to_buff(&res, &pos, &csize, txtdef1.RotBL, true);
		add_dbl_to_buff(&res, &pos, &csize, txtdef1.RotCHAR, true);
		add_int_to_buff(&res, &pos, &csize, style == 1 ? (TXA_HLEFT | TXA_VCENTER):(TXA_HCENTER | TXA_VBOTTOM), true, 0);
		add_to_buff(&res, &pos, &csize, " 1 0 0 \"", 8);
		if(n < 7) add_to_buff(&res, &pos, &csize, "n = ", 4);
		add_int_to_buff(&res, &pos, &csize, ny[i], false, 0);
		add_to_buff(&res, &pos, &csize, "\"\n", 2);
		}
	add_to_buff(&res,&pos,&csize, "\n[", 2);				add_int_to_buff(&res,&pos,&csize, curr_id++, false, 0);
	add_to_buff(&res, &pos, &csize, "=BoxPlot]\nBounds=", 17);
	add_dbl_to_buff(&res,&pos,&csize, dBounds.Xmin, true);	add_dbl_to_buff(&res,&pos,&csize, dBounds.Ymax, true);
	add_dbl_to_buff(&res,&pos,&csize, dBounds.Xmax, true);	add_dbl_to_buff(&res,&pos,&csize, dBounds.Ymin, true);

	add_to_buff(&res,&pos,&csize, "\nBoxes=(", 0);			add_int_to_buff(&res,&pos,&csize, n, false, 0);
	add_to_buff(&res,&pos,&csize, "){", 2);
	for(i = 0; i < n; i++, first_b++) {
		add_int_to_buff(&res,&pos,&csize, first_b, false, 0);	add_to_buff(&res,&pos,&csize, ",", 1);
		if(i && (i%16)== 0 && first_b < (curr_id-2)) add_to_buff(&res, &pos, &csize, "\n   ", 4);
		}
	while(res[pos-1] == ',' || res[pos-1] < 33) pos --;		add_to_buff(&res, &pos, &csize, "}", 2);
	add_to_buff(&res,&pos,&csize, "\nWhiskers=(", 0);		add_int_to_buff(&res,&pos,&csize, n, false, 0);
	add_to_buff(&res,&pos,&csize, "){", 2);
	for(i = 0; i < n; i++, first_w++) {
		add_int_to_buff(&res,&pos,&csize, first_w, false, 0);	add_to_buff(&res,&pos,&csize, ",", 1);
		if(i && (i%16)== 0 && first_b < (curr_id-2)) add_to_buff(&res, &pos, &csize, "\n   ", 4);
		}
	while(res[pos-1] == ',' || res[pos-1] < 33) pos --;		add_to_buff(&res, &pos, &csize, "}", 2);
	add_to_buff(&res,&pos,&csize, "\nSymbols=(", 10);		add_int_to_buff(&res,&pos,&csize, n, false, 0);
	add_to_buff(&res,&pos,&csize, "){", 2);
	for(i = 0; i < n; i++, first_s++) {
		add_int_to_buff(&res,&pos,&csize, first_s, false, 0);	add_to_buff(&res,&pos,&csize, ",", 1);
		if(i && (i%16)== 0 && first_s < (curr_id-2)) add_to_buff(&res, &pos, &csize, "\n   ", 4);
		}
	while(res[pos-1] == ',' || res[pos-1] < 33) pos --;		add_to_buff(&res, &pos, &csize, "}", 2);
	add_to_buff(&res,&pos,&csize, "\nLabels=(", 9);			add_int_to_buff(&res,&pos,&csize, n, false, 0);
	add_to_buff(&res,&pos,&csize, "){", 2);
	for(i = 0; i < n; i++, first_l++) {
		add_int_to_buff(&res,&pos,&csize, first_l, false, 0);	add_to_buff(&res,&pos,&csize, ",", 1);
		if(i && (i%16)== 0 && first_s < (curr_id-2)) add_to_buff(&res, &pos, &csize, "\n   ", 4);
		}
	while(res[pos-1] == ',' || res[pos-1] < 33) pos --;		add_to_buff(&res, &pos, &csize, "}\n", 2);
	return res;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// create a scatterplot for a report
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static char* mk_scatt(int style, double *x, double *y, double *ss, int *ny, int n, char *s_nam, char *x_desc, char *y_desc)
{
	int i, csize, pos, first;
	char *res;
	double size, linew, tmp, val;

	if(!(res = (char*)malloc(csize = 2000)))return 0L;
	if(n < 20) size = defs.GetSize(SIZE_SYMBOL);
	else size = defs.GetSize(SIZE_SYMBOL)/2.0 + 20.0 * defs.GetSize(SIZE_SYMBOL)/(2.0 * n);
	linew = defs.GetSize(SIZE_SYM_LINE);
	first = curr_id;
	for(i = pos = 0; i < n && res; i++) {
		add_to_buff(&res, &pos, &csize, "\n[", 2);		add_int_to_buff(&res, &pos, &csize, curr_id++, false, 0);
		add_to_buff(&res, &pos, &csize, "=Symbol]\nPos=", 13);
		add_dbl_to_buff(&res, &pos, &csize, x ? x[i] : (double)(i+1), true);
		add_dbl_to_buff(&res, &pos, &csize, y[i], true);
		add_to_buff(&res, &pos, &csize, "\nSize=", 6);	add_dbl_to_buff(&res, &pos, &csize, size, true);
		add_to_buff(&res, &pos, &csize, "\n", 1);
		add_to_buff(&res, &pos, &csize, SymLineStr, cbSymLineStr);
		add_to_buff(&res, &pos, &csize, "FillCol= 0x00ffffff\n", 20);
		}
	if(ss && ny) {
		for(i = 0; i < n && res; i++) {
			if(ny[i] > 1) tmp = sqrt(ss[i]/(ny[i]-1));
			else tmp = 0.0;
			add_to_buff(&res, &pos, &csize, "\n[", 2);
			add_int_to_buff(&res, &pos, &csize, curr_id++, false, 0);
			add_to_buff(&res, &pos, &csize, "=ErrorBar]\nType=", 16);
			add_int_to_buff(&res, &pos, &csize, style & 0x10 ? 3 : 0, true, 0);
			add_to_buff(&res, &pos, &csize, "\nPos=", 5);
			add_dbl_to_buff(&res, &pos, &csize, x ? x[i] : (double)(i+1), true);
			add_dbl_to_buff(&res, &pos, &csize, y[i], true);
			add_to_buff(&res, &pos, &csize, "\nErr=", 5);
			add_dbl_to_buff(&res, &pos, &csize, tmp, true);
			add_to_buff(&res, &pos, &csize, "\nDesc= \"Std. Dev.\"\n", 19);
			}
		for(i = 0; i < n && res; i++) {
			if(ny[i] > 1) tmp = sqrt(ss[i]/(ny[i]-1));
			else tmp = 0.0;
			add_to_buff(&res, &pos, &csize, "\n[", 2);
			add_int_to_buff(&res, &pos, &csize, curr_id++, false, 0);
			add_to_buff(&res, &pos, &csize, "=Label]\nPos=", 12);
			if(style & 0x10) {
				val = x ? x[i] : (double)(i+1);
				if(dBounds.Xmin > (val-tmp)) dBounds.Xmin = val-tmp;
				if(dBounds.Xmax < (val+tmp)) dBounds.Xmax = val+tmp;
				add_dbl_to_buff(&res, &pos, &csize, val+tmp, true);
				add_dbl_to_buff(&res, &pos, &csize, y[i], true);
				add_to_buff(&res, &pos, &csize, "\nDist=", 6);
				add_dbl_to_buff(&res, &pos, &csize, txtdef1.fSize/2.0, true);
				add_to_buff(&res, &pos, &csize, " 0", 2);
				}
			else {
				if(dBounds.Ymin > (y[i]-tmp)) dBounds.Ymin = y[i]-tmp;
				if(dBounds.Ymax < (y[i]+tmp)) dBounds.Ymax = y[i]+tmp;
				add_dbl_to_buff(&res, &pos, &csize, x ? x[i] : ((double)(i+1)), true);
				add_dbl_to_buff(&res, &pos, &csize, y[i] +tmp, true);
				add_to_buff(&res, &pos, &csize, "\nDist= 0", 8);
				add_dbl_to_buff(&res, &pos, &csize, -txtdef1.fSize/4.0, true);
				}
			add_to_buff(&res, &pos, &csize, "\nFlags= 0x00000011\nTxtDef= 0x00000000 0x00ffffff", 48);
			add_dbl_to_buff(&res, &pos, &csize, txtdef1.fSize, true);
			add_dbl_to_buff(&res, &pos, &csize, txtdef1.RotBL, true);
			add_dbl_to_buff(&res, &pos, &csize, txtdef1.RotCHAR, true);
			add_int_to_buff(&res, &pos, &csize, (style & 0x10)?(TXA_HLEFT | TXA_VCENTER) : (TXA_HCENTER | TXA_VBOTTOM), true, 0);
			add_to_buff(&res, &pos, &csize, " 1 0 0 \"", 8);
			if(n < 7) add_to_buff(&res, &pos, &csize, "n = ", 4);
			add_int_to_buff(&res, &pos, &csize, ny[i], false, 0);
			add_to_buff(&res, &pos, &csize, "\"\n", 2);
			}
		}
	add_to_buff(&res,&pos,&csize, "\n[", 2);				add_int_to_buff(&res,&pos,&csize, curr_id++, false, 0);
	add_to_buff(&res, &pos, &csize, "=PlotScatt]\nBounds=", 19);
	add_dbl_to_buff(&res,&pos,&csize, dBounds.Xmin, true);	add_dbl_to_buff(&res,&pos,&csize, dBounds.Ymax, true);
	add_dbl_to_buff(&res,&pos,&csize, dBounds.Xmax, true);	add_dbl_to_buff(&res,&pos,&csize, dBounds.Ymin, true);
	add_to_buff(&res,&pos,&csize, "\nSymbols=(", 10);		add_int_to_buff(&res,&pos,&csize, n, false, 0);
	add_to_buff(&res,&pos,&csize, "){", 2);
	for(i = 0; i < n; i++, first++) {
		add_int_to_buff(&res,&pos,&csize, first, false,0);	add_to_buff(&res,&pos,&csize, ",", 1);
		if(i && (i%16)== 0 && first < (curr_id-2)) add_to_buff(&res, &pos, &csize, "\n   ", 4);
		}
	while(res[pos-1] == ',' || res[pos-1] < 33) pos --;		add_to_buff(&res, &pos, &csize, "}\n", 2);
	if(ss && ny) {
		add_to_buff(&res,&pos,&csize, "ErrBars=(", 9);		add_int_to_buff(&res,&pos,&csize, n, false, 0);
		add_to_buff(&res,&pos,&csize, "){", 2);
		for(i = 0; i < n; i++, first++) {
			add_int_to_buff(&res,&pos,&csize, first,false,0);	add_to_buff(&res,&pos,&csize, ",", 1);
			if(i && (i%16)== 0 && first < (curr_id-2)) add_to_buff(&res, &pos, &csize, "\n   ", 4);
			}
		while(res[pos-1] == ',' || res[pos-1] < 33) pos --;	add_to_buff(&res, &pos, &csize, "}\n", 2);
		add_to_buff(&res,&pos,&csize, "Labels=(", 8);		add_int_to_buff(&res,&pos,&csize, n, false, 0);
		add_to_buff(&res,&pos,&csize, "){", 2);
		for(i = 0; i < n; i++, first++) {
			add_int_to_buff(&res,&pos,&csize, first,false,0);	add_to_buff(&res,&pos,&csize, ",", 1);
			if(i && (i%16)== 0 && first < (curr_id-2)) add_to_buff(&res, &pos, &csize, "\n   ", 4);
			}
		while(res[pos-1] == ',' || res[pos-1] < 33) pos --;	add_to_buff(&res, &pos, &csize, "}\n", 2);
		}
	if(x_desc && x_desc[0]){
		add_to_buff(&res,&pos,&csize, "x_info= \"", 9);		add_to_buff(&res,&pos,&csize, x_desc, 0);
		add_to_buff(&res,&pos,&csize, "\"\n", 2);
		}
	if(y_desc && y_desc[0]){
		add_to_buff(&res,&pos,&csize, "y_info= \"", 9);		add_to_buff(&res,&pos,&csize, y_desc, 0);
		add_to_buff(&res,&pos,&csize, "\"\n", 2);
		}
	if(s_nam && s_nam[0]) {
		add_to_buff(&res, &pos, &csize, "DataDesc=\"", 10);
		add_to_buff(&res, &pos, &csize, s_nam, 0);			add_to_buff(&res, &pos, &csize, "\"\n", 2);
		}
	return res;
}

static double contrasts_level = 95.0;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// create a contrasts report for one way anova
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static void mk_contrasts(GraphObj* par, int type, double dx, double dy, double *y, double *ss, int *ny, int n, 
	char **names, double ci, double msw, double msdf)
{
	double tmp, tkd, pcorr, cx[10], *raw;
	int i, j, k, l, c, df, *co, nco, cb;
	char ctext[5], **contrasts;
	char *headings[] = {"<i>Groups</i>", "<i>Mean</i>", "<i>Std. Dev.</i>", "<i>N</i>",
		"<i>Contrasts</i><sup>1)</sup>"};

	if(!par || !y || !ss || !ny || n < 2) return;
	cx[0] = txtdef1.fSize*5.0;		cx[1] = cx[0] + linsp1*5.0;
	cx[2] = cx[1] + linsp1*5.0;		cx[3] = cx[2] + linsp1*5.0;
	cx[4] = cx[3] + linsp1*4.0;		cx[5] = cx[4] + linsp1*3.0;
	cx[6] = cx[5] + linsp1*4.0;

	rep_DrawText(par, dx, dy, false, TXA_HLEFT, &txtdef1, "<b>Summary:</b>");
	for(i = 0, dy += linsp2; i < 5; i++) {				//column headers
		c = (i == 4) ? TXA_HLEFT : TXA_HRIGHT;
		rep_DrawText(par, cx[i+1], dy, false, c, &txtdef1, headings[i]);
		}
	mk_hr(par, cx[0], cx[6]+txtdef1.fSize*2.0, dy +linsp1);
	if(type == 1 || type == 2) {	
		if(!(co = (int*)malloc(n*sizeof(int)))) return;
		if(!(contrasts = (char**)malloc(n*sizeof(char*)))) return;
		rlp_strcpy(ctext, 5, ", a");
		for(i = df = 0, nco = n; i < n; i++) {
			if(ny[i] > 0) df += (ny[i]-1);
			co[i] = i;
			contrasts[i] = (char*)calloc(50, sizeof(char));
			}
		tkd = qtukey(1.0-ci, 1.0, (double) n, (double)df, 1, 0);
		for(i = 0; nco; ) {
			for(j = 0; j < n; j++) {
				switch(type) {
					case 1:					//Tukey-Kramer
						tmp = tkd * sqrt((msw*(1.0/((double)ny[j]) + 1.0/((double)ny[co[i]])))/2.0);
						break;
					case 2:					//Tukey's HSD
						tmp = tkd * sqrt(msw/(ny[j] <= ny[co[i]] ? ny[j] : ny[co[i]]));
						break;
					}
				if(fabs(y[j]-y[co[i]]) < tmp) {
					cb = (int)strlen(contrasts[j]);
					rlp_strcpy((contrasts[j])+cb, 50-cb, ctext);
					}
				}
			for(j = nco = 0; j < n; j++) {
				if(!(contrasts[j][0])) co[nco++] = j;
				}
			ctext[2]++;
			}
		}
	else if(type == 10) {
		if(!(co = (int*)malloc(n*sizeof(int)))) return;
		if(!(contrasts = (char**)malloc(n*sizeof(char*)))) return;
		if(!(raw = (double*)malloc((n*n-1)*sizeof(double))))return;
		rlp_strcpy(ctext, 5, ", a");
		for(i = df = 0, nco = n; i < n; i++) {
			if(ny[i] > 0) df += (ny[i]-1);
			co[i] = i;
			contrasts[i] = (char*)calloc(50, sizeof(char));
			}
		for(i = k = 0; i < (n-1); i++) for(j = i+1; j < n; j++) {
			raw[k++] = t_dist(fabs(0.5*(y[i]-y[j])/sqrt(msw/(ny[i]+ny[j]))), msdf, 0.0);
			}
		SortArray(k, raw);
		for(i = 0; nco; ) {
			for(j = 0; j < n; j++) {
				tmp = t_dist(fabs(0.5*(y[j]-y[co[i]])/sqrt(msw/(ny[j]+ny[co[i]]))), msdf, 0.0);
				for(l = 0; l < k && tmp > raw[l]; l++);
				switch(type) {
				case 10:					//Dunn Sidak
					pcorr = 1.0 - pow((1.0 - ci), 1.0 /(double(k-l)));
					break;
					}
				if(tmp > pcorr || j == co[i]) {
					cb = (int)strlen(contrasts[j]);
					rlp_strcpy((contrasts[j])+cb, 50-cb, ctext);
					}
				}
			for(j = nco = 0; j < n; j++) {
				if(!(contrasts[j][0])) co[nco++] = j;
				}
			ctext[2]++;
			}
		free(raw);
		}
	else return;

	for(i = 0, dy += linsp2; i < n; i++, dy +=linsp1) {
		if(ny[i] > 1) tmp = sqrt(ss[i]/(ny[i]-1));
		else tmp = 0.0;
		rep_DrawText(par, cx[1], dy, false, TXA_HRIGHT, &txtdef1, names[i]);
		dbl_to_str1(TmpTxt, 20, "%g", y[i]);
		rep_DrawText(par, cx[2], dy, false, TXA_HRIGHT, &txtdef1, TmpTxt);
		if(tmp > 0.0) {
			dbl_to_str1(TmpTxt, 20, "%g", tmp);
			rep_DrawText(par, cx[3], dy, false, TXA_HRIGHT, &txtdef1, TmpTxt);
			}
		if(ny[i] >1) {
			dbl_to_str1(TmpTxt, 20, "%.0lf", (double)ny[i]);
			rep_DrawText(par, cx[4], dy, false, TXA_HRIGHT, &txtdef1, TmpTxt);
			}
		rep_DrawText(par, cx[5], dy, false, TXA_HLEFT, &txtdef1, contrasts[i]+2);
		}
	mk_hr(par, cx[0], cx[6]+txtdef1.fSize*2.0, dy+txtdef1.fSize*0.2);
	cb = dbl_to_str1(TmpTxt, 200, "<sup>1)</sup> Groups not sharing the same letter are different "
		"on the %g%% level ", (1.0-ci)*100.0);
	switch (type) {
	case 1:						//Tukey-Kramer
		rlp_strcpy(TmpTxt+cb, TMP_TXT_SIZE - cb, "(Tukey-Kramer method)");
		break;
	case 2:						//Tukey's HSD
		rep_DrawText(par, cx[0]+ txtdef1.fSize*2.0, dy + txtdef2.fSize + linsp1, false, 
			TXA_HLEFT, &txtdef1, "(Tukey's honest significant difference)");
		break;
	case 10:					//Dunn-Sidak
		rep_DrawText(par, cx[0]+ txtdef1.fSize*2.0, dy + txtdef2.fSize + linsp1, false, 
			TXA_HLEFT, &txtdef1, "(sequential Dunn-Sidak method)");
		break;
		}
	rep_DrawText(par, cx[0], dy += txtdef2.fSize , false, TXA_HLEFT, &txtdef1, TmpTxt);
	for(i = 0; i < n; i++) free(contrasts[i]);
	free(co);		free(contrasts);
	return;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// create a homogeneity of variances report for one way anova
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static void mk_v_homogeneity(GraphObj* par, DataObj *data, double *dx, double *dy, double *y, double *ss,
	int *ny, int n, double **vals)
{
	int i;
	double tmp, *sd, f1, f2, p1, p2;
	char *txt_obj;
	scaleINFO scale = {{0.0, 0.8}, {0.0, 0.8}, {0.0, 0.8}};
	Graph *graph;

	if(!par || !y || !ss || !ny || n < 2) return;
	if(!(sd = (double*)malloc(n*sizeof(double)))) return;
	rep_DrawText(par, *dx, *dy, false, TXA_HLEFT, &txtdef1, "<b>Homogeneity of Variances:</b>");
	for(i = 0; i < n; i++) {
		if(ny[i] > 1) sd[i] = sqrt(ss[i]/(ny[i]-1));
		else sd[i] = 0.0;
		if(i) {
			if(dBounds.Xmax < y[i]) dBounds.Xmax = y[i];
			if(dBounds.Xmin > y[i]) dBounds.Xmin = y[i];
			if(dBounds.Ymax < sd[i]) dBounds.Ymax = sd[i];
			if(dBounds.Ymin > sd[i]) dBounds.Ymin = sd[i];
			}
		else {
			dBounds.Xmax = dBounds.Xmin = y[0];
			dBounds.Ymax = dBounds.Ymin = sd[0];
			}
		}
	if((graph = new Graph(par, data, 0L, 0)) && (txt_obj = mk_scatt(0, y, sd, 0L, ny, n, 
		"Variables", "Means", "Std.Dev."))){
		graph->GRect.Xmax = defs.GetSize(SIZE_GRECT_BOTTOM)*0.8;
		graph->GRect.Ymax *= 0.8;
		graph->DRect.Xmin *= 0.8;				graph->DRect.Ymax *= 0.8;				
		graph->DRect.Xmax = graph->GRect.Xmax - (txtdef1.fSize*2.0);
		scale.sx.fx = par->GetSize(SIZE_GRECT_RIGHT) - txtdef1.fSize*5.0 - graph->GRect.Xmax*0.8 + graph->GRect.Xmin*0.8;		
		scale.sy.fx = *dy;
		OpenGraph(graph, 0L, (unsigned char*)txt_obj, false);
		free(txt_obj);
		graph->Command(CMD_SCALE, &scale, 0L);
		if(!(par->Command(CMD_DROP_GRAPH, graph, 0L))) delete graph;
		else graph->moveable = 0;
		}
	if(bartlett(n, ny, ss, &tmp)) {
		rep_DrawText(par, *dx + txtdef1.fSize*2.0, *dy += (linsp2*1.5), false, TXA_HLEFT, &txtdef1, "Bartlett's test:");
		i = dbl_to_str1(TmpTxt, TMP_TXT_SIZE, "Chi<sup>2</sup> = %.2lf, ", tmp);
		tmp = chi_dist(tmp, n-1, 0);
		dbl_to_str1(TmpTxt+i, TMP_TXT_SIZE-i, tmp < 0.0001 ? (char*)"P < 0.0001" : (char*)"P = %.4lf", tmp);
		rep_DrawText(par, *dx + txtdef1.fSize*3.0, *dy += linsp1, false, TXA_HLEFT, &txtdef1, TmpTxt);
		}
	if(levene(1, n, ny, y, vals, &f1, &p1) && levene(2, n, ny, y, vals, &f2, &p2) ) {
		rep_DrawText(par, *dx + txtdef1.fSize*2.0, *dy += (linsp2*1.5), false, TXA_HLEFT, &txtdef1, "Levene's test:");
		i = dbl_to_str1(TmpTxt, TMP_TXT_SIZE, "using means: F = %.2lf, ", f1);
		dbl_to_str1(TmpTxt+i, TMP_TXT_SIZE-i, tmp < 0.0001 ? (char*)"P < 0.0001" : (char*)"P = %.4lf", p1);
		rep_DrawText(par, *dx + txtdef1.fSize*3.0, *dy += linsp1, false, TXA_HLEFT, &txtdef1, TmpTxt);
		i = dbl_to_str1(TmpTxt, TMP_TXT_SIZE, "using medians: F = %.2lf, ", f2);
		dbl_to_str1(TmpTxt+i, TMP_TXT_SIZE-i, tmp < 0.0001 ? (char*)"P < 0.0001" : (char*)"P = %.4lf", p2);
		rep_DrawText(par, *dx + txtdef1.fSize*3.0, *dy += linsp1, false, TXA_HLEFT, &txtdef1, TmpTxt);
		}
	free(sd);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// one way anova
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static char *AnovaDlg_Tmpl =
	"1,+,,DEFAULT,PUSHBUTTON,-1,158,10,45,12\n"
	".,.,,,PUSHBUTTON,-2,158,25,45,12\n"
	".,,10,ISPARENT | CHECKED,GROUP,0,0,0,0,0\n"
	"10,+,152,ISPARENT | CHECKED,SHEET,1,5,10,140,90\n"
	".,20,100,ISPARENT,SHEET,2,5,10,140,90\n"
	"20,,,CHECKED,CHECKPIN,0,5,0,12,8\n"
	"100,104,,ISRADIO,CHECKBOX,8,20,25,100,9\n"
	"104,+,,,LTEXT,4,15,37,100,9\n"
	".,.,,ISRADIO,CHECKBOX,5,20,47,100,9\n"
	".,.,,ISRADIO,CHECKBOX,9,20,57,100,9\n"
	".,110,,ISRADIO,CHECKBOX,10,20,67,100,9\n"
	"110,+,,,LTEXT,7,20,85,55,9\n"
	".,.,,,EDVAL1,6,80,85,25,10\n"
	".,,,,LTEXT,-10,107,85,10,9\n"
	"152,+,,ISPARENT | CHECKED,GROUPBOX,3,12,30,128,65\n"
	".,.,,,LTEXT,0,25,45,60,8\n"
	".,.,,,RANGEINPUT,0,25,55,100,10\n"
	".,.,0,,PUSHBUTTON,-8,95,70,30,12\n"
	".,,,LASTOBJ,PUSHBUTTON,-9,60,70,35,12";

void rep_anova(GraphObj *parent, DataObj *data)
{
	TabSHEET tab1 = {0, 45, 10, "Anova Input"};
	TabSHEET tab2 = {45, 75, 10, "Tests"};
	DlgInfo *AnovaDlg;
	void *dyndata[] = {(void*)&tab1, (void*)&tab2, (void*)" select one range for every variable ",
		(void*)"Contrasts:", (void*)" Tukey-Kramer method", (void*)&contrasts_level, (void*)"significance level:",
		(void*)" Homogeneity of Variances", (void*)" Tukey's honest sig. difference", (void*)" Dunn-Sidak"};
	DlgRoot *Dlg;
	void *hDlg;
	double **cols = 0L, *csums=0L, mtot, *css=0L, cx, cy;
	double **res_tab = 0L, ci;
	int i, j, n, c, r, res, nc, ntot, currYR = 0, maxYR=0, ny, *ncols = 0L;;
	bool bContinue = false, updateYR = true;
	anyResult ares;
	AccRange *rD =0L;
	char **rd = 0L, **names, *txt_obj;
	Graph *graph;
	Page *page;

	if(!parent || !data) return;
	if(!UseRangeMark(data, 2, TmpTxt, TmpTxt+100, TmpTxt+200, TmpTxt+300, TmpTxt+400,
		TmpTxt+500, TmpTxt+600, TmpTxt+700, TmpTxt+800, TmpTxt+900, TmpTxt+1000)) return;
	if(!(AnovaDlg = CompileDialog(AnovaDlg_Tmpl, dyndata))) return;
	if(TmpTxt[0] && TmpTxt[100] && (rd = (char**)calloc(12, sizeof(char*)))) {
		for(i=j=0; i <= 1000; i +=100) if(TmpTxt[i]) 
			rd[j++] = (char*)memdup(TmpTxt+i, ((int)strlen(TmpTxt+i))+2, 0);	 maxYR = j-1;
		}
	if(!rd && !(rd = (char**)calloc(1, sizeof(char*))))return;
	if(!(Dlg = new DlgRoot(AnovaDlg, data)))return;
	if(rd && rd[currYR] &&  *(rd[currYR])) Dlg->SetText(154, rd[currYR]);
	hDlg = CreateDlgWnd("Single-Classification Anova", 50, 50, 420, 240, Dlg, 0x4L);
	do {
		if(updateYR) {
			if(currYR >0) Dlg->ShowItem(156, true);
			else Dlg->ShowItem(156, false);
#ifdef USE_WIN_SECURE
			sprintf_s(TmpTxt, TMP_TXT_SIZE, "variable # %d/%d", currYR+1, maxYR+1);
#else
			sprintf(TmpTxt,"variable # %d/%d", currYR+1, maxYR+1);
#endif
			Dlg->SetText(153, TmpTxt);
			updateYR = false;
			}
		LoopDlgWnd();
		res = Dlg->GetResult();
		switch (res) {
		case 0:
			if(bContinue) res = -1;
			else if(Dlg->GetCheck(10)) res = -1;
			break;
		case -1:
			bContinue = false;
			break;
		case 155:		case 156:
			res = com_StackDlg(res, Dlg, 0L, 0L, &rd, &currYR,
				&rD, &bContinue, &ny, &maxYR, &updateYR);
			break;
			}
		}while (res < 0);
	if(res == 1 && (res_tab = (double**)calloc(3, sizeof(double*)))
		&& (res_tab[0] = (double*) malloc(5*sizeof(double)))
		&& (res_tab[1] = (double*) malloc(5*sizeof(double)))
		&& (res_tab[2] = (double*) malloc(5*sizeof(double)))
		&& (cols = (double**)calloc(maxYR+1, sizeof(double*)))
		&& (names = (char**)calloc(maxYR+1, sizeof(char*)))
		&& (ncols = (int*)calloc(maxYR+1, sizeof(int)))) {
		rep_init();		if(rD) delete rD;		rD = 0L;
		if(Dlg->GetValue(111, &ci)) {
			contrasts_level = ci;		ci = 1.0-(ci/100.0);
			}
		dBounds.Ymin = HUGE_VAL;		dBounds.Ymax = -HUGE_VAL;
		// get data into two dimensional array
		for(nc = maxYR+1, i = ntot = 0, mtot = 0.0; i < nc; i++) {
			if((rD = new AccRange(rd[i])) && (n = rD->CountItems()) && (cols[i] = (double*)malloc(n*sizeof(double)))) {
				names[i] = rD->RangeDesc(data, 1);
				for(n = 0, rD->GetFirst(&c, &r); rD->GetNext(&c, &r); ) {
					if(data->GetResult(&ares, r, c, false) && ares.type == ET_VALUE) {
						if(ares.value < dBounds.Ymin) dBounds.Ymin = ares.value;
						if(ares.value > dBounds.Ymax) dBounds.Ymax = ares.value;
						cols[i][n++] = ares.value;
						}
					}
				ncols[i] = n;			ntot += n;
				delete(rD);			rD = 0L;
				}
			if(!names[i] && (names[i] = (char*)malloc(20*sizeof(char)))){
#ifdef USE_WIN_SECURE
				sprintf_s(names[i], 20, "Group %d", i+1);
#else
				sprintf(names[i], "Group %d", i+1);
#endif
				}
			}
		// check for unique names
		for(i = 0; i < (nc-1); i++) for(j = i+1; j < nc; j++) {
			if(!strcmp(names[i], names[j])) {
				names[i] = (char*) realloc(names[i], 20 *sizeof(char));
				names[j] = (char*) realloc(names[j], 20 *sizeof(char));
#ifdef USE_WIN_SECURE
				sprintf_s(names[i], 20, "Group %d", i+1);	sprintf_s(names[j], 20, "Group %d", j+1);
#else
				sprintf(names[i], "Group %d", i+1);			sprintf(names[j], "Group %d", j+1);
#endif
				}
			}

		if(do_anova1(nc, ncols, cols, res_tab, &mtot, &csums, &css)){
			dBounds.Xmin = 0.5;				dBounds.Xmax = ((double)nc)+0.5;
			page = new Page(parent, data);
			mk_header(page, "<b>Single-Classification ANOVA</b>", data);
			if((graph = new Graph(parent, data, 0L, 0)) && (txt_obj = mk_scatt(0, 0L, csums, css, ncols, nc, "Mean", "Groups", "Means <u>+</u> S.D."))){
				OpenGraph(graph, 0L, (unsigned char*)txt_obj, false);
				if(LastOpenGO && LastOpenGO->Id == GO_PLOTSCATT) {
					if(((PlotScatt*)LastOpenGO)->x_tv = new TextValue()){
						for(i = 0; i < nc; i++) ((PlotScatt*)LastOpenGO)->x_tv->GetValue(names[i]);
						}
					}
				free(txt_obj);								graph->moveable = 0;
				graph->GRect.Xmin += (txtdef1.fSize*5.0);	graph->GRect.Xmax += (txtdef1.fSize*5.0);
				graph->GRect.Ymin += (txtdef1.fSize*10.0);	graph->GRect.Ymax += (txtdef1.fSize*10.0);
				page->Command(CMD_DROP_GRAPH, graph, 0L);
				}
			cx = graph->GRect.Xmin;		cy = graph->GetSize(SIZE_GRECT_BOTTOM)+txtdef2.fSize*2.0;
			rep_DrawText(page, cx, cy, false, TXA_HLEFT, &txtdef1, "<b>Anova:</b>");
			cy = mk_table(page, cx, cy+txtdef2.fSize, 1, res_tab)+txtdef2.fSize;
			if(Dlg->GetCheck(100)) mk_v_homogeneity(page, data, &cx, &cy, csums, css, ncols, nc, cols);
			else if(Dlg->GetCheck(105)) mk_contrasts(page, 1, cx, cy, csums, css, ncols, nc, names, ci, res_tab[1][2], res_tab[1][0]);
			else if(Dlg->GetCheck(106)) mk_contrasts(page, 2, cx, cy, csums, css, ncols, nc, names, ci, res_tab[1][2], res_tab[1][0]);
			else if(Dlg->GetCheck(107)) mk_contrasts(page, 10, cx, cy, csums, css, ncols, nc, names, ci, res_tab[1][2], res_tab[1][0]);
			if(ntot > (nc<<1) && nc >1 && parent->Command(CMD_DROP_GRAPH, page, 0L));
			else {
				delete page;
				InfoBox("No or insufficient\ndata for ANOVA\n");
				}
			}
		for(i = 0; i < nc; i++){
			if(cols[i]) free(cols[i]);		if(names[i]) free(names[i]);
			}
		for(i = 0; i < 3; i++) if(res_tab[i]) free(res_tab[i]);
		free(cols);			free(ncols);			free(names);
		free(res_tab);			if(css)free(css);		if(csums)free(csums);
		}
	if(rD) delete rD;		CloseDlgWnd(hDlg);
	delete Dlg;				free(AnovaDlg);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Breakdown One Way Anova
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static char *BdAnovDlg_Tmpl =
	"1,+,,DEFAULT,PUSHBUTTON,-1,158,10,45,12\n"
	".,.,,,PUSHBUTTON,-2,158,25,45,12\n"
	".,,10,ISPARENT | CHECKED,GROUP,0,0,0,0,0\n"
	"10,+,150,ISPARENT | CHECKED,SHEET,1,5,10,140,90\n"
	".,20,100,ISPARENT,SHEET,2,5,10,140,90\n"
	"20,,,CHECKED,CHECKPIN,0,5,0,12,8\n"
	"100,104,,ISRADIO,CHECKBOX,8,20,25,100,9\n"
	"104,+,,,LTEXT,4,15,37,100,9\n"
	".,.,,ISRADIO,CHECKBOX,5,20,47,100,9\n"
	".,.,,ISRADIO,CHECKBOX,9,20,57,100,9\n"
	".,110,,ISRADIO,CHECKBOX,10,20,67,100,9\n"
	"110,+,,,LTEXT,7,20,85,55,9\n"
	".,.,,,EDVAL1,6,80,85,25,10\n"
	".,,,,LTEXT,-10,107,85,10,9\n"
	"150,+,,,LTEXT,3,20,32,100,9\n"
	".,.,,,RANGEINPUT,-16,20,44,110,10\n"
	".,.,,,LTEXT,11,20,60,100,9\n"
	".,,,LASTOBJ,RANGEINPUT,-17,20,72,110,10";

void rep_bdanova(GraphObj *parent, DataObj *data)
{
	TabSHEET tab1 = {0, 45, 10, "Anova Input"};
	TabSHEET tab2 = {45, 75, 10, "Tests"};
	DlgInfo *AnovaDlg;
	void *dyndata[] = {(void*)&tab1, (void*)&tab2, (void*)"range for grouping variable",
		(void*)"Contrasts:", (void*)" Tukey-Kramer method", (void*)&contrasts_level, (void*)"significance level:",
		(void*)" Homogeneity of Variances", (void*)" Tukey's honest sig. difference", (void*)" Dunn-Sidak",
		(void*)"range for values"};
	DlgRoot *Dlg;
	void *hDlg;
	int i, l, nc, nv, res, gr, gc, dr, dc;
	int *ncols = 0L;
	double cv, mv, ci, **cols = 0L, *csums=0L, mtot, *css=0L;
	double **res_tab = 0L, cx, cy;
	anyResult gres, dres;
	AccRange *rG = 0L, *rD = 0L;
	TextValue *tv = 0L;
	char *txt_obj, **names;
	bool bContinue = false;
	Graph *graph;
	Page *page;

	if(!UseRangeMark(data, 2, TmpTxt+100, TmpTxt+200, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L)) return;
	if(!(AnovaDlg = CompileDialog(BdAnovDlg_Tmpl, dyndata))) return;
	if(!(Dlg = new DlgRoot(AnovaDlg, data)))return;
	hDlg = CreateDlgWnd("Breakdown One Way Anova", 50, 50, 420, 250, Dlg, 0x4L);
	do {
		LoopDlgWnd();
		res = Dlg->GetResult();
		switch (res) {
		case 0:
			if(bContinue) res = -1;
			else if(Dlg->GetCheck(10)) res = -1;
			break;
		case -1:
			bContinue = false;
			break;
		case 1:
			if(!Dlg->GetText(151, TmpTxt+100, 100) || !Dlg->GetText(153, TmpTxt+200, 100)) {
				ErrorBox("Invalid Ranges!\nBoth ranges must be defined\nand must be of equal size.\n");
				res = -1;	bContinue = true;
				}
			else if(!(rG = new AccRange(TmpTxt+100)) || !(rD = new AccRange(TmpTxt+200)) 
				|| (l = rG->CountItems()) < 3 || (l = rD->CountItems()) < 3) {
				ErrorBox("Insufficient Data!\nCheck data ranges.\n");
				res = -1;	bContinue = true;
				}
			else for(l = nv = 0, mv = 0.0; l < 2 && rG->GetFirst(&gc, &gr) && rD->GetFirst(&dc, &dr); l++) {
				if(l) {
					dBounds.Ymin = HUGE_VAL;		dBounds.Ymax = -HUGE_VAL;
					nc = (int)(mv);
					cols = (double**)calloc(nc, sizeof(double*));
					ncols = (int*)calloc(nc, sizeof(int));
					if(cols && ncols) for(i = 0; i < nc; i++) cols[i] = (double*) malloc(nv * sizeof(double));
					while(rG->GetNext(&gc, &gr) && rD->GetNext(&dc, &dr)) {
						if(data->GetResult(&gres, gr, gc, false) && data->GetResult(&dres, dr, dc, false)
							&& dres.type == ET_VALUE) {
							switch (gres.type) {
							case ET_TEXT:
								cv = tv->GetValue(gres.text);		break;
							default:
								TranslateResult(&gres);
								cv = tv->GetValue(gres.text);		break;
								}
							i = (int)(cv);
							if(dres.value < dBounds.Ymin) dBounds.Ymin = dres.value;
							if(dres.value > dBounds.Ymax) dBounds.Ymax = dres.value;
							if(cols && ncols && cols[i-1]) cols[i-1][ncols[i-1]++] = dres.value;
							}
						}
					}
				else if(tv = new TextValue()) {
					while(rG->GetNext(&gc, &gr) && rD->GetNext(&dc, &dr)) {
						if(data->GetResult(&gres, gr, gc, false) && data->GetResult(&dres, dr, dc, false)
							&& dres.type == ET_VALUE) {
							switch (gres.type) {
							case ET_TEXT:
								cv = tv->GetValue(gres.text);		break;
							default:
								TranslateResult(&gres);
								cv = tv->GetValue(gres.text);		break;
								}
							if(mv < cv) mv = cv;	nv++;
							}
						}
					}
				}
			}
		}while (res < 0);
	if(res == 1 && tv && cols && ncols && (res_tab = (double**)calloc(3, sizeof(double*)))
		&& (res_tab[0] = (double*) malloc(5*sizeof(double)))
		&& (res_tab[1] = (double*) malloc(5*sizeof(double)))
		&& (res_tab[2] = (double*) malloc(5*sizeof(double)))
		&& (names = (char**)malloc(nc*sizeof(char*)))) {
		if(Dlg->GetValue(111, &ci)) {
			contrasts_level = ci;		ci = 1.0-(ci/100.0);
			}
		else ci = 0.05;					rep_init();
		for(i = 0; i < nc; i++) tv->GetItem(i, &names[i], &cv);
		if(rD) delete rD;				rD = 0L;
		if(do_anova1(nc, ncols, cols, res_tab, &mtot, &csums, &css)){
			dBounds.Xmin = 0.5;				dBounds.Xmax = ((double)nc)+0.5;
			page = new Page(parent, data);
			mk_header(page, "<b>Breakdown and Single-Classification ANOVA</b>", data);
			if((graph = new Graph(parent, data, 0L, 0)) && (txt_obj = mk_scatt(0, 0L, csums, css, ncols, nc, "Mean", "Groups", "Means <u>+</u> S.D."))){
				OpenGraph(graph, 0L, (unsigned char*)txt_obj, false);
				if(LastOpenGO && LastOpenGO->Id == GO_PLOTSCATT) {
					((PlotScatt*)LastOpenGO)->x_tv = tv;
					}
				else delete tv;
				free(txt_obj);								graph->moveable = 0;
				graph->GRect.Xmin += (txtdef1.fSize*5.0);	graph->GRect.Xmax += (txtdef1.fSize*5.0);
				graph->GRect.Ymin += (txtdef1.fSize*10.0);	graph->GRect.Ymax += (txtdef1.fSize*10.0);
				page->Command(CMD_DROP_GRAPH, graph, 0L);
				}
			cx = graph->GRect.Xmin;		cy = graph->GetSize(SIZE_GRECT_BOTTOM)+txtdef2.fSize*2.0;
			rep_DrawText(page, cx, cy, false, TXA_HLEFT, &txtdef1, "<b>Anova:</b>");
			cy = mk_table(page, cx, cy+txtdef2.fSize, 1, res_tab)+txtdef2.fSize;
			if(Dlg->GetCheck(100)) mk_v_homogeneity(page, data, &cx, &cy, csums, css, ncols, nc, cols);
			else if(Dlg->GetCheck(105)) mk_contrasts(page, 1, cx, cy, csums, css, ncols, nc, names, ci, res_tab[1][2], res_tab[1][0]);
			else if(Dlg->GetCheck(106)) mk_contrasts(page, 2, cx, cy, csums, css, ncols, nc, names, ci, res_tab[1][2], res_tab[1][0]);
			else if(Dlg->GetCheck(107)) mk_contrasts(page, 10, cx, cy, csums, css, ncols, nc, names, ci, res_tab[1][2], res_tab[1][0]);
			if(nv > (nc<<1) && nc >1 && parent->Command(CMD_DROP_GRAPH, page, 0L));
			else {
				delete page;
				InfoBox("No or insufficient\ndata for ANOVA\n");
				}
			}
		for(i = 0; i < nc; i++)	if(cols[i]) free(cols[i]);
		for(i = 0; i < 3; i++) if(res_tab[i]) free(res_tab[i]);
		free(cols);		free(ncols);			free(names);
		free(res_tab);		if(css)free(css);		if(csums)free(csums);
		}

	if(rD) delete rD;		if(rG) delete rG;
	CloseDlgWnd(hDlg);
	delete Dlg;			free(AnovaDlg);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Parametric two way anova
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static char *TwAnov_DlgTmpl = 
	"1,2,,DEFAULT,PUSHBUTTON,-1,148,10,45,12\n"
	"2,3,,,PUSHBUTTON,-2,148,25,45,12\n"
	"3,,4,ISPARENT | CHECKED,GROUP,0,0,0,0,0\n"
	"4,10,100,ISPARENT | CHECKED,SHEET,1,5,10,130,80\n"
	"10,,,CHECKED,CHECKPIN,0,5,0,12,8\n"
	"100,+,,,LTEXT,2,10,30,60,8\n"
	".,.,,,RANGEINPUT,-15,20,40,100,10\n"
	".,,,LASTOBJ,CHECKBOX,3,20,60,100,9";

void rep_twanova(GraphObj *parent, DataObj *data)
{
	TabSHEET tab1 = {0, 40, 10, "Input Data"};
	DlgInfo *TwAnovDlg;
	void *dyndata[] = {(void*)&tab1, (void*)"rectangular range for variables",
		(void*)" column/row headers present"};
	DlgRoot *Dlg;
	void *hDlg;
	int i, hc, hr, mr, mc, nr, nc, c, r, res, *nvr=0L, *nvc=0L;
	bool bContinue = false;
	char *mrk, *txt_obj, **cnames = 0L, **rnames = 0L;
	double gm, ssc, ssr, sse, tmp, cx, cy, dmin, dmax;
	double **vals = 0L, *cs = 0L, *rs = 0L, **res_tab = 0L, *c_ss, *r_ss, *c_m, *r_m, *abc;
	RECT rec;
	scaleINFO scale = {{0.0, 0.7}, {0.0, 0.7}, {0.0, 0.7}};
	AccRange *rD =0L, *rDesc;
	anyResult ares;
	Graph *graph;
	Page *page;

	if(!parent || !data) return;
	if(!(TwAnovDlg = CompileDialog(TwAnov_DlgTmpl, dyndata))) return;
	if(data->Command(CMD_GETMARK, &mrk, 0L))rlp_strcpy(TmpTxt, TMP_TXT_SIZE, mrk);
	else {
		data->ValueRec(&rec);
		rlp_strcpy(TmpTxt, 100, mkRangeRef(rec.top, rec.left, rec.bottom, rec.right));
		}
	if(!(Dlg = new DlgRoot(TwAnovDlg, data)))return;
	hDlg = CreateDlgWnd("Two-Way Anova", 50, 50, 420, 220, Dlg, 0x4L);
	do {
		LoopDlgWnd();
		res = Dlg->GetResult();
		switch (res) {
		case 0:
			if(bContinue) res = -1;
			else if(Dlg->GetCheck(10)) res = -1;
			break;
		case -1:
			bContinue = false;
			break;
		case 1:
			if(Dlg->GetText(101, TmpTxt+200, TMP_TXT_SIZE-200) &&(rD = new AccRange(TmpTxt+200))&& rD->BoundRec(&rec)
				&& (vals = (double**)calloc(rec.bottom - rec.top +1, sizeof(double*)))) {
				nc = rec.right-rec.left+1;				nr = rec.bottom-rec.top+1;
				nvc = (int*)calloc(nc, sizeof(int));	nvr = (int*)calloc(nr, sizeof(int));
				dmin = HUGE_VAL;						dmax = -HUGE_VAL;
				if(Dlg->GetCheck(102)) hr = hc = 1;
				else hr = hc = 0;
				for(i = rec.top+hr; i <= rec.bottom; i++) vals[i-rec.top] = (double*)calloc(nc, sizeof(double));
				for(c = rec.left+hc; c <= rec.right; c++) for(r = rec.top; r <= rec.bottom; r++) {
					if(data->GetResult(&ares, r, c,false) && ares.type == ET_VALUE){
						nvc[c-rec.left]++;				nvr[r-rec.top]++;
						if(vals[r-rec.top]) vals[r-rec.top][c-rec.left] = ares.value;
						if(ares.value > dmax) dmax = ares.value;
						if(ares.value < dmin) dmin = ares.value;
						}
					}
				while(!nvc[nc-1] && nc > 1) nc--;
				while(!nvr[nr-1] && nr > 1) nr--;
				for(i = 1, mr = nvr[0]; i < nr; i++)if(nvr[i] > mr) mr = nvr[i];
				for(i = 1, mc = nvc[0]; i < nc; i++)if(nvc[i] > mc) mc = nvc[i];
				for( ; nvr[hr] < mr && hr < nr; hr++);
				for( ; nvc[hc] < mc && hc < nc; hc++);
				for(i = hr; i < nr; i++) if(nvr[i] < mr) res = -1;
				for(i = hc; i < nc; i++) if(nvc[i] < mc) res = -1;
				for(i = 0, mr = nc-hc; i < nr; i++) nvr[i] = mr;
				for(i = 0, mc = nr-hr; i < nc; i++) nvc[i] = mc;
				if(res < 0 || mr < 2 || mc < 2) {
					InfoBox("There are missing data!");
					for(i = 0; i < nc; i++) if(vals[i]) free(vals[i]);
					free(vals);			free(nvr);			free(nvc);
					nvr = nvc = 0L;		vals = 0L;
					bContinue = true;
					}
				delete rD;
				}
			break;
		default:
			nr = nc = 0;		break;
			}
		}while (res < 0);
	if(res == 1 && (vals) && (cs = (double*)calloc(nc, sizeof(double))) 
		&& (rs = (double*)calloc(nr, sizeof(double)))
		&& (c_ss = (double*)calloc(nc, sizeof(double)))
		&& (r_ss = (double*)calloc(nr, sizeof(double)))
		&& (c_m = (double*)calloc(nc, sizeof(double)))
		&& (r_m = (double*)calloc(nr, sizeof(double)))
		&& (abc = (double*)calloc(nr > nc ? nr : nc, sizeof(double)))
		&& (cnames = (char**)calloc(rec.right-rec.left+1, sizeof(char*)))
		&& (rnames = (char**)calloc(rec.bottom-rec.top+1, sizeof(char*)))
		&& (res_tab = (double**)calloc(4, sizeof(double*)))
		&& (res_tab[0] = (double*)calloc(5, sizeof(double)))
		&& (res_tab[1] = (double*)calloc(5, sizeof(double)))
		&& (res_tab[2] = (double*)calloc(5, sizeof(double)))
		&& (res_tab[3] = (double*)calloc(5, sizeof(double)))){
		//get column and row descriptors
		for(c = hc; c < nc; c++) {
			if(rDesc = new AccRange(mkRangeRef(rec.top, rec.left+c, rec.bottom, rec.left+c))) {
				cnames[c-hc] = rDesc->RangeDesc(data, hr ? 4 : 1);
				delete rDesc;
				}
			}
		for(r = hr; r < nr; r++) {
			if(rDesc = new AccRange(mkRangeRef(rec.top+r, rec.left, rec.top+r, rec.right))) {
				rnames[r-hr] = rDesc->RangeDesc(data, hc ? 4 : 1);
				delete rDesc;
				}
			}
		//grand mean
		for(c = hc, gm = 0.0; c < nc; c++) for(r = hr; r < nr; r++) {
			gm += vals[r][c];	cs[c] += vals[r][c];	rs[r] += vals[r][c];
			}
		gm /= ((double)((nc-hc)*(nr-hr)));
		//anova stats
		for(c = hc; c < nc; c++) cs[c] /= ((double)nvc[c]);
		for(c = hc, ssc = 0.0; c < nc; c++) ssc += ((tmp = cs[c]-gm)*tmp); 
		for(r = hr; r < nr; r++) rs[r] /= ((double)nvr[r]);
		for(r = hr, ssr = 0.0; r < nr; r++) ssr += ((tmp = rs[r]-gm)*tmp);
		ssc *= ((double)(nr-hr));		ssr *= ((double)(nc-hc));
		for(c = hc, sse = 0.0; c < nc; c++) for(r = hr; r < nr; r++) {
			sse += ((tmp = vals[r][c]-cs[c]-rs[r]+gm)*tmp);
			}
		for(c = hc; c < nc; c++) for(r = hr; r < nr; r++) {
			c_m[c-hc] += vals[r][c];		r_m[r-hr] += vals[r][c];
			}
		for(c = hc; c < nc; c++) c_m[c-hc] /= ((double)(nvc[c]));
		for(r = hr; r < nr; r++) r_m[r-hr] /= ((double)(nvr[r]));
		for(c = hc; c < nc; c++) for(r = hr; r < nr; r++) {
			c_ss[c-hc] += ((tmp = vals[r][c]-c_m[c-hc])*tmp);
			r_ss[r-hr] += ((tmp = vals[r][c]-r_m[r-hr])*tmp);
			}
		//prepare table for report
		res_tab[0][0] = (double)(nc-hc-1);			res_tab[1][0] = (double)(nr-hr-1);
		res_tab[2][0] = res_tab[0][0] * res_tab[1][0];
		res_tab[3][0] = res_tab[0][0] + res_tab[1][0] + res_tab[2][0];
		res_tab[0][1] = ssc;						res_tab[0][2] = ssc/res_tab[0][0];
		res_tab[1][1] = ssr;						res_tab[1][2] = ssr/res_tab[1][0];
		res_tab[2][1] = sse;						res_tab[2][2] = sse/res_tab[2][0];
		res_tab[3][1] = ssc + ssr + sse;
		res_tab[0][3] = res_tab[0][2] / res_tab[2][2];
		res_tab[1][3] = res_tab[1][2] / res_tab[2][2];
		res_tab[0][4] = f_dist(res_tab[0][3], res_tab[0][0], res_tab[2][0]);
		res_tab[1][4] = f_dist(res_tab[1][3], res_tab[1][0], res_tab[2][0]);
		rep_init();
		page = new Page(parent, data);
		mk_header(page, "<b>Two-Way ANOVA</b>", data);
		cx = txtdef1.fSize*5.0;		cy = txtdef1.fSize*10.0;
		dBounds.Xmin = 0.5;			dBounds.Xmax = ((double)(nc-hc))+0.5;
		dBounds.Ymin = dmin;		dBounds.Ymax = dmax;
		//plot column results
		if((graph = new Graph(parent, data, 0L, 0)) && (txt_obj = mk_scatt(0, 0L, c_m, c_ss, nvc+hc, nc-hc, "Mean", "Columns", "Means <u>+</u> S.D."))){
			OpenGraph(graph, 0L, (unsigned char*)txt_obj, false);
			if(LastOpenGO && LastOpenGO->Id == GO_PLOTSCATT) {
				if(((PlotScatt*)LastOpenGO)->x_tv = new TextValue()){
					for(i = 0; i < (nc-hc); i++) ((PlotScatt*)LastOpenGO)->x_tv->GetValue(cnames[i]);
					}
				}
			free(txt_obj);								graph->moveable = 0;
			graph->DRect.Xmax = graph->DRect.Xmin + graph->DRect.Ymax - graph->DRect.Ymin;
			graph->GRect.Xmax = graph->GRect.Xmin + graph->GRect.Ymax - graph->GRect.Ymin;
			scale.sx.fx = txtdef1.fSize*5.0;			scale.sy.fx = txtdef1.fSize*10.0;
			graph->Command(CMD_SCALE, &scale, 0L);
			page->Command(CMD_DROP_GRAPH, graph, 0L);
			cx = graph->GetSize(SIZE_GRECT_RIGHT)+txtdef1.fSize;
			cy = graph->GetSize(SIZE_GRECT_BOTTOM)+txtdef2.fSize*2.0;
			}
		dBounds.Ymin = 0.5;			dBounds.Ymax = ((double)(nr-hr))+0.5;
		dBounds.Xmin = dmin;		dBounds.Xmax = dmax;
		for(r = 0, tmp = 1.0; r < (nr-hr); r++, tmp += 1.0) abc[r] = tmp;
		//plot row results
		if((graph = new Graph(parent, data, 0L, 0x10)) && (txt_obj = mk_scatt(0x10, r_m, abc, r_ss, nvr+hr, nr-hr, "Mean", "Means <u>+</u> S.D.", "Rows"))){
			OpenGraph(graph, 0L, (unsigned char*)txt_obj, false);
			if(LastOpenGO && LastOpenGO->Id == GO_PLOTSCATT) {
				if(((PlotScatt*)LastOpenGO)->y_tv = new TextValue()){
					for(i = 0; i < nr; i++) ((PlotScatt*)LastOpenGO)->y_tv->GetValue(rnames[i]);
					}
				}
			free(txt_obj);								graph->moveable = 0;
			graph->DRect.Xmax = graph->DRect.Xmin + graph->DRect.Ymax - graph->DRect.Ymin;
			graph->GRect.Xmax = graph->GRect.Xmin + graph->GRect.Ymax - graph->GRect.Ymin;
			scale.sx.fx = cx;							scale.sy.fx = txtdef1.fSize*10.0;
			graph->Command(CMD_SCALE, &scale, 0L);
			page->Command(CMD_DROP_GRAPH, graph, 0L);
			}
		cx = txtdef1.fSize*5.0;
		//draw anova table and clean up
		rep_DrawText(page, cx, cy, false, TXA_HLEFT, &txtdef1, "<b>Anova:</b>");
		cy = mk_table(page, cx, cy+txtdef2.fSize, 4, res_tab)+txtdef2.fSize;
		if(!(parent->Command(CMD_DROP_GRAPH, page, 0L))) delete page;
		free(c_ss);			free(r_ss);
		free(c_m);			free(r_m);
		}
	if(vals) {
		for(i = 0; i < nc; i++) if(vals[i]) free(vals[i]);
		free(vals);
		}
	if(res_tab) {
		for(i = 0; i < 4; i++) if(res_tab[i]) free(res_tab[i]);
		free(res_tab);
		}
	if (cnames) {
		for(c = 0; c < (rec.right-rec.left+1); c++) if(cnames[c]) free(cnames[c]);
		free(cnames);
		}
	if (rnames) {
		for(r = 0; r < (rec.bottom-rec.top+1); r++) if(rnames[r]) free(rnames[r]);
		free(rnames);
		}
	if(nvr) free(nvr);		if(nvc) free(nvc);
	CloseDlgWnd(hDlg);		delete Dlg;		free(TwAnovDlg);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Friedman's non-parametric two way anova
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void rep_fmanova(GraphObj *parent, DataObj *data)
{
	TabSHEET tab1 = {0, 40, 10, "Input Data"};
	DlgInfo *FmAnovDlg;
	void *dyndata[] = {(void*)&tab1, (void*)"rectangular range for variables",
		(void*)" column/row headers present"};
	DlgRoot *Dlg;
	void *hDlg;
	int i, hc, hr, mr, mc, nr, nc, c, r, res, *nvr=0L, *nvc=0L;
	bool bContinue = false;
	char *mrk, *txt_obj, **cnames = 0L, **rnames = 0L;
	double tmp, cx, cy, dmin, dmax, cchi2, rchi2, prob;
	double **vals = 0L, **rows = 0L, **cols = 0L, *idx, *cs = 0L, *rs = 0L;
	double *m, *b1, *b2, *w1, *w2, *trc;
	RECT rec;
	scaleINFO scale = {{0.0, 0.7}, {0.0, 0.7}, {0.0, 0.7}};
	AccRange *rD =0L, *rDesc;
	anyResult ares;
	Graph *graph;
	Page *page;

	if(!parent || !data) return;
	if(!(FmAnovDlg = CompileDialog(TwAnov_DlgTmpl, dyndata))) return;
	if(data->Command(CMD_GETMARK, &mrk, 0L))rlp_strcpy(TmpTxt, TMP_TXT_SIZE, mrk);
	else {
		data->ValueRec(&rec);
		rlp_strcpy(TmpTxt, 100, mkRangeRef(rec.top, rec.left, rec.bottom, rec.right));
		}
	if(!(Dlg = new DlgRoot(FmAnovDlg, data)))return;
	hDlg = CreateDlgWnd("Friedman's Non-Parametric Two-Way Anova", 50, 50, 420, 220, Dlg, 0x4L);
	do {
		LoopDlgWnd();
		res = Dlg->GetResult();
		switch (res) {
		case 0:
			if(bContinue) res = -1;
			else if(Dlg->GetCheck(10)) res = -1;
			break;
		case -1:
			bContinue = false;
			break;
		case 1:
			if(Dlg->GetText(101, TmpTxt+200, TMP_TXT_SIZE-200) &&(rD = new AccRange(TmpTxt+200))&& rD->BoundRec(&rec)
				&& (vals = (double**)calloc(rec.bottom - rec.top +1, sizeof(double*)))) {
				nc = rec.right-rec.left+1;				nr = rec.bottom-rec.top+1;
				nvc = (int*)calloc(nc, sizeof(int));	nvr = (int*)calloc(nr, sizeof(int));
				dmin = HUGE_VAL;						dmax = -HUGE_VAL;
				if(Dlg->GetCheck(102)) hr = hc = 1;
				else hr = hc = 0;
				for(i = rec.top+hr; i <= rec.bottom; i++) vals[i-rec.top] = (double*)calloc(nc, sizeof(double));
				for(c = rec.left+hc; c <= rec.right; c++) for(r = rec.top; r <= rec.bottom; r++) {
					if(data->GetResult(&ares, r, c,false) && ares.type == ET_VALUE){
						nvc[c-rec.left]++;				nvr[r-rec.top]++;
						if(vals[r-rec.top]) vals[r-rec.top][c-rec.left] = ares.value;
						if(ares.value > dmax) dmax = ares.value;
						if(ares.value < dmin) dmin = ares.value;
						}
					}
				while(!nvc[nc-1] && nc > 1) nc--;
				while(!nvr[nr-1] && nr > 1) nr--;
				for(i = 1, mr = nvr[0]; i < nr; i++)if(nvr[i] > mr) mr = nvr[i];
				for(i = 1, mc = nvc[0]; i < nc; i++)if(nvc[i] > mc) mc = nvc[i];
				for( ; nvr[hr] < mr && hr < nr; hr++);
				for( ; nvc[hc] < mc && hc < nc; hc++);
				for(i = hr; i < nr; i++) if(nvr[i] < mr) res = -1;
				for(i = hc; i < nc; i++) if(nvc[i] < mc) res = -1;
				for(i = 0, mr = nc-hc; i < nr; i++) nvr[i] = mr;
				for(i = 0, mc = nr-hr; i < nc; i++) nvc[i] = mc;
				if(res < 0 || mr < 2 || mc < 2) {
					InfoBox("There are missing data!");
					for(i = 0; i < nc; i++) if(vals[i]) free(vals[i]);
					free(vals);			free(nvr);			free(nvc);
					nvr = nvc = 0L;		vals = 0L;
					bContinue = true;
					}
				delete rD;
				}
			break;
		default:
			nr = nc = 0;		break;
			}
		}while (res < 0);
	if(res == 1&& (vals) && (cs = (double*)calloc(nr, sizeof(double))) 
		&& (rs = (double*)calloc(nc, sizeof(double)))
		&& (cols = (double**)calloc(nc, sizeof(double*)))
		&& (rows = (double**)calloc(nr, sizeof(double*)))
		&& (cnames = (char**)calloc(rec.right-rec.left+1, sizeof(char*)))
		&& (rnames = (char**)calloc(rec.bottom-rec.top+1, sizeof(char*)))
		&& (idx = (double*)malloc((nr > nc ? nr:nc)*sizeof(double)))
		&& (m = (double*)malloc((nr > nc ? nr:nc)*sizeof(double)))
		&& (b1 = (double*)malloc((nr > nc ? nr:nc)*sizeof(double)))
		&& (b2 = (double*)malloc((nr > nc ? nr:nc)*sizeof(double)))
		&& (w1 = (double*)malloc((nr > nc ? nr:nc)*sizeof(double)))
		&& (w2 = (double*)malloc((nr > nc ? nr:nc)*sizeof(double)))
		&& (trc = (double*)malloc((nr > nc ? nr:nc)*sizeof(double)))) {
		//get column and row descriptors
		for(c = hc; c < nc; c++) {
			if(rDesc = new AccRange(mkRangeRef(rec.top, rec.left+c, rec.bottom, rec.left+c))) {
				cnames[c-hc] = rDesc->RangeDesc(data, hr ? 4 : 1);
				delete rDesc;
				}
			}
		for(r = hr; r < nr; r++) {
			if(rDesc = new AccRange(mkRangeRef(rec.top+r, rec.left, rec.top+r, rec.right))) {
				rnames[r-hr] = rDesc->RangeDesc(data, hc ? 4 : 1);
				delete rDesc;
				}
			}
		//create ranks
		for(c = hc; c < nc; c++) if(cols[c-hc] = (double*)calloc(nr, sizeof(double))) {
			for(r = hr; r < nr; r++) {
				cols[c-hc][r-hr] = vals[r][c];		idx[r-hr] = (double)(r-hr);
				}
			SortArray2(nr-hr, cols[c-hc], idx);		crank(nr-hr, cols[c-hc], &tmp);
			SortArray2(nr-hr, idx, cols[c-hc]);
			}
		for(r = hr; r < nr; r++) if(rows[r-hr] = (double*)calloc(nc, sizeof(double))) {
			for(c = hc; c < nc; c++) {
				rows[r-hr][c-hc] = vals[r][c];		idx[c-hc] = (double)(c-hc);
				}
			SortArray2(nc-hc, rows[r-hr], idx);		crank(nc-hc, rows[r-hr], &tmp);
			SortArray2(nc-hc, idx, rows[r-hr]);	
			}
		for(r = 0; r < (nr-hr); r++) for(c = 0; c < (nc-hc); c++){
			cs[r] += cols[c][r];	rs[c] += rows[r][c];
			}
		//rank sums and statistics
		for(r = 0, cchi2 = 0.0; r < (nr-hr); r++) cchi2 += (cs[r]*cs[r]);
		cchi2 = cchi2 * 12.0 / ((double)((nr-hr)*(nc-hc)*(nr-hr+1))) - 3.0*(nc-hc)*(nr-hr+1);
		for(c = 0, rchi2 = 0.0; c < (nc-hc); c++) rchi2 += (rs[c]*rs[c]);
		rchi2 = rchi2 * 12.0 / ((double)((nc-hc)*(nr-hr)*(nc-hc+1))) - 3.0*(nr-hr)*(nc-hc+1);
		//create report page
		rep_init();
		page = new Page(parent, data);
		mk_header(page, "<b>Friedman's non-parametric two-way ANOVA</b>", data);
		cx = txtdef1.fSize*5.0;		cy = txtdef1.fSize*10.0;
		//plot column results
		for(c = hc; c < nc; c++) {
			w1[c-hc] = HUGE_VAL;		w2[c-hc] = -HUGE_VAL;
			for(r = hr; r < nr; r++) {
				trc[r-hr] = vals[r][c];
				if(vals[r][c] < w1[c-hc]) w1[c-hc] = vals[r][c];
				if(vals[r][c] > w2[c-hc]) w2[c-hc] = vals[r][c];
				}
			d_quartile(r-hr, trc, b1+c-hc, m+c-hc, b2+c-hc);
			}
		dBounds.Xmin = 0.5;			dBounds.Xmax = ((double)(nc-hc))+0.5;
		dBounds.Ymin = dmin;		dBounds.Ymax = dmax;
		if((graph = new Graph(parent, data, 0L, 0)) && 
			(txt_obj = mk_boxplot(0, 0L, m, b1, b2, w1, w2, nvc, nc-hc, "medians", "25-75%", "min/max"))){
			OpenGraph(graph, 0L, (unsigned char*)txt_obj, false);
			if(LastOpenGO && LastOpenGO->Id == GO_BOXPLOT) {
				if(((BoxPlot*)LastOpenGO)->x_tv = new TextValue()){
					for(i = 0; i < (nc-hc); i++) ((PlotScatt*)LastOpenGO)->x_tv->GetValue(cnames[i]);
					}
				if(((BoxPlot*)LastOpenGO)->x_info=(char*)malloc(20*sizeof(char)))
					rlp_strcpy(((BoxPlot*)LastOpenGO)->x_info, 20, "Columns");
				if(((BoxPlot*)LastOpenGO)->y_info=(char*)malloc(20*sizeof(char)))
					rlp_strcpy(((BoxPlot*)LastOpenGO)->y_info, 20, "Location");
				}
			free(txt_obj);								graph->moveable = 0;
			graph->DRect.Xmax = graph->DRect.Xmin + graph->DRect.Ymax - graph->DRect.Ymin;
			graph->GRect.Xmax = graph->GRect.Xmin + graph->GRect.Ymax - graph->GRect.Ymin;
			scale.sx.fx = txtdef1.fSize*5.0;			scale.sy.fx = txtdef1.fSize*10.0;
			graph->Command(CMD_SCALE, &scale, 0L);
			page->Command(CMD_DROP_GRAPH, graph, 0L);
			cx = graph->GetSize(SIZE_GRECT_RIGHT)+txtdef1.fSize;
			}
		//plot row results
		for(r = hr; r < nr; r++) {
			w1[r-hr] = HUGE_VAL;		w2[r-hr] = -HUGE_VAL;
			for(c = hc; c < nc; c++) {
				trc[c-hc] = vals[r][c];
				if(vals[r][c] < w1[r-hr]) w1[r-hr] = vals[r][c];
				if(vals[r][c] > w2[r-hr]) w2[r-hr] = vals[r][c];
				if(vals[r][c] < dBounds.Xmin) dBounds.Xmin = vals[r][c];
				if(vals[r][c] > dBounds.Xmax) dBounds.Xmax = vals[r][c];
				}
			d_quartile(c-hc, trc, b1+r-hr, m+r-hr, b2+r-hr);
			}
		for(r = hr; r < nr; r++) trc[r-hr] = ((double)(r-hr+1));
		dBounds.Ymin = 0.5;			dBounds.Ymax = ((double)(nr-hr))+0.5;
		dBounds.Xmin = dmin;		dBounds.Xmax = dmax;
		if((graph = new Graph(parent, data, 0L, 0x10)) && 
			(txt_obj = mk_boxplot(1, m, trc, b1, b2, w1, w2, nvr, nr-hr, "medians", "25-75%", "min/max"))){
			OpenGraph(graph, 0L, (unsigned char*)txt_obj, false);
			if(LastOpenGO && LastOpenGO->Id == GO_BOXPLOT) {
				if(((BoxPlot*)LastOpenGO)->y_tv = new TextValue()){
					for(i = 0; i < (nr-hr); i++)((PlotScatt*)LastOpenGO)->y_tv->GetValue(rnames[i]);
					}
				if(((BoxPlot*)LastOpenGO)->y_info=(char*)malloc(20*sizeof(char)))
					rlp_strcpy(((BoxPlot*)LastOpenGO)->y_info, 20, "Rows");
				if(((BoxPlot*)LastOpenGO)->x_info=(char*)malloc(20*sizeof(char)))
					rlp_strcpy(((BoxPlot*)LastOpenGO)->x_info, 20, "Location");
				}
			free(txt_obj);								graph->moveable = 0;
			graph->DRect.Xmax = graph->DRect.Xmin + graph->DRect.Ymax - graph->DRect.Ymin;
			graph->GRect.Xmax = graph->GRect.Xmin + graph->GRect.Ymax - graph->GRect.Ymin;
			scale.sx.fx = cx;							scale.sy.fx = txtdef1.fSize*10.0;
			graph->Command(CMD_SCALE, &scale, 0L);
			page->Command(CMD_DROP_GRAPH, graph, 0L);
			}
		cx = txtdef1.fSize*5.0;			cy = graph->GetSize(SIZE_GRECT_BOTTOM)+txtdef2.fSize*2.0;
		free(m);	free(b1);	free(b2);	free(w1);	free(w2);	free(trc);
		rep_DrawText(page, cx, cy, false, TXA_HLEFT, &txtdef1, "<b>Between columns:</b>");
		cy += txtdef1.fSize *1.5;
		prob = chi_dist(fabs(rchi2), nc-hc-1, 0.0);
#ifdef USE_WIN_SECURE
		i = sprintf_s(TmpTxt, 60, "N = %d, Chi<sup>2</sup> = %.2lf, P ", nc-hc, rchi2);
		if(prob > 0.001) sprintf_s(TmpTxt+i, 20, "= %.3lf", prob);
#else
		i = sprintf(TmpTxt, "N = %d, Chi<sup>2</sup> = %.2lf, P ", nc-hc, rchi2);
		if(prob > 0.001) sprintf(TmpTxt+i, "= %.3lf", prob);
#endif
		else rlp_strcpy(TmpTxt+i, 40, "< 0.001");
		rep_DrawText(page, cx+txtdef1.fSize*3.0, cy, false, TXA_HLEFT, &txtdef1, TmpTxt);
		cy += txtdef1.fSize*2.0;
		rep_DrawText(page, cx, cy, false, TXA_HLEFT, &txtdef1, "<b>Between rows:</b>");
		cy += txtdef1.fSize *1.5;
		prob = chi_dist(fabs(cchi2), nr-hr-1, 0.0);
#ifdef USE_WIN_SECURE
		i = sprintf_s(TmpTxt, 60, "N = %d, Chi<sup>2</sup> = %.2lf, P ", nr-hr, cchi2);
		if(prob > 0.001) sprintf_s(TmpTxt+i, 20, "= %.3lf", prob);
#else
		i = sprintf(TmpTxt, "N = %d, Chi<sup>2</sup> = %.2lf, P ", nr-hr, cchi2);
		if(prob > 0.001) sprintf(TmpTxt+i, "= %.3lf", prob);
#endif
		else rlp_strcpy(TmpTxt+i, 40, "< 0.001");
		rep_DrawText(page, cx+txtdef1.fSize*3.0, cy, false, TXA_HLEFT, &txtdef1, TmpTxt);
		if(!(parent->Command(CMD_DROP_GRAPH, page, 0L))) delete page;
		free(rs);		free(cs);		free(idx);
		}
	if(cols) {
		for(i = 0; i < nc; i++) if(cols[i]) free(cols[i]);
		free(cols);
		}
	if(rows) {
		for(i = 0; i < nr; i++) if(rows[i]) free(rows[i]);
		free(rows);
		}
	if(vals) {
		for(i = 0; i < nc; i++) if(vals[i]) free(vals[i]);
		free(vals);
		}
	if (cnames) {
		for(c = 0; c < (rec.right-rec.left+1); c++) if(cnames[c]) free(cnames[c]);
		free(cnames);
		}
	if (rnames) {
		for(r = 0; r < (rec.bottom-rec.top+1); r++) if(rnames[r]) free(rnames[r]);
		free(rnames);
		}
	if(nvr) free(nvr);		if(nvc) free(nvc);
	CloseDlgWnd(hDlg);		delete Dlg;		free(FmAnovDlg);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Two way anova with replica
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static char *TwAnovDlg_Tmpl = 
	"1,2,,DEFAULT,PUSHBUTTON,-1,148,10,45,12\n"
	"2,3,,,PUSHBUTTON,-2,148,25,45,12\n"
	"3,,4,ISPARENT | CHECKED,GROUP,0,0,0,0,0\n"
	"4,10,100,ISPARENT | CHECKED,SHEET,1,5,10,130,80\n"
	"10,,,CHECKED,CHECKPIN,0,5,0,12,8\n"
	"100,101,,,LTEXT,2,10,30,60,8\n"
	"101,102,,,RANGEINPUT,-15,20,40,100,10\n"
	"102,103,,,LTEXT,3,20,55,53,8\n"
	"103,,,LASTOBJ,EDVAL1,4,75,55,30,10";

typedef struct _anov_group_info {
	double *rmeans, *cmeans;
	int *vpr, *vpc, nvals;
	double mean;
	}anov_group_info;

void rep_twoway_anova(GraphObj *parent, DataObj *data)
{
	TabSHEET tab1 = {0, 40, 10, "Input Data"};
	DlgInfo *TwAnovDlg;
	double dlpr = 3.0, *cmeans;
	void *dyndata[] = {(void*)&tab1, (void*)"rectangular range for variables", (void*)"lines per replica:",
		(void*)&dlpr};
	DlgRoot *Dlg;
	void *hDlg;
	int i, j, k, res, ntot, lpr, ngr, r1, r2, c1, c2, iErr;
	int *vpc, *vpr;
	double tmp, dn, gMean, SSwithin, SSsubgr, SStotal, SSrows, SScols, SSinteract;
	double **res_tab = 0L, cx, cy;
	bool bContinue = false;
	AccRange *rD =0L;
	char *mrk;
	RECT rec;
	anyResult ares;
	anov_group_info *agr;
	Page *page;

	if(!parent || !data) return;
	if(!(TwAnovDlg = CompileDialog(TwAnovDlg_Tmpl, dyndata))) return;
	if(data->Command(CMD_GETMARK, &mrk, 0L))rlp_strcpy(TmpTxt, TMP_TXT_SIZE, mrk);
	else {
		data->ValueRec(&rec);
		rlp_strcpy(TmpTxt, 100, mkRangeRef(rec.top, rec.left, rec.bottom, rec.right));
		}
	if(!(Dlg = new DlgRoot(TwAnovDlg, data)))return;
	hDlg = CreateDlgWnd("Two-Way Anova with Replica", 50, 50, 420, 220, Dlg, 0x4L);
loop1:
	do {
		LoopDlgWnd();
		res = Dlg->GetResult();
		switch (res) {
		case 0:
			if(bContinue) res = -1;
			else if(Dlg->GetCheck(10)) res = -1;
			break;
		case -1:
			bContinue = false;
			break;
		case 1:
			Dlg->GetValue(103, &dlpr);		lpr = (int)dlpr;
			break;
			}
		}while (res < 0);
	if(res == 1 && Dlg->GetText(101, TmpTxt+200, TMP_TXT_SIZE-200) &&(rD = new AccRange(TmpTxt+200))
		&& rD->BoundRec(&rec) && (ntot = rD->CountItems())){
		r1 = rec.top;		r2 = rec.bottom+1;			c1= rec.left;	c2 = rec.right+1;
		vpc = (int*)calloc(c2-c1+1, sizeof(int));		vpr = (int*)calloc(r2-r1+1, sizeof(int));
		for(k = iErr = 0; k < 2; k++) {
			for(i = c1;	i < c2; i++) for(j = r1; j < r2; j++) {
				data->GetResult(&ares, j, i, false);
				if(ares.type == ET_VALUE) {
					vpc[i-c1]++;					vpr[j-r1]++;
					}
				}
			if(!k) {
				for(i = 0; !vpc[i]; i++);			for(j = 0; !vpr[j]; j++);
				memset(vpc,0,sizeof(int)*(c2-c1));	memset(vpr,0,sizeof(int)*(r2-r1));
				c1 += i;		r1 += j;
				}
			}
		while(c2 > c1 && !vpc[c2-c1-1]) c2--;		while(r2 > r1 && !vpr[r2-r1-1]) r2--;
		ngr = (int)(((double)(r2-r1))/dlpr);
		if(ngr * lpr < r2 -r1) iErr = 2;
		agr = (anov_group_info *)calloc(ngr+1, sizeof(anov_group_info));
		cmeans = (double *)calloc(c2-c1+1, sizeof(double));
		for(i = 0; i <= ngr; i++) {
			agr[i].cmeans = (double*)calloc(c2-c1+2, sizeof(double));
			agr[i].vpc = (int*)calloc(c2-c1+2, sizeof(int));
			agr[i].rmeans = (double*)calloc(lpr+2, sizeof(double));
			agr[i].vpr = (int*)calloc(lpr+2, sizeof(int));
			}
		for(i = c1; i < c2; i++) for(j = r1; j < r2; j++) {
			k = (j-r1)/lpr;		data->GetResult(&ares, j, i, false);
			if(ares.type == ET_VALUE) {
				agr[k].cmeans[i-c1] += ares.value;		agr[k].vpc[i-c1]++;
				agr[k].rmeans[j-k*lpr] += ares.value;	agr[k].vpr[j-k*lpr]++;
				agr[k].mean += ares.value;				agr[k].nvals++;
				cmeans[i-c1] += ares.value;
				}
			else iErr = 1;
			}
		for(k = 0; k < ngr; k++) {
			agr[k].mean /= ((double)(agr[k].nvals));
			for(i = 0; i < (c2-c1); i++) {
				agr[k].cmeans[i] /= ((double)(agr[k].vpc[i]));
				}
			}
		for(i = c1, SSwithin = gMean = 0.0, dn = 1.0; i < c2; i++) for(j = r1; j < r2; j++) {
			k = (j-r1)/lpr;		data->GetResult(&ares, j, i, false);
			if(ares.type == ET_VALUE) {
				SSwithin += ((tmp = ares.value-agr[k].cmeans[i-c1])*tmp);
				gMean += ((ares.value - gMean)/dn);		dn	+=	1.0;
				}
			}
		for(k = 0, SSsubgr = SSrows = 0.0; k < ngr; k++) {
			for(i = 0; i < (c2-c1); i++) {
				SSsubgr += (dlpr*((tmp = agr[k].cmeans[i] - gMean) * tmp));
				}
			SSrows += ((tmp = (agr[k].mean - gMean)) * tmp);
			}
		for(i = c1, SScols = 0.0; i < c2; i++) {
			cmeans[i-c1] /= ((double)(r2-r1));
			SScols += ((tmp = cmeans[i-c1]-gMean) * tmp);
			}
		SStotal = SSsubgr + SSwithin;		SSrows *= (dlpr *((double)(c2-c1)));
		SScols *= (dlpr * ((double)ngr));	SSinteract = fabs(SSsubgr - SSrows - SScols);
		if(!iErr&& (res_tab = (double**)calloc(5, sizeof(double*)))
			&& (res_tab[0] = (double*)calloc(5, sizeof(double)))
			&& (res_tab[1] = (double*)calloc(5, sizeof(double)))
			&& (res_tab[2] = (double*)calloc(5, sizeof(double)))
			&& (res_tab[3] = (double*)calloc(5, sizeof(double)))
			&& (res_tab[4] = (double*)calloc(5, sizeof(double)))) {
			res_tab[0][0] = (double)(ngr-1);				res_tab[1][0] = (double)(c2-c1-1);
			res_tab[2][0] = res_tab[0][0]* res_tab[1][0];	res_tab[3][0] = (double)(ngr*(c2-c1)*(lpr-1));
			res_tab[4][0] = res_tab[0][0] + res_tab[1][0] + res_tab[2][0] + res_tab[3][0];
			res_tab[0][1] = SSrows;			res_tab[0][2] = res_tab[0][1] / res_tab[0][0];
			res_tab[1][1] = SScols;			res_tab[1][2] = res_tab[1][1] / res_tab[1][0];
			res_tab[2][1] = SSinteract;		res_tab[2][2] = res_tab[2][1] / res_tab[2][0];
			res_tab[3][1] = SSwithin;		res_tab[3][2] = res_tab[3][1] / res_tab[3][0];
			res_tab[4][1] = SStotal;		res_tab[0][3] = res_tab[0][2] / res_tab[3][2];
			res_tab[1][3] = res_tab[1][2] / res_tab[3][2];
			res_tab[2][3] = res_tab[2][2] / res_tab[3][2];
			res_tab[0][4] = f_dist(res_tab[0][3], res_tab[0][0], res_tab[3][0]);
			res_tab[1][4] = f_dist(res_tab[1][3], res_tab[1][0], res_tab[3][0]);
			res_tab[2][4] = f_dist(res_tab[2][3], res_tab[2][0], res_tab[3][0]);


			rep_init();
//			dBounds.Xmin = 0.5;				dBounds.Xmax = ((double)nc)+0.5;
			page = new Page(parent, data);
			mk_header(page, "<b>Two-Way ANOVA with Replication</b>", data);

			cx = txtdef1.fSize*5.0;		cy = txtdef1.fSize*10.0;

			rep_DrawText(page, cx, cy, false, TXA_HLEFT, &txtdef1, "<b>Anova:</b>");
			cy = mk_table(page, cx, cy+txtdef2.fSize, 3, res_tab)+txtdef2.fSize;
			if(!(parent->Command(CMD_DROP_GRAPH, page, 0L))) delete page;
			free(res_tab[0]);			free(res_tab[1]);			free(res_tab[2]);
			free(res_tab[3]);			free(res_tab[4]);			free(res_tab);
			}
		for(i = 0; i <= ngr; i++) {
			free(agr[i].cmeans);					free(agr[i].vpc);
			free(agr[i].rmeans);					free(agr[i].vpr);
			}
		free(vpc);			free(vpr);		free(cmeans);
		delete rD;
		switch(iErr) {
			case 1:		
				ErrorBox("There are missing Data!");
				goto loop1;
			case 2:		
				ErrorBox("The total number of lines\nmust be a multiple of\nlines per replica.");
				goto loop1;
			}
		}
		CloseDlgWnd(hDlg);		delete Dlg;		free(TwAnovDlg);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Kruskal-Wallis Test for Differences of Location
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static char *RepKruskal_DlgTmpl =
	"1,2,,DEFAULT,PUSHBUTTON,-1,158,10,45,12\n"
	"2,3,,,PUSHBUTTON,-2,158,25,45,12\n"
	"3,,10,ISPARENT | CHECKED,GROUP,0,0,0,0,0\n"
	"10,20,152,ISPARENT | CHECKED, SHEET,1,5,10,140,70\n"
	"20,,,CHECKED,CHECKPIN,0,5,0,12,8\n"
	"152,153,,ISPARENT | CHECKED,GROUPBOX,2,12,30,128,45\n"
	"153,154,,,LTEXT,0,25,35,60,8\n"
	"154,155,,,RANGEINPUT,0,25,45,100,10\n"
	"155,156,0,,PUSHBUTTON,-8,95,57,30,12\n"
	"156,,,LASTOBJ,PUSHBUTTON,-9,60,57,35,12";

void rep_kruskal(GraphObj *parent, DataObj *data)
{
	TabSHEET tab1 = {0, 25, 10, "Data"};
	
	void *dyndata[] = {(void*)&tab1, (void*)" select one range for every variable "};
	DlgInfo *KruskalDlg;
	DlgRoot *Dlg;
	void *hDlg;
	int i, j, n, c, r, nt, res, currYR = 0, maxYR = 0, ny, nr, *nvals;
	bool updateYR = true, bContinue = false;
	double h, h1, p, **vals, *x, *y, *by1, *by2, *wy1, *wy2, *ranks, *ridx, *rsums, th, cy, cx[10];
	char **rd = 0L, **names, *txt_obj;
	char *headings[] = {"<i>Groups</i>", "<i>N</i>", "<i>Median</i>", "<i>25% - 75%</i>",
		"<i>Range</i>","<i>Rank Sums</i>"};
	scaleINFO scale = {{0.0, 1.0}, {0.0, 1.0}, {0.0, 1.0}};
	AccRange *rV1 = 0L;
	anyResult ares;
	Page *page;
	Graph *graph;

	if(!parent || !data) return;
	if(!UseRangeMark(data, 2, TmpTxt, TmpTxt+100, TmpTxt+200, TmpTxt+300, TmpTxt+400,
		TmpTxt+500, TmpTxt+600, TmpTxt+700, TmpTxt+800, TmpTxt+900, TmpTxt+1000)) return;
	if(!(KruskalDlg = CompileDialog(RepKruskal_DlgTmpl, dyndata))) return;
	if(TmpTxt[0] && TmpTxt[100] && (rd = (char**)calloc(12, sizeof(char*)))) {
		for(i=j=0; i <= 1000; i +=100) if(TmpTxt[i]) 
			rd[j++] = (char*)memdup(TmpTxt+i, ((int)strlen(TmpTxt+i))+2, 0);	 maxYR = j-1;
		}
	if(!rd && !(rd = (char**)calloc(1, sizeof(char*))))return;
	if(!(Dlg = new DlgRoot(KruskalDlg, data))) return;
	if(rd && rd[currYR] &&  *(rd[currYR])) Dlg->SetText(154, rd[currYR]);
	hDlg = CreateDlgWnd("Kruskal-Wallis Nonparametric Anova", 50, 50, 420, 200, Dlg, 0x4L);
	do {
		if(updateYR) {
			if(currYR >0) Dlg->ShowItem(156, true);
			else Dlg->ShowItem(156, false);
#ifdef USE_WIN_SECURE
			sprintf_s(TmpTxt, TMP_TXT_SIZE, "variable # %d/%d", currYR+1, maxYR+1);
#else
			sprintf(TmpTxt,"variable # %d/%d", currYR+1, maxYR+1);
#endif
			Dlg->SetText(153, TmpTxt);
			updateYR = false;
			}
		LoopDlgWnd();
		res = Dlg->GetResult();
		switch(res) {
		case 0:
			if(bContinue || Dlg->GetCheck(20)) res = -1;
			break;
		case -1:
			bContinue = false;
			break;
		case 155:		case 156:
			res = com_StackDlg(res, Dlg, 0L, 0L, &rd, &currYR,
				&rV1, &bContinue, &ny, &maxYR, &updateYR);
			break;
			}
		}while (res < 0);
	if(res == 1 && (vals = (double**)calloc(sizeof(double*), maxYR+1)) && (nvals = (int*)calloc(sizeof(int), maxYR+1))
		&& (names = (char**)calloc(maxYR+1, sizeof(char*))) && (x = (double*)calloc(maxYR+1, sizeof(double)))
		&& (y = (double*)calloc(maxYR+1, sizeof(double))) && (by1 = (double*)calloc(maxYR+1, sizeof(double)))
		&& (by2 = (double*)calloc(maxYR+1, sizeof(double))) && (wy1 = (double*)calloc(maxYR+1, sizeof(double)))
		&& (wy2 = (double*)calloc(maxYR+1, sizeof(double)))
		&& (rsums = (double*)calloc(maxYR+1, sizeof(double)))) {
		maxYR++;	rep_init();		page = new Page(parent, data);
		dBounds.Xmin = 0.5;			dBounds.Xmax = (double)maxYR+0.3;
		if(rV1) delete rV1;			rV1 = 0L;	ranks = ridx = 0L;
		cy = txtdef1.fSize*10.0;
		mk_header(page, "<b>Kruskal-Wallis Test for Differences of Location</b>", data);
		dBounds.Ymin = HUGE_VAL;	dBounds.Ymax = -HUGE_VAL;
		// get data into two dimensional array
		for(nr = maxYR, i = nt = 0; i < nr; i++) {
			x [i] = y[i] = by1[i] = by2[i] = wy1[i] = wy2[i] = 0.0;		nvals[i] = 0;
			if((rV1 = new AccRange(rd[i])) && (n = rV1->CountItems()) && (vals[i] = (double*)malloc(n*sizeof(double)))) {
				names[i] = rV1->RangeDesc(data, 1);
				for(n = 0, rV1->GetFirst(&c, &r); rV1->GetNext(&c, &r); ) {
					if(data->GetResult(&ares, r, c, false) && ares.type == ET_VALUE) {
						if(!n) wy1[i] = wy2[i] = ares.value;
						else {
							if(ares.value < wy1[i]) wy1[i] = ares.value;
							if(ares.value > wy2[i]) wy2[i] = ares.value;
							}
						if(ares.value < dBounds.Ymin) dBounds.Ymin = ares.value;
						if(ares.value > dBounds.Ymax) dBounds.Ymax = ares.value;
						vals[i][n] = ares.value;		n++;
						}
					}
				nvals[i] = n;	nt += n;	delete rV1;		rV1 = 0;
				}
			if(!names[i] && (names[i] = (char*)malloc(20*sizeof(char)))){
#ifdef USE_WIN_SECURE
				sprintf_s(names[i], 20, "Group %d", i+1);
#else
				sprintf(names[i], "Group %d", i+1);
#endif
				}
			}
		// rank sums
		if(nt && (ranks=(double*)malloc(nt*sizeof(double))) && (ridx=(double*)malloc(nt*sizeof(double)))) {
			for(i = n = 0; i < nr; i++) {
				for(j = 0; j < nvals[i]; j++) {
					ridx[n] = (double)(i);	ranks[n] = vals[i][j];	n++;
					}
				}
			SortArray2(n, ranks, ridx);			crank(n, ranks, &th);
			for(i = 0; i < n; i++) rsums[(int)ridx[i]] += ranks[i];
			//statistics on range sums
			for(i = 0, h = 0.0; i < nr; i++) h += rsums[i]*rsums[i]/((double)nvals[i]);
			h = h * 12.0/(((double)n)*((double)(n+1))) - 3.0*((double)n+1);
			h1 = h  / (1.0 - th/(((double)(n-1)) * ((double)n)* ((double)(n+1))));
			}
		else h = h1 = -1.0;
		// check for unique names
		for(i = 0; i < (nr-1); i++) for(j = i+1; j < nr; j++) {
			if(!strcmp(names[i], names[j])) {
				names[i] = (char*) realloc(names[i], 20 *sizeof(char));
				names[j] = (char*) realloc(names[j], 20 *sizeof(char));
#ifdef USE_WIN_SECURE
				sprintf_s(names[i], 20, "Group %d", i+1);	sprintf_s(names[j], 20, "Group %d", j+1);
#else
				sprintf(names[i], "Group %d", i+1);			sprintf(names[j], "Group %d", j+1);
#endif
				}
			}
		// simple group statistics
		for(i = 0; i < nr; i++) {
			x[i] = (double)(i+1);		d_quartile(nvals[i], vals[i], by1+i, y+i, by2+i);
			}
		// create boxplot
		if((graph = new Graph(parent, data, 0L, 0)) && (txt_obj = mk_boxplot(0, x, y, by1, by2, wy1, wy2,
			nvals, nr,"Median","25-75%","Min./Max."))){
			scale.sx.fx = (txtdef1.fSize*5.0);			scale.sy.fx = (txtdef1.fSize*10.0);
			OpenGraph(graph, 0L, (unsigned char*)txt_obj, false);
			if(LastOpenGO && LastOpenGO->Id == GO_BOXPLOT) {
				if(((BoxPlot*)LastOpenGO)->x_tv = new TextValue()){
					for(i = 0; i < nr; i++) ((BoxPlot*)LastOpenGO)->x_tv->GetValue(names[i]);
					}
				if(((BoxPlot*)LastOpenGO)->x_info = (char*)malloc(20*sizeof(char)))
					rlp_strcpy(((BoxPlot*)LastOpenGO)->x_info, 20, "Groups");
				if(((BoxPlot*)LastOpenGO)->y_info = (char*)malloc(20*sizeof(char)))
					rlp_strcpy(((BoxPlot*)LastOpenGO)->y_info, 20, "Location");
				}
			free(txt_obj);		graph->Command(CMD_SCALE, &scale, 0L);
			cy = graph->GetSize(SIZE_GRECT_BOTTOM)+txtdef1.fSize*2.0;
			page->Command(CMD_DROP_GRAPH, graph, 0L);
			}
		parent->Command(CMD_DROP_GRAPH, page, 0L);
		//report statistics
		cx[0] = txtdef1.fSize*5.0;				cx[1] = cx[0] + linsp1*5.0;
		cx[2] = cx[1] + linsp1*2.5;		cx[3] = cx[2] + linsp1*4.0;
		cx[4] = cx[3] + linsp1*5.0;		cx[5] = cx[4] + linsp1*7.0;
		cx[6] = cx[5] + linsp1*8.0;
		rep_DrawText(page, cx[0], cy, false, TXA_HLEFT, &txtdef1, "<b>Test Statistics:</b>");
		cy += linsp2;			p = chi_dist(h,(double)(nr-1), 0.0);
		dbl_to_str2(TmpTxt, 100, p < 0.0001 ?(char*)"H = %.2lf, P < 0.0001" : (char*)"H = %.2lf, P = %.4lf", h, p);
		rep_DrawText(page, cx[1], cy, false, TXA_HLEFT, &txtdef1, TmpTxt);
		cy += linsp1;			p = chi_dist(h1,(double)(nr-1), 0.0);
		dbl_to_str2(TmpTxt, 100, p < 0.0001 ?(char*)"H(corr.) = %.2lf, P < 0.0001" : (char*)"H(corr.) = %.2lf, P = %.4lf", h1, p);
		if(th >= 1.0) {
			rep_DrawText(page, cx[1], cy, false, TXA_HLEFT, &txtdef1, TmpTxt);
			cy += linsp1;
			}
		cy += linsp2;
		// create summary table
		rep_DrawText(page, cx[0], cy, false, TXA_HLEFT, &txtdef1, "<b>Summary:</b>");
		for(i = 0, cy += linsp2; i < 6; i++) {				//column headers
			c = (i == 3 || i == 4) ? TXA_HCENTER : TXA_HRIGHT;
			rep_DrawText(page, cx[i+1], cy, false, c, &txtdef1, headings[i]);
			}
		mk_hr(page, cx[0], cx[6]+txtdef1.fSize*2.0, cy +linsp1);
		for(i = 0, cy += linsp2; i < nr; i++, cy += linsp1) {
			for(j = 0; j < 6; j++) {
				switch(j) {
				default:	rlp_strcpy(TmpTxt, 20, names[i]);						break;
				case 1:		dbl_to_str1(TmpTxt, 20, "%.0lf", (double)nvals[i]);		break;
				case 2:		dbl_to_str1(TmpTxt, 20, "%g", y[i]);					break;
				case 3:		dbl_to_str2(TmpTxt, 20, "%g - %g", by1[i], by2[i]);		break;
				case 4:		dbl_to_str2(TmpTxt, 20, "%g - %g", wy1[i], wy2[i]);		break;
				case 5:		dbl_to_str1(TmpTxt, 20, "%g", rsums[i]);				break;
					}
				c = (j == 3 || j == 4) ? TXA_HCENTER : TXA_HRIGHT;
				rep_DrawText(page, cx[j+1], cy, false, c, &txtdef1,TmpTxt);
				}
			}
		mk_hr(page, cx[0], cx[6]+txtdef1.fSize*2.0, cy+txtdef1.fSize*0.2);
		for(i= 0; i< nr; i++) {
			if(vals[i]) free(vals[i]);	if(names[i]) free(names[i]);
			}
		free(vals);				free(nvals);		free(names);
		free(x);				free(y);			free(by1);
		free(by2);				free(wy1);			free(wy2);
		free(rsums);
		}
	CloseDlgWnd(hDlg);
	delete Dlg;
	if(rd) {
		for (i = 0; i < maxYR; i++)	if(rd[i]) free(rd[i]);
		free(rd);
		}
	if(ranks)free(ranks);	if(ridx)free(ridx);
	if(rV1) delete rV1;		free(KruskalDlg);
	return;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// simple sample statistics
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static char *SmplStatDlg_Tmpl = 
	"1,2,,DEFAULT,PUSHBUTTON,-1,148,10,45,12\n"
	"2,3,,,PUSHBUTTON,-2,148,25,45,12\n"
	"3,,4,ISPARENT | CHECKED,GROUP,0,0,0,0,0\n"
	"4,10,100,ISPARENT | CHECKED,SHEET,1,5,10,130,80\n"
	"10,,,CHECKED,CHECKPIN,0,5,0,12,8\n"
	"100,101,,,LTEXT,2,10,30,60,8\n"
	"101,,,LASTOBJ,RANGEINPUT,-15,20,40,100,10";
void rep_samplestats(GraphObj *parent, DataObj *data)
{
	TabSHEET tab1 = {0, 40, 10, "Input Data"};
	DlgInfo *SmplStatDlg;
	void *dyndata[] = {(void*)&tab1, (void*)"rectangular range for variables"};
	DlgRoot *Dlg;
	void *hDlg;
	int res, nr, nc, ntot, cb;
	double val, *src_data, cx, cy, ksprob, ksd, sww, swp, mean, sd;
	bool bContinue = false;
	AccRange *rD =0L;
	char *mrk, *x_info, *y_info;
	RECT rec;
	Plot *plot;
	Graph *graph;
	Page *page;

	if(!parent || !data) return;
	if(!(SmplStatDlg = CompileDialog(SmplStatDlg_Tmpl, dyndata))) return;
	if(data->Command(CMD_GETMARK, &mrk, 0L))rlp_strcpy(TmpTxt, TMP_TXT_SIZE, mrk);
	else {
		data->ValueRec(&rec);
		rlp_strcpy(TmpTxt, 100, mkRangeRef(rec.top, rec.left, rec.bottom, rec.right));
		}
	if(!(Dlg = new DlgRoot(SmplStatDlg, data)))return;
	hDlg = CreateDlgWnd("Sample Statistics", 50, 50, 420, 220, Dlg, 0x4L);
	do {
		LoopDlgWnd();
		res = Dlg->GetResult();
		switch (res) {
		case 0:
			if(bContinue) res = -1;
			else if(Dlg->GetCheck(10)) res = -1;
			break;
		case -1:
			bContinue = false;
			break;
			}
		}while (res < 0);
	if(res == 1 && Dlg->GetText(101, TmpTxt+200, TMP_TXT_SIZE) &&(rD = new AccRange(TmpTxt+200))
		&& rD->BoundRec(&rec) && (ntot = rD->CountItems()) && (src_data = (double*)malloc(ntot*sizeof(double)))){
		rep_init();
		x_info = rD->RangeDesc(data, 2);
		if(y_info = (char*)malloc(20)) rlp_strcpy(y_info, 20, "Normal quantiles");
		page = new Page(parent, data);
		cb = rlp_strcpy(TmpTxt, 100, "<b>Sample Statistics for \"");
		if(x_info && x_info[0])	cb += rlp_strcpy(TmpTxt+cb, 100-cb, x_info);
		else cb += rlp_strcpy(TmpTxt+cb, 100-cb, TmpTxt+200);
		rlp_strcpy(TmpTxt+cb,100-cb, "\"</b>");		mk_header(page, TmpTxt, data);
		for(ntot = 0, rD->GetFirst(&nc, &nr); rD->GetNext(&nc, &nr); ) {
			if(data->GetValue(nr, nc, &val)) src_data[ntot++] = val;
			}
		if(ntot > 2 && (graph = new Graph(page, data, 0L, 0))) {
			if(plot = new NormQuant(page, data, src_data, ntot)) {
				plot->x_info = x_info;		plot->y_info = y_info;
				}
			if(!(graph->Command(CMD_DROP_PLOT, (void *)plot, 0L))) {
				delete plot;	plot =0L;
				}
			graph->moveable = 0;					graph->GRect.Xmin += (txtdef1.fSize*5.0);	
			graph->GRect.Xmax += (txtdef1.fSize*5.0);		graph->GRect.Ymin += (txtdef1.fSize*10.0);
			graph->GRect.Ymax += (txtdef1.fSize*10.0);		page->Command(CMD_DROP_GRAPH, graph, 0L);
			cy = graph->GetSize(SIZE_GRECT_BOTTOM)+txtdef1.fSize*3;
			rep_DrawText(page, graph->GRect.Xmin, cy, false, TXA_HLEFT, &txtdef1, "<b>Descriptive Statistics:</b>");
			cy += txtdef1.fSize*1.5;			cx = graph->GetSize(SIZE_GRECT_LEFT)+txtdef1.fSize*25.0;
			mk_median_report(page, cx, cy, src_data, ntot, .95, 0L);
			cx = graph->GetSize(SIZE_GRECT_LEFT)+txtdef1.fSize*2.0;
			cy = mk_mean_report(page, cx, cy, src_data, ntot, .95, 0L);
			//data are sorted by the mk_median_report();
			d_variance(ntot, src_data, &mean, &sd);
			sd = sqrt(sd/((double)(ntot-1)));
			KolSmir(ntot, src_data, norm_dist, mean, sd, true, &ksd, &ksprob);
			cy += txtdef1.fSize*1.5;			cx = graph->GRect.Xmin;
			rep_DrawText(page, cx , cy, false, TXA_HLEFT, &txtdef1, "<b>Test for Normal Distribution:</b>");
			cy += linsp1;						cx += (txtdef1.fSize*2.0);
			cb = dbl_to_str1(TmpTxt, 100, "Kolmogorov-Smirnov D = %.4lf, P ", ksd);
			dbl_to_str1(TmpTxt+cb, 100-cb, ksprob >= 0.0001 ? (char*)"= %.4lf" : (char*)"< 0.0001", ksprob);
			rep_DrawText(page, cx , cy, false, TXA_HLEFT, &txtdef1,TmpTxt);			cy += linsp1/1.2;
			swilk1(ntot, src_data, norm_dist, 0.0, 1.0, true, &sww, &swp);
			if(sww >= 0.0 && swp >= 0.0 && (cb = dbl_to_str1(TmpTxt, 100, "Shapiro-Wilk W = %.4lf, P ", sww))
				&& (dbl_to_str1(TmpTxt+cb, 100-cb, swp >= 0.0001 ? (char*)"= %.4lf" : (char*)"< 0.0001", swp))){
				rep_DrawText(page, cx , cy, false, TXA_HLEFT, &txtdef1,TmpTxt);		cy += linsp1/1.2;
				}
			parent->Command(CMD_DROP_GRAPH, page, 0L);
			}
		else {
			ErrorBox("Insuficient data to do stats\n");
			delete page;
			}
		delete rD;	free(src_data);
		}
	CloseDlgWnd(hDlg);	delete Dlg;		free(SmplStatDlg);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// linear regression analysis
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static double mk_regr_summary(GraphObj *parent, double x, double y, double *dres, double ci, int n)
{
	char *fmts[] = {"slope = %g", "intercept = %g", "observations = %g", "r<sup> 2</sup> = %g", "r = %g"};
	char *ci_fmt = "%g  -  %g";
	char lbl[80];
	double z, s;
	double x1 = x + txtdef1.fSize*3.0;
	double x2 = x + txtdef1.fSize*25.0;
#ifdef _WINDOWS
	double hrw = txtdef1.fSize*38.0;
#else
	double hrw = txtdef1.fSize*1.3*38.0;
#endif

	rep_DrawText(parent, x, y, false, TXA_HLEFT, &txtdef1, "<b>Regression:</b>");
	dbl_to_str1(lbl, 80, "%g%% C.I.", ci);
	rep_DrawText(parent, x2, y, false, TXA_HCENTER, &txtdef1, lbl);
	mk_hr(parent, x, x+hrw, y+txtdef1.fSize*1.2);
	y += linsp1*1.5;		dbl_to_str1(lbl, 80, fmts[0], dres[0]);
	rep_DrawText(parent, x1, y, false, TXA_HLEFT, &txtdef1, lbl);
	dbl_to_str2(lbl, 80, ci_fmt, dres[0]-dres[10], dres[0]+dres[10]);
	rep_DrawText(parent, x2, y, false, TXA_HCENTER, &txtdef1, lbl);
	y += linsp1;		dbl_to_str1(lbl, 80, fmts[1], dres[1]);
	rep_DrawText(parent, x1, y, false, TXA_HLEFT, &txtdef1, lbl);
	dbl_to_str2(lbl, 80, ci_fmt, dres[1]-dres[11], dres[1]+dres[11]);
	rep_DrawText(parent, x2, y, false, TXA_HCENTER, &txtdef1, lbl);
	y += linsp1;		dbl_to_str1(lbl, 80, fmts[2], (double)n);
	rep_DrawText(parent, x1, y, false, TXA_HLEFT, &txtdef1, lbl);
	y += linsp1;		dbl_to_str1(lbl, 80, fmts[3], dres[12]);
	rep_DrawText(parent, x1, y, false, TXA_HLEFT, &txtdef1, lbl);
	y += linsp1;		dbl_to_str1(lbl, 80, fmts[4], sqrt(dres[12]));
	rep_DrawText(parent, x1, y, false, TXA_HLEFT, &txtdef1, lbl);
	z = 0.5 * log((1.0+sqrt(dres[12])+_PREC)/(1.0-sqrt(dres[12])+_PREC));	//Fishers z-transform
	s = distinv(t_dist, 1.0E+10, 1.0, (100-ci)/100.0, 2.0)/sqrt((double)(n-3));	
	dbl_to_str2(lbl, 80, ci_fmt, tanh(z-s), tanh(z+s));
	rep_DrawText(parent, x2, y, false, TXA_HCENTER, &txtdef1, lbl);
	mk_hr(parent, x, x+hrw, y+txtdef1.fSize*1.2);
	return y + linsp1*3.0;
}

static char *RegrDlg_Tmpl =
	"1,+,,DEFAULT,PUSHBUTTON,-1,148,10,45,12\n"
	".,.,,,PUSHBUTTON,-2,148,25,45,12\n"
	".,,4,ISPARENT | CHECKED,GROUP,0,0,0,0,0\n"
	"4,10,100,ISPARENT | CHECKED,SHEET,1,5,10,130,100\n"
	"10,,,CHECKED,CHECKPIN,0,5,0,12,8\n"
	"100,+,,,LTEXT,2,10,30,60,8\n"
	".,.,,,RANGEINPUT,-15,20,40,100,10\n"
	".,.,,,LTEXT,3,10,55,60,8\n"
	".,.,,,RANGEINPUT,-16,20,65,100,10\n"
	".,.,,,LTEXT,4,10,80,60,8\n"
	".,.,,,EDVAL1,5,74,80,25,10\n"
	".,.,,,LTEXT,-10,101,80,60,8\n"
	".,,,LASTOBJ,CHECKBOX,6,10,95,100,8";

void
rep_regression(GraphObj *parent, DataObj *data)
{
	TabSHEET tab1 = {0, 60, 10, "Regression Input"};
	double ci = 95.0;
	DlgInfo *RegrDlg;
	void *dyndata[] = {(void*)&tab1, (void*)"range for independent variable (x)",
		(void*)"range for dependent variable (y)", (void*)"confidence interval:",
		(void*)&ci, (void*)" include origin"};
	DlgRoot *Dlg;
	void *hDlg;
	int i, n, n1, rx, cx, ry, cy, res, align = 0;
	int x_dtype, y_dtype, nVals, nTxt, nTime;
	bool bContinue = false, bValid, bParZ;
	AccRange *rX = 0L, *rY = 0L;
	double *x = 0L, *y = 0L, **res_tab = 0L, c_x, c_y;
	double sx, sy, dx, dy, sxy, sxx, syy, sdy, df, t, ts, ty;
	double dres[14], ly[4];
	char *txt_obj, *x_desc=0L, *y_desc=0L;
	anyResult xRes, yRes;
	TextValue *x_tv, *y_tv;
	Graph *graph;
	Page *page;

	if(!parent || !data) return;
	if(!(RegrDlg = CompileDialog(RegrDlg_Tmpl, dyndata))) return;
	UseRangeMark(data, 1, TmpTxt, TmpTxt+100);
	if(!(Dlg = new DlgRoot(RegrDlg, data)))return;
	hDlg = CreateDlgWnd("Linear Regression", 50, 50, 420, 260, Dlg, 0x4L);
	do {
		LoopDlgWnd();
		res = Dlg->GetResult();
		switch (res) {
		case 0:
			if(bContinue) res = -1;
			else if(Dlg->GetCheck(10)) res = -1;
			break;
		case -1:
			bContinue = false;
			break;
		case 1:
			Dlg->GetValue(105, &ci);			bParZ = Dlg->GetCheck(107);
			if(rX) delete rX;					if(rY) delete rY;
			rX = rY = 0L;
			if(Dlg->GetText(101, TmpTxt, TMP_TXT_SIZE)) rX = new AccRange(TmpTxt);
			n = rX ? rX->CountItems() : 0;
			if(!n) {
				ErrorBox("Range not specified\nor not valid.");
				bContinue = true;
				res = -1;
				}
			if(n && Dlg->GetText(103, TmpTxt, TMP_TXT_SIZE) && (rY = new AccRange(TmpTxt))){
				if(n != rY->CountItems()) {
					ErrorBox("Both ranges must be given\nand must have the same size");
					bContinue = true;
					res = -1;
					}
				}
			}
		}while (res < 0);
	n1 = n;
	if(res == 1 && n >1 && rX && rY && (x = (double*)malloc(n*sizeof(double))) 
		&& (y = (double*)malloc(n*sizeof(double)))
		&& (res_tab = (double**)calloc(3, sizeof(double*)))
		&& (res_tab[0] = (double*) malloc(5*sizeof(double)))
		&& (res_tab[1] = (double*) malloc(5*sizeof(double)))
		&& (res_tab[2] = (double*) malloc(5*sizeof(double)))) {
		x_desc = rX->RangeDesc(data, 0);	y_desc = rY->RangeDesc(data, 0);
		//analyse data types
		x_dtype = y_dtype = 0;				x_tv = y_tv = 0L;
		if(rX->DataTypes(data, &nVals, &nTxt, &nTime)){
			if(!nVals && nTxt > 1 && nTxt > nTime) x_tv = new TextValue();
			else if(!nVals && nTime > 1 && nTime > nTxt) x_dtype = ET_DATETIME;
			}
		if(rY->DataTypes(data, &nVals, &nTxt, &nTime)){
			if(!nVals && nTxt > 1 && nTxt > nTime) y_tv = new TextValue();
			else if(!nVals && nTime > 1 && nTime > nTxt) y_dtype = ET_DATETIME;
			}
		rX->GetFirst(&cx, &rx);				rY->GetFirst(&cy, &ry);
		rep_init();
		dBounds.Xmin = dBounds.Ymin = HUGE_VAL;		dBounds.Xmax = dBounds.Ymax = -HUGE_VAL;
		//read data into x[] and y[]
		for(i = n = 0; i < n1 && rX->GetNext(&cx, &rx) && rY->GetNext(&cy, &ry); i++) {
			bValid = false;
			if(data->GetResult(&xRes, rx, cx, false) && data->GetResult(&yRes, ry, cy, false)) {
				bValid = true;
				if(x_tv) {
					if(xRes.type == ET_TEXT) dx = x_tv->GetValue(xRes.text);
					else bValid = false;
					}
				else if(x_dtype == ET_DATETIME) {
					if(xRes.type == ET_DATE || xRes.type == ET_TIME  || xRes.type == ET_DATETIME) dx = xRes.value;
					else bValid = false;
					}
				else {
					if(xRes.type == ET_VALUE) dx = xRes.value;
					else bValid = false;
					}
				if(y_tv) {
					if(yRes.type == ET_TEXT) dy = y_tv->GetValue(yRes.text);
					else bValid = false;
					}
				else if(y_dtype == ET_DATETIME) {
					if(yRes.type == ET_DATE || yRes.type == ET_TIME  || yRes.type == ET_DATETIME) dy = yRes.value;
					else bValid = false;
					}
				else {
					if(yRes.type == ET_VALUE) dy = yRes.value;
					else bValid = false;
					}
				}
			if(bValid){
				x[n] = dx;				y[n] = dy;
				if(dBounds.Xmin > x[n]) dBounds.Xmin = x[n];
				if(dBounds.Xmax < x[n]) dBounds.Xmax = x[n];
				if(dBounds.Ymin > y[n]) dBounds.Ymin = y[n];
				if(dBounds.Ymax < y[n]) dBounds.Ymax = y[n];
				n++;
				}
			}
		if(!bParZ) {
			for(i = 0, 	sx = sy = 0.0; i < n; i++) {
				sx += x[i];			sy += y[i];
				}
			dres[2] = sx /n;		dres[3] = sy/n;
			}
		else {
			dres[2] = sx = dres[3] = sy = 0.0;
			}
		sxy = sxx = syy = 0.0;
		for(i = 0; i < n; i++) {
			dx = x[i]-dres[2];	dy = y[i]-dres[3];
			sxx += (dx*dx);		syy += (dy*dy);		sxy += (dx*dy);
			}
		dres[0] = sxy / sxx;	dres[1] = dres[3] - dres[0] * dres[2];
		for(i = 0, sdy = 0.0; i < n; i++) {
			dy = y[i] - (dres[1] + x[i] *dres[0]);
			sdy += (dy * dy);
			}
		df = bParZ ? (n-1) : (n-2);		sdy = sdy/df;					dres[4] = sqrt(sdy/sxx);
		dres[5] = sxx/(n-1);			dres[6] = syy/(n-1);			dres[7] = sdy;
		dres[8] = sxy/sdy*sxy/sxx;		dres[9] = f_dist(dres[8], 1.0, df);
		t = distinv(t_dist, df, 1.0, (100.0-ci)/100.0, 2.0); 
		dres[10] = t * sqrt(dres[7]/sxx);
		dres[11] = t * sqrt(dres[7]*(dres[2]*dres[2]/sxx +1.0/(double)n));
		if (n && (graph = new Graph(parent, data, 0L, 0))) {
			if(txt_obj = mk_scatt(0, x, y, 0L, 0L, n, "Data", x_desc, y_desc)){
				OpenGraph(graph, 0L, (unsigned char*)txt_obj, false);
				free(txt_obj);
				if(LastOpenGO && LastOpenGO->Id >= GO_PLOT && LastOpenGO ->Id < GO_GRAPH) {
					((Plot*)LastOpenGO)->x_dtype = x_dtype;	((Plot*)LastOpenGO)->y_dtype = y_dtype;
					((Plot*)LastOpenGO)->x_tv = x_tv;		((Plot*)LastOpenGO)->y_tv = y_tv;
					}
				}
#ifdef USE_WIN_SECURE
			i = sprintf_s(TmpTxt, TMP_TXT_SIZE, "[1=Function]\nx1= %g\nx2= %g\nxstep= %g\nLine= %g 1 0x000000ff 0x0\n", 
				dBounds.Xmin, dBounds.Xmax, (dBounds.Xmax -dBounds.Xmin)/100.0, defs.GetSize(SIZE_DATA_LINE));
			i += sprintf_s(TmpTxt+i, TMP_TXT_SIZE-i, "f_xy=\"%g+x*%g\\n\"\n", dres[1], dres[0]);
			i += sprintf_s(TmpTxt+i, TMP_TXT_SIZE-i, "Desc=\"Regression\"\n");
			OpenGraph(graph, 0L, (unsigned char*)TmpTxt, false);
			i = sprintf_s(TmpTxt, TMP_TXT_SIZE, "[1=Function]\nx1= %g\nx2= %g\nxstep= %g\nLine= %g 1 0x000000ff 0x0\n", 
				dBounds.Xmin, dBounds.Xmax, (dBounds.Xmax -dBounds.Xmin)/100.0, defs.GetSize(SIZE_DATA_LINE)*0.5);
			i += sprintf_s(TmpTxt+i, TMP_TXT_SIZE-i, "f_xy=\"y=%g+x*%g;\\nts=sqrt((((x-(%g))^2)/%g+%g)*%g);\\ny=y+ts*%g\\n\"\n", 
				dres[1], dres[0], dres[2], sxx, (1.0/(double)n), dres[7], t);
			i += sprintf_s(TmpTxt+i, TMP_TXT_SIZE-i, "Desc=\"%g%% C.I.\"\n", ci);
			OpenGraph(graph, 0L, (unsigned char*)TmpTxt, false);
			i = sprintf_s(TmpTxt, TMP_TXT_SIZE, "[1=Function]\nx1= %g\nx2= %g\nxstep= %g\nLine= %g 1 0x000000ff 0x0\n", 
				dBounds.Xmin, dBounds.Xmax, (dBounds.Xmax -dBounds.Xmin)/100.0, defs.GetSize(SIZE_DATA_LINE)*0.5);
			i += sprintf_s(TmpTxt+i, TMP_TXT_SIZE-i, "f_xy=\"y=%g+x*%g;\\nts=sqrt((((x-(%g))^2)/%g+%g)*%g);\\ny=y-ts*%g\\n\"\n", 
				dres[1], dres[0], dres[2], sxx, (1.0/(double)n), dres[7], t);
			i += sprintf_s(TmpTxt+i, TMP_TXT_SIZE-i, "Desc=\"%g%% C.I.\"\n", ci);
			OpenGraph(graph, 0L, (unsigned char*)TmpTxt, false);
#else
			i = sprintf(TmpTxt, "[1=Function]\nx1= %g\nx2= %g\nxstep= %g\nLine= %g 1 0x000000ff 0x0\n", 
				dBounds.Xmin, dBounds.Xmax, (dBounds.Xmax -dBounds.Xmin)/100.0, defs.GetSize(SIZE_DATA_LINE));
			i += sprintf(TmpTxt+i, "f_xy=\"%g+x*%g\\n\"\n", dres[1], dres[0]);
			i += sprintf(TmpTxt+i, "Desc=\"Regression\"\n");
			OpenGraph(graph, 0L, (unsigned char*)TmpTxt, false);
			i = sprintf(TmpTxt, "[1=Function]\nx1= %g\nx2= %g\nxstep= %g\nLine= %g 1 0x000000ff 0x0\n", 
				dBounds.Xmin, dBounds.Xmax, (dBounds.Xmax -dBounds.Xmin)/100.0, defs.GetSize(SIZE_DATA_LINE)*0.5);
			i += sprintf(TmpTxt+i, "f_xy=\"y=%g+x*%g;\\nts=sqrt((((x-(%g))^2)/%g+%g)*%g);\\ny=y+ts*%g\\n\"\n", 
				dres[1], dres[0], dres[2], sxx, (1.0/(double)n), dres[7], t);
			i += sprintf(TmpTxt+i, "Desc=\"%g%% C.I.\"\n", ci);
			OpenGraph(graph, 0L, (unsigned char*)TmpTxt, false);
			i = sprintf(TmpTxt, "[1=Function]\nx1= %g\nx2= %g\nxstep= %g\nLine= %g 1 0x000000ff 0x0\n", 
				dBounds.Xmin, dBounds.Xmax, (dBounds.Xmax -dBounds.Xmin)/100.0, defs.GetSize(SIZE_DATA_LINE)*0.5);
			i += sprintf(TmpTxt+i, "f_xy=\"y=%g+x*%g;\\nts=sqrt((((x-(%g))^2)/%g+%g)*%g);\\ny=y-ts*%g\\n\"\n", 
				dres[1], dres[0], dres[2], sxx, (1.0/(double)n), dres[7], t);
			i += sprintf(TmpTxt+i, "Desc=\"%g%% C.I.\"\n", ci);
			OpenGraph(graph, 0L, (unsigned char*)TmpTxt, false);
#endif
			ts = t * sqrt(dres[7]*((dBounds.Xmax-dres[2])*(dBounds.Xmax-dres[2])/sxx +1.0/(double)n));
			ty = dBounds.Xmax * dres[0] +dres[1];
			ly[0] = ty +ts;		ly[1] = ty -ts;
			ts = t * sqrt(dres[7]*((dBounds.Xmin-dres[2])*(dBounds.Xmin-dres[2])/sxx +1.0/(double)n));
			ty = dBounds.Xmin * dres[0] +dres[1];
			ly[2] = ty +ts;		ly[3] = ty -ts;
			for(i = 0; i < 4; i++) if(ly[i] > -HUGE_VAL && ly[i] < HUGE_VAL) {
				if(ly[i] < dBounds.Ymin) dBounds.Ymin = ly[i];
				if(ly[i] > dBounds.Ymax) dBounds.Ymax = ly[i];
				}
#ifdef USE_WIN_SECURE
			if(!bParZ) sprintf_s(TmpTxt, TMP_TXT_SIZE, "y = %g %c %g * x",dres[1],(dres[0] < 0.0 ? '-' : '+'), fabs(dres[0]));
			else sprintf_s(TmpTxt, TMP_TXT_SIZE, "y = %g * x", fabs(dres[0]));
#else
			if(!bParZ) sprintf(TmpTxt, "y = %g %c %g * x",dres[1],(dres[0] < 0.0 ? '-' : '+'), fabs(dres[0]));
			else sprintf(TmpTxt, "y = %g * x", fabs(dres[0]));
#endif
			rep_DrawText(graph, (graph->GetSize(SIZE_DRECT_LEFT) + graph->GetSize(SIZE_DRECT_RIGHT))/2.0,
				graph->GetSize(SIZE_DRECT_TOP)+txtdef1.fSize/2.0, true, TXA_HCENTER, &txtdef1, TmpTxt);
			page = new Page(parent, data);					page->Command(CMD_DROP_GRAPH, graph, 0L);
			graph->moveable = 0;
			graph->GRect.Xmin += (txtdef1.fSize*5.0);		graph->GRect.Xmax += (txtdef1.fSize*5.0);
			graph->GRect.Ymin += (txtdef1.fSize*10.0);		graph->GRect.Ymax += (txtdef1.fSize*10.0);
			mk_header(page, "<b>Linear Regression Analysis</b>", data);
			res_tab[0][0] = 1;						res_tab[1][0] = df;
			res_tab[2][0] = df+1.0;					res_tab[0][1] = sxy*sxy/sxx;
			res_tab[1][1] = syy-res_tab[0][1];		res_tab[2][1] = syy;
			res_tab[0][2] = res_tab[0][1];			res_tab[1][2] = res_tab[1][1]/df;
			res_tab[0][3] = dres[8];				res_tab[0][4] = dres[9];
			dres[12] = res_tab[0][1]/res_tab[2][1];
			c_y = graph->GetSize(SIZE_GRECT_BOTTOM)+txtdef2.fSize*3.0;
			c_x = graph->GRect.Xmin;
			c_y = mk_regr_summary(page, c_x, c_y, dres, ci, n);
			rep_DrawText(page, c_x, c_y, false, TXA_HLEFT, &txtdef1, "<b>Anova:</b>");
			c_y += txtdef1.fSize*1.5;			mk_table(page, c_x, c_y, 2, res_tab);
			parent->Command(CMD_DROP_GRAPH, page, 0L);
			}
		}
	CloseDlgWnd(hDlg);
	delete Dlg;				if(res_tab) {
		for(i = 0; i < 3; i++) if(res_tab[i]) free(res_tab[i]);
		free(res_tab);
		}
	if(x_desc) free(x_desc);	if(y_desc)free(y_desc);
	if(x) free(x);				if(y) free(y);
	if(rX) delete rX;			if(rY) delete rY;		free(RegrDlg);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Kendall's robust line-fit
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static char *RobLineDlg_Tmpl =
	"1,+,,DEFAULT,PUSHBUTTON,-1,148,10,45,12\n"
	".,.,,,PUSHBUTTON,-2,148,25,45,12\n"
	".,,4,ISPARENT | CHECKED,GROUP,0,0,0,0,0\n"
	"4,10,100,ISPARENT | CHECKED,SHEET,1,5,10,130,78\n"
	"10,,,CHECKED,CHECKPIN,0,5,0,12,8\n"
	"100,+,,,LTEXT,2,10,30,60,8\n"
	".,.,,,RANGEINPUT,-15,20,40,100,10\n"
	".,.,,,LTEXT,3,10,55,60,8\n"
	".,,,LASTOBJ,RANGEINPUT,-16,20,65,100,10";

void
rep_robustline(GraphObj *parent, DataObj *data)
{
	TabSHEET tab1 = {0, 90, 10, "Nonparametric Regression"};
	DlgInfo *RegrDlg;
	void *dyndata[] = {(void*)&tab1, (void*)"range for x-data", (void*)"range for y-data"};
	DlgRoot *Dlg;
	void *hDlg;
	int i, j, k, n, n1, rx, cx, ry, cy, res, align = 0;
	int x_dtype, y_dtype, nVals, nTxt, nTime;
	bool bContinue = false, bValid;
	AccRange *rX = 0L, *rY = 0L;
	double *x = 0L, *y = 0L, *a = 0L, *b = 0L, c_x, c_y;
	double dx, dy, slope, intercept;
	char *txt_obj, *x_desc=0L, *y_desc=0L;
	scaleINFO scale = {{0.0, 0.45}, {0.0, 0.45}, {0.0, 0.45}};
	anyResult xRes, yRes;
	TextValue *x_tv, *y_tv;
	Plot *plot;
	Graph *graph;
	Page *page;

	if(!parent || !data) return;
	if(!(RegrDlg = CompileDialog(RobLineDlg_Tmpl, dyndata))) return;
	UseRangeMark(data, 1, TmpTxt, TmpTxt+100);
	if(!(Dlg = new DlgRoot(RegrDlg, data)))return;
	hDlg = CreateDlgWnd("Kendall's robust line-fit", 50, 50, 420, 220, Dlg, 0x4L);
	do {
		LoopDlgWnd();
		res = Dlg->GetResult();
		switch (res) {
		case 0:
			if(bContinue) res = -1;
			else if(Dlg->GetCheck(10)) res = -1;
			break;
		case -1:
			bContinue = false;
			break;
		case 1:
			if(rX) delete rX;					if(rY) delete rY;
			rX = rY = 0L;
			if(Dlg->GetText(101, TmpTxt, TMP_TXT_SIZE)) rX = new AccRange(TmpTxt);
			n = rX ? rX->CountItems() : 0;
			if(!n) {
				ErrorBox("Range not specified\nor not valid.");
				bContinue = true;
				res = -1;
				}
			if(n && Dlg->GetText(103, TmpTxt, TMP_TXT_SIZE) && (rY = new AccRange(TmpTxt))){
				if(n != rY->CountItems()) {
					ErrorBox("Both ranges must be given\nand must have the same size");
					bContinue = true;
					res = -1;
					}
				}
			}
		}while (res < 0);
	for(n1 = n, i = k = 1; i < n; i++) k += i;
	if(res == 1 && n >1 && rX && rY && (x = (double*)malloc(n*sizeof(double))) 
		&& (y = (double*)malloc(n*sizeof(double)))
		&& (a = (double*)malloc(k*sizeof(double)))
		&& (b = (double*)malloc(k*sizeof(double)))){
		x_desc = rX->RangeDesc(data, 0);	y_desc = rY->RangeDesc(data, 0);
		//analyse data types
		x_dtype = y_dtype = 0;				x_tv = y_tv = 0L;
		if(rX->DataTypes(data, &nVals, &nTxt, &nTime)){
			if(!nVals && nTxt > 1 && nTxt > nTime) x_tv = new TextValue();
			else if(!nVals && nTime > 1 && nTime > nTxt) x_dtype = ET_DATETIME;
			}
		if(rY->DataTypes(data, &nVals, &nTxt, &nTime)){
			if(!nVals && nTxt > 1 && nTxt > nTime) y_tv = new TextValue();
			else if(!nVals && nTime > 1 && nTime > nTxt) y_dtype = ET_DATETIME;
			}
		rX->GetFirst(&cx, &rx);				rY->GetFirst(&cy, &ry);
		rep_init();
		dBounds.Xmin = dBounds.Ymin = HUGE_VAL;		dBounds.Xmax = dBounds.Ymax = -HUGE_VAL;
		//read data into x[] and y[]
		for(i = n = 0; i < n1 && rX->GetNext(&cx, &rx) && rY->GetNext(&cy, &ry); i++) {
			bValid = false;
			if(data->GetResult(&xRes, rx, cx, false) && data->GetResult(&yRes, ry, cy, false)) {
				bValid = true;
				if(x_tv) {
					if(xRes.type == ET_TEXT) dx = x_tv->GetValue(xRes.text);
					else bValid = false;
					}
				else if(x_dtype == ET_DATETIME) {
					if(xRes.type == ET_DATE || xRes.type == ET_TIME  || xRes.type == ET_DATETIME) dx = xRes.value;
					else bValid = false;
					}
				else {
					if(xRes.type == ET_VALUE) dx = xRes.value;
					else bValid = false;
					}
				if(y_tv) {
					if(yRes.type == ET_TEXT) dy = y_tv->GetValue(yRes.text);
					else bValid = false;
					}
				else if(y_dtype == ET_DATETIME) {
					if(yRes.type == ET_DATE || yRes.type == ET_TIME  || yRes.type == ET_DATETIME) dy = yRes.value;
					else bValid = false;
					}
				else {
					if(yRes.type == ET_VALUE) dy = yRes.value;
					else bValid = false;
					}
				}
			if(bValid){
				x[n] = dx;				y[n] = dy;
				if(dBounds.Xmin > x[n]) dBounds.Xmin = x[n];
				if(dBounds.Xmax < x[n]) dBounds.Xmax = x[n];
				if(dBounds.Ymin > y[n]) dBounds.Ymin = y[n];
				if(dBounds.Ymax < y[n]) dBounds.Ymax = y[n];
				n++;
				}
			}
		SortArray2(n, x, y);
		for(i = k = 0; i < (n-1); i++) for(j = i+1; j < n; j++) {
			if(x[i] != x[j]) {
				b[k] = (y[j] - y[i])/(x[j] - x[i]);
				a[k] = y[i] - b[k]*x[i];	k++;
				}
			}
		d_quartile(k, b, 0L, &slope, 0L);		d_quartile(k, a, 0L, &intercept, 0L);
		slope = slope;		intercept = intercept;
		if (n && (graph = new Graph(parent, data, 0L, 0))) {
			if(txt_obj = mk_scatt(0, x, y, 0L, 0L, n, "Data", x_desc, y_desc)){
				OpenGraph(graph, 0L, (unsigned char*)txt_obj, false);
				free(txt_obj);
				if(LastOpenGO && LastOpenGO->Id >= GO_PLOT && LastOpenGO ->Id < GO_GRAPH) {
					((Plot*)LastOpenGO)->x_dtype = x_dtype;	((Plot*)LastOpenGO)->y_dtype = y_dtype;
					((Plot*)LastOpenGO)->x_tv = x_tv;		((Plot*)LastOpenGO)->y_tv = y_tv;
					}
				}
#ifdef USE_WIN_SECURE
			i = sprintf_s(TmpTxt, TMP_TXT_SIZE, "[1=Function]\nx1= %g\nx2= %g\nxstep= %g\nLine= %g 1 0x000000ff 0x0\n", 
				dBounds.Xmin, dBounds.Xmax, (dBounds.Xmax -dBounds.Xmin)/100.0, defs.GetSize(SIZE_DATA_LINE));
			i += sprintf_s(TmpTxt+i, TMP_TXT_SIZE-i, "f_xy=\"%g+x*%g\\n\"\n", intercept, slope);
			i += sprintf_s(TmpTxt+i, TMP_TXT_SIZE-i, "Desc=\"Fitted Line\"\n");
			OpenGraph(graph, 0L, (unsigned char*)TmpTxt, false);
			sprintf_s(TmpTxt, TMP_TXT_SIZE, "y = %g %c %g * x",intercept,(slope < 0.0 ? '-' : '+'), fabs(slope));
#else
			i = sprintf(TmpTxt, "[1=Function]\nx1= %g\nx2= %g\nxstep= %g\nLine= %g 1 0x000000ff 0x0\n", 
				dBounds.Xmin, dBounds.Xmax, (dBounds.Xmax -dBounds.Xmin)/100.0, defs.GetSize(SIZE_DATA_LINE));
			i += sprintf(TmpTxt+i, "f_xy=\"%g+x*%g\\n\"\n", intercept, slope);
			i += sprintf(TmpTxt+i, "Desc=\"Fitted Line\"\n");
			OpenGraph(graph, 0L, (unsigned char*)TmpTxt, false);
			sprintf(TmpTxt, "y = %g %c %g * x", intercept, (slope < 0.0 ? '-' : '+'), fabs(slope));
#endif
			rep_DrawText(graph, (graph->GetSize(SIZE_DRECT_LEFT) + graph->GetSize(SIZE_DRECT_RIGHT))/2.0,
				graph->GetSize(SIZE_DRECT_TOP)+txtdef1.fSize/2.0, true, TXA_HCENTER, &txtdef1, TmpTxt);
			page = new Page(parent, data);					page->Command(CMD_DROP_GRAPH, graph, 0L);
			graph->moveable = 0;
			graph->GRect.Xmin += (txtdef1.fSize*5.0);		graph->GRect.Xmax += (txtdef1.fSize*5.0);
			graph->GRect.Ymin += (txtdef1.fSize*9.0);		graph->GRect.Ymax += (txtdef1.fSize*9.0);
			mk_header(page, "<b>Kendall's Robust Line-Fit</b>", data);
			c_y = graph->GetSize(SIZE_GRECT_BOTTOM)+txtdef2.fSize*2.0;
			c_x = graph->GRect.Xmin;
			j = (int)isqr(k);				i = (int)((double)k/10.0);
			if(j < 8) j = 8;
			else if (j >40) j = 40;
			scale.sx.fx = (graph->GRect.Xmin + graph->GRect.Xmax)/2.0;
			scale.sy.fx = c_y;
			graph = new Graph(parent, data, 0L, 0);
			if(plot = new FreqDist(graph, data, b+(i>>1), k-i, j)){
				if(plot->x_info = (char*)malloc(30)) rlp_strcpy(plot->x_info, 30, "90% of all slopes");
				if(plot->y_info = (char*)malloc(30)) rlp_strcpy(plot->y_info, 30, "No. of observations");
				if(!(graph->Command(CMD_DROP_PLOT, plot, 0L))) delete(plot);
				}
			graph->Command(CMD_SCALE, &scale, 0L);
			page->Command(CMD_DROP_GRAPH, graph, 0L);
			mk_median_report(page, c_x, c_y, b, k, .95, "Slope");
			scale.sy.fx = c_y = graph->GRect.Ymax + txtdef1.fSize;
			graph = new Graph(parent, data, 0L, 0);
			if(plot = new FreqDist(graph, data, a+(i>>1), k-i, j)){
				if(plot->x_info = (char*)malloc(30)) rlp_strcpy(plot->x_info, 30, "90% of all intercepts");
				if(plot->y_info = (char*)malloc(30)) rlp_strcpy(plot->y_info, 30, "No. of observations");
				if(!(graph->Command(CMD_DROP_PLOT, plot, 0L))) delete(plot);
				}
			graph->Command(CMD_SCALE, &scale, 0L);
			page->Command(CMD_DROP_GRAPH, graph, 0L);
			c_y = mk_median_report(page, c_x, c_y, a, k, .95, "Intercept");
			parent->Command(CMD_DROP_GRAPH, page, 0L);
			}
		}
	CloseDlgWnd(hDlg);
	delete Dlg;
	if(x_desc) free(x_desc);	if(y_desc)free(y_desc);
	if(x) free(x);				if(y) free(y);
	if(a) free(a);				if(b) free(b);
	if(rX) delete rX;			if(rY) delete rY;		free(RegrDlg);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Correlation reports
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static char *RepCorrel_DlgTmpl =
		"1,2,,DEFAULT,PUSHBUTTON,-1,158,10,45,12\n"
		"2,3,,,PUSHBUTTON,-2,158,25,45,12\n"
		"3,,10,ISPARENT | CHECKED,GROUP,0,0,0,0,0\n"
		"10,11,152,ISPARENT | CHECKED, SHEET,1,5,10,140,70\n"
		"11,20,200,ISPARENT | TOUCHEXIT,SHEET,2,5,10,140,70\n"
		"20,21,,CHECKED,CHECKPIN,0,5,0,12,8\n"
		"21,22,,CHECKED,CHECKBOX,7,25,85,130,9\n"
		"22,23,,,LTEXT,8,35,95,50,9\n"
		"23,24,,,EDVAL1,9,87,95,30,10\n"
		"24,,,,LTEXT,-10,120,95,10,9\n"
		"152,153,,ISPARENT | CHECKED,GROUPBOX,3,12,30,128,45\n"
		"153,154,,,LTEXT,0,25,35,60,8\n"
		"154,155,,,RANGEINPUT,0,25,45,100,10\n"
		"155,156,0,,PUSHBUTTON,-8,95,57,30,12\n"
		"156,,,,PUSHBUTTON,-9,60,57,35,12\n"
		"200,201,,TOUCHEXIT,RADIO1,4,25,30,60,8\n"
		"201,202,,TOUCHEXIT,RADIO1,5,25,45,60,8\n"
		"202,,,TOUCHEXIT,RADIO1,6,25,60,60,8\n"
		"300,,,LASTOBJ,NONE,0,0,0,0,0";
static int use_corr = 2;
static double use_ci = 95.0;

void rep_correl(GraphObj *parent, DataObj *data, int style)
{
	TabSHEET tab1 = {0, 25, 10, "Data"};
	TabSHEET tab2 = {25, 57, 10, "Method"};
	
	void *dyndata[] = {(void*)&tab1, (void*)&tab2, 
		(void*)" select one range for every variable ", (void*)" Pearsons product moment",
		(void*)" Spearmans rank correlation", (void*)" Kendalls Tau", (void*)" highlight significant correlations,",
		(void*)"sigificance level", (void*)&use_ci};
	DlgInfo *CorrelDlg;
	DlgRoot *Dlg;
	void *hDlg;
	int i, j, res, nr, currYR = 0, maxYR = 0, ny, corr;
	int r1, c1, r2, c2, n, cb;
	bool updateYR = true, bContinue = false, bMtab = false, bHiLite;
	double lmarg, line_inc, *v1, *v2, val1, val2, cx, cy, r,dn, p, sf, ra[20], cl;
	char **rd = 0L, *txt_obj, *info1, *info2;
	scaleINFO scale = {{0.0, 0.14}, {0.0, 0.14}, {0.0, 0.14}};
	AccRange *rV1 = 0L, *rV2 = 0L;
	TextDEF mtext;
	Plot *plot;
	Graph *graph;
	Page *page;

	if(!parent || !data) return;
	info1 = info2 = 0L;
	if(!UseRangeMark(data, 2, TmpTxt, TmpTxt+100, TmpTxt+200, TmpTxt+300, TmpTxt+400,
		TmpTxt+500, TmpTxt+600, TmpTxt+700, TmpTxt+800, TmpTxt+900, TmpTxt+1000)) return;
	if(!(CorrelDlg = CompileDialog(RepCorrel_DlgTmpl, dyndata))) return;
	if(TmpTxt[0] && TmpTxt[100] && (rd = (char**)calloc(12, sizeof(char*)))) {
		for(i=j=0; i <= 1000; i +=100) if(TmpTxt[i]) 
			rd[j++] = (char*)memdup(TmpTxt+i, ((int)strlen(TmpTxt+i))+2, 0);	 maxYR = j-1;
		}
	if(!rd && !(rd = (char**)calloc(1, sizeof(char*))))return;
	if(!(Dlg = new DlgRoot(CorrelDlg, data))) return;
	if(rd && rd[currYR] &&  *(rd[currYR])) Dlg->SetText(154, rd[currYR]);
	switch(use_corr) {
	case 1:			Dlg->SetCheck(200, 0L, true);			corr = 1;			break;
	default:		Dlg->SetCheck(201, 0L, true);			corr = 2;			break;
	case 3:			Dlg->SetCheck(202, 0L, true);			corr = 3;			break;
		}
	hDlg = CreateDlgWnd(style? (char*)"Create Tiled Correlation Plots" : 
		(char*)"Create a Correlation Matrix", 50, 50, 420, 252, Dlg, 0x4L);
	do {
		if(updateYR) {
			if(currYR >0) Dlg->ShowItem(156, true);
			else Dlg->ShowItem(156, false);
#ifdef USE_WIN_SECURE
			sprintf_s(TmpTxt, TMP_TXT_SIZE, "variable # %d/%d", currYR+1, maxYR+1);
#else
			sprintf(TmpTxt,"variable # %d/%d", currYR+1, maxYR+1);
#endif
			Dlg->SetText(153, TmpTxt);
			updateYR = false;
			}
		LoopDlgWnd();
		res = Dlg->GetResult();
		switch(res) {
		case 0:
			if(bContinue || Dlg->GetCheck(20)) res = -1;
			break;
		case -1:
			bContinue = false;
			break;
		case 11:					//select correlation method
			bMtab = true;	res =-1;	break;
		case 200:					//select pearson
		case 201:					//select spearman
		case 202:					//select kendall
			corr = res-199;		res =-1;	break;
		case 1:
			if(!bMtab) {
				Dlg->SetCheck(11, 0L, true);
				bMtab = true;	res = -1;	
				break;
				}
		case 155:		case 156:
			res = com_StackDlg(res, Dlg, 0L, 0L, &rd, &currYR,
				&rV1, &bContinue, &ny, &maxYR, &updateYR);
			break;
			}
		}while (res < 0);

	if(res ==1) {
		if(bHiLite = Dlg->GetCheck(21)) {
			Dlg->GetValue(23, &use_ci);		cl = 1.0 - use_ci/100.0;
			}
		maxYR++;	rep_init();		page = new Page(parent, data);
		if(rV1) delete rV1;			rV1 = 0L;		use_corr = corr;
		switch(corr) {
		case 1:
			mk_header(page, "<b>Pearsons product moment correlations</b>", data);
			break;
		case 2:
			mk_header(page, "<b>Spearmans rank correlations</b>", data);
			break;
		case 3:
			mk_header(page, "<b>Kendalls non parametric correlations</b>", data);
			break;
		default:
			mk_header(page, "<b>### Correlation Error ###</b>", data);
			break;
			}
		memcpy(&mtext, &txtdef1, sizeof(TextDEF));
		if(style == 0) {
			lmarg = txtdef1.fSize*12.0;
			cy = txtdef1.fSize*13.0;	cx = txtdef1.fSize*6.0;
			line_inc = linsp1;
			sf = (page->GetSize(SIZE_GRECT_RIGHT)-lmarg-linsp1)/(cx *(double)maxYR);
			if(sf< 1.0) {
				cx *= sf;		line_inc *= sf;		lmarg *= sf;
				mtext.fSize *= sf;		mtext.iSize = 0;
				}
			}
		else {
			lmarg = txtdef1.fSize*8.0;
			cy = txtdef1.fSize*13.0;
			cx = (page->GetSize(SIZE_GRECT_RIGHT)-txtdef1.fSize*3.0-lmarg)/maxYR;
			switch(defs.cUnits) {
			default:
				scale.sx.fy = scale.sy.fy = scale.sz.fy = cx/165.0;			break;
			case 1:
				scale.sx.fy = scale.sy.fy = scale.sz.fy = cx/16.50;			break;
			case 2:
				scale.sx.fy = scale.sy.fy = scale.sz.fy = cx/6.49606;		break;
				}
			line_inc = cx/1.44;
			}
		for(nr = maxYR, i = 0; i < nr; i++) for(j = 0; j < nr; j++) {
			if(i == 0 &&(rV1 = new AccRange(rd[j]))) {	//first row
				if(info1 = rV1->RangeDesc(data, style == 0 || nr > 5 ? 1 : 2)) {
					if(style == 0)
						rep_DrawText(page, lmarg+cx*j, cy-txtdef1.fSize*2.0, false, TXA_HCENTER, &mtext, info1);
					else if(style == 1)
						rep_DrawText(page, lmarg+cx*j+cx/2.0, cy-txtdef1.fSize*2.0, false, TXA_HCENTER, &mtext, info1);
					free(info1);	info1 = 0L;
					}
				delete rV1;			rV1 = 0L;
				}
			if(j == 0 &&(rV1 = new AccRange(rd[i]))) {	//first column
				if(info1 = rV1->RangeDesc(data, 1)) {
					if(style == 0) 
						rep_DrawText(page, lmarg-cx/2.0-txtdef1.fSize, cy+line_inc, false, TXA_HRIGHT, &mtext, info1);
					else if(style == 1)
						rep_DrawText(page, lmarg-txtdef1.fSize, cy+line_inc/2.0-mtext.fSize/2.0, false, TXA_HRIGHT, &mtext, info1);
					free(info1);	info1 = 0L;
					}
				delete rV1;			rV1 = 0L;
				}
			if(i == j) {					//self correlation: do something else ...
				if(style == 0) {			//correlation matrix
					rep_DrawText(page, lmarg+cx*j, cy+line_inc, false, TXA_HCENTER, &mtext, "---");
					}
				else if(style = 1) {		//tiled plots
					graph = new Graph(parent, data, 0L, 0);
					scale.sx.fx = lmarg+cx*j;			scale.sy.fx = cy;
					if(plot = new FreqDist(graph, data, rd[i], true)){
						if(rV1 = new AccRange(rd[i])){
							plot->x_info = rV1->RangeDesc(data, 2);
							delete rV1;		rV1 = 0L;
							}
						if(!(graph->Command(CMD_DROP_PLOT, plot, 0L))) delete(plot);
						}
					graph->Command(CMD_SCALE, &scale, 0L);
					page->Command(CMD_DROP_GRAPH, graph, 0L);
					}
				}
			else {
				rV1 = new AccRange(rd[i]);	rV2 = new AccRange(rd[j]);
				v1 = (double*)malloc((rV1->CountItems()+1) * sizeof(double));
				v2 = (double*)malloc((rV2->CountItems()+1) * sizeof(double));
				dBounds.Xmin = dBounds.Ymin = HUGE_VAL;		dBounds.Xmax = dBounds.Ymax = -HUGE_VAL;
				rV1->GetFirst(&c1, &r1);	rV2->GetFirst(&c2, &r2);
				//copy values into arrays
				for(n = 0; rV1->GetNext(&c1, &r1) && rV2->GetNext(&c2, &r2); ) {
					if(data->GetValue(r1, c1, &val1) && data->GetValue(r2, c2, &val2)) {
						if(dBounds.Xmin > val1) dBounds.Xmin = val1;
						if(dBounds.Xmax < val1) dBounds.Xmax = val1;
						if(dBounds.Ymin > val2) dBounds.Ymin = val2;
						if(dBounds.Ymax < val2) dBounds.Ymax = val2;
						v1[n] = val1;	v2[n] = val2;	n++;
						}
					}
				//do correlation
				dn = n;			r = 0.0;
				if(n) switch(corr) {
				case 1:
					d_pearson(v1, v2, n, 0L, 0L, ra);
					r = ra[0];	p = ra[2];	dn = ra[3];
					break;
				case 2:
					d_spearman(v1, v2, n, 0L, 0L, ra);
					r = ra[3];	p = ra[4];	dn = ra[5];
					break;
				case 3:
					d_kendall(v1, v2, n, 0L, 0L, ra);
					r = ra[0];	p = ra[2];	dn = ra[3];
					break;
				default:
					r = 0.0;	dn = 0.0;	p = 1.0;			break;
					}
				//process result
				if(dn > 1.0 && style == 0) {			//correlation matrix
					if(bHiLite && p < cl && (txt_obj = mk_rect(lmarg+cx*j-cx/2.1, cy-line_inc/4.0, lmarg+cx*j+cx/2.1, 
						cy+line_inc*3.25, 0x0000ffffL, 0x0080ffffL))) {
						OpenGraph(page, 0L, (unsigned char*)txt_obj, false);
						free(txt_obj);
						}
					dbl_to_str1(TmpTxt, 80, "%g", r);
					rep_DrawText(page, lmarg+cx*j, cy, false, TXA_HCENTER, &mtext, TmpTxt);
					dbl_to_str1(TmpTxt, 80, "n = %g", dn);
					rep_DrawText(page, lmarg+cx*j, cy+line_inc, false, TXA_HCENTER, &mtext, TmpTxt);
					dbl_to_str1(TmpTxt, 80, p < 0.001 ? (char*)"P < 0.0001" : (char*)"P = %.4lf", p);
					rep_DrawText(page, lmarg+cx*j, cy+line_inc*2.0, false, TXA_HCENTER, &mtext, TmpTxt);
					if(j == (nr-1)) cy += (line_inc*4.0);
					}
				else if(style == 0) {					//corr. matrix but no data
					if(j == (nr-1)) cy += (line_inc*4.0);
					}
				else if(style == 1) {		//tiled plots
					graph = new Graph(parent, data, 0L, 0);
					scale.sx.fx = lmarg+cx*j;			scale.sy.fx = cy;
					info1 = rV1->RangeDesc(data, 2);	info2 = rV2->RangeDesc(data, 2);
					if(txt_obj = mk_scatt(0, v1, v2, 0L, 0L, n, "Data", info1, info2)){
						OpenGraph(graph, 0L, (unsigned char*)txt_obj, false);
						free(txt_obj);
						}
					if(info1) free(info1);				if(info2) free(info2);
					info1 = info2 = 0L;
					if(bHiLite && p < cl) {
						graph->SetColor(COL_GRECT, 0x0000ffffL);	graph->SetColor(COL_DRECT, 0x00c0ffffL);
						}
					switch(corr) {
					case 1:
						cb = dbl_to_str2(TmpTxt, 80, "r = %.4lf, n = %g, ", r, dn);				break;
					case 2:
						cb = dbl_to_str2(TmpTxt, 80, "r<sub>S</sub> = %.4lf, n = %g, ", r, dn);	break;
					case 3:
						cb = dbl_to_str2(TmpTxt, 80, "r<sub>K</sub> = %.4lf, n = %g, ", r, dn);	break;
					default:
						TmpTxt[0] = 0;		cb = 0;		break;
						}
					dbl_to_str1(TmpTxt+cb, 80, p < 0.001 ? (char*)"P < 0.0001" : (char*)"P = %.4lf", p);
					rep_DrawText(graph, graph->GetSize(SIZE_DRECT_LEFT)+txtdef1.fSize, 
						graph->GetSize(SIZE_DRECT_TOP)+txtdef1.fSize, false, TXA_HLEFT, &txtdef1, TmpTxt);
					if(LastOpenGO)LastOpenGO->SetColor(COL_TEXT, 0x00cb0000L);
					graph->Command(CMD_SCALE, &scale, 0L);
					if(dn > 1.0) page->Command(CMD_DROP_GRAPH, graph, 0L);
					if(j == (nr-1)) cy += line_inc;
					}
				free(v1);		free(v2);
				if(rV1)delete rV1;		if(rV2)delete rV2;		rV1 = rV2 = 0L;
				}
			}
		parent->Command(CMD_DROP_GRAPH, page, 0L);
		}
	CloseDlgWnd(hDlg);
	delete Dlg;
	if(rd) {
		for (i = 0; i < maxYR; i++)	if(rd[i]) free(rd[i]);
		free(rd);
		}
	if(rV2) delete rV2;		if(rV1) delete rV1;		free(CorrelDlg);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 2x2 table
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
static char *twDlg_Tmpl =
	"1,2,100,ISPARENT | CHECKED,GROUP,0,0,0,0,0\n"
	"2,3,400,ISPARENT | CHECKED,GROUP,0,0,0,0,0\n"
	"3,4,600,ISPARENT | CHECKED,GROUP,0,0,0,0,0\n"
	"4,5,,DEFAULT,PUSHBUTTON,-1,168,10,45,12\n"
	"5,,,,PUSHBUTTON,2,168,25,45,12\n"
	"100,101,,,CTEXT,1,35,10,40,8\n"
	"101,102,,,EDTEXT,0,35,20,40,10\n"
	"102,103,,,EDTEXT,0,77,20,40,10\n"
	"103,104,,,EDTEXT,0,35,32,40,10\n"
	"104,105,,,EDTEXT,0,77,32,40,10\n"
	"105,106,,,LTEXT,3,10,20,40,8\n"
	"106,107,,,LTEXT,4,10,32,40,8\n"
	"107,,,,CTEXT,5,77,10,40,8\n"
	"400,401,,,EDTEXT,0,119,20,40,10\n"
	"401,402,,,EDTEXT,0,119,32,40,10\n"
	"402,403,,,EDTEXT,0,35,44,40,10\n"
	"403,404,,,EDTEXT,0,77,44,40,10\n"
	"404,405,,,EDTEXT,0,119,44,40,10\n"
	"405,406,,,CTEXT,6,119,10,40,8\n"
	"406,407,,,LTEXT,7,10,44,40,8\n"
	"407,408,,,LTEXT,2,35,59,40,8\n"
	"408,409,,,LTEXT,2,35,69,40,8\n"
	"409,410,,,LTEXT,8,119,59,60,8\n"
	"410,,,,LTEXT,0,119,69,60,8\n"
	"600,601,,DEFAULT,PUSHBUTTON,-1,128,10,45,12\n"
	"601,,,LASTOBJ,PUSHBUTTON,-2,128,25,45,12";

void rep_twowaytable(GraphObj *parent, DataObj *data)
{
	DlgInfo *twDlg;
	void *dyndata[] = {(void*)"Group A", (void*)"Close", (void*)"Case 1", (void*)"Case 2",
		(void*)"Group B", (void*)"A + B", (void*)"C1+C2", (void*)"Fisher's exact:"};
	DlgRoot *Dlg;
	void *hDlg;
	int i, r, c, level, res, wcc;
	int v_idx[] = {101,102,400,103,104,401,402,403,404};
	double tmp, v[9], chi2, p, dn, pf, pfa[128];
	char *rng;
	AccRange *ar;

	if(!parent || !data) return;
	if(!(twDlg = CompileDialog(twDlg_Tmpl, dyndata))) return;
	if(!(Dlg = new DlgRoot(twDlg, data)))return;
	for(i = 400; i < 405; i++) Dlg->Activate(i, false);
	if(data->Command(CMD_GETMARK, &rng, 0L) && rng && rng[0] && (ar = new AccRange(rng)) && ar->GetFirst(&c, &r)) {
		for(i = 0; i < 4 && ar->GetNext(&c, &r); ) {
			if(data->GetValue(r, c, &tmp)) {
				Dlg->SetValue(101+i, tmp);				i++;
				}
			}
		delete ar;
		if(i == 4) Dlg->ItemCmd(600, CMD_ENDDIALOG, 0L);
		}
	level = wcc = 0;
	Dlg->ShowItem(2, false);		Dlg->ShowItem(4, false);	
	Dlg->ShowItem(5, false);	
	hDlg = CreateDlgWnd("2x2 Table", 50, 50, 450, 200, Dlg, 0x4L);
	ResizeDlgWnd(hDlg, 370, 150);
	do {
		LoopDlgWnd();
		res = Dlg->GetResult();
		switch(res) {
			case 0:
				res = -1;
				break;
			case 600:			//level 0 OK
				Dlg->ShowItem(2, true);			Dlg->ShowItem(4, true);	
				Dlg->ShowItem(5, true);			Dlg->ShowItem(3, false);
				ResizeDlgWnd(hDlg, 450, 200);	Dlg->Command(CMD_REDRAW, 0L, 0L);
				level = 1;
			case 4:				//level 1 OK
				for(i = 0; i < 9; i++) {
					v[i] = 0.0;					Dlg->GetValue(v_idx[i], &v[i]);
					v[i] = fabs(floor(v[i]));
					}
				v[2] = v[0] + v[1];				v[5] = v[3] + v[4];
				v[6] = v[0] + v[3];				v[7] = v[1] + v[4];
				v[8] = v[6] + v[7];				chi2 = v[0]*v[4]-v[1]*v[3];
				for(i = wcc = 0; i < 9; i++) {
					dbl_to_str1(TmpTxt, TMP_TXT_SIZE, "%g", v[i]);
					Dlg->SetText(v_idx[i], TmpTxt);
					}
				if(v[8] < 128.0) do {
					pf = factrl((int)v[2])/factrl((int)v[0])*factrl((int)v[5])/factrl((int)v[1])
						*factrl((int)v[6])/factrl((int)v[3])*factrl((int)v[7])/factrl((int)v[4]);
					pf /= factrl((int)v[8]);
					pfa[wcc++] = pf;
					//worse case correction
					//RR Sokal & FJ Rohlf: Biometry, 3rd ed., pp. 734 ff.
					if((v[0]*v[4]- v[1]*v[3]) < 0.0) {
						v[0]-=1.0;	v[4]-=1.0;	v[1]+=1.0;	v[3]+=1.0;
						}
					else if((v[0]*v[4]- v[1]*v[3]) > 0.0) {
						v[0]+=1.0;	v[4]+=1.0;	v[1]-=1.0;	v[3]-=1.0;
						}
					else break;
					}while(v[0]>=0.0 && v[1]>=0.0 && v[3]>=0.0 && v[4]>=0.0 && wcc < 128);
				if(wcc){
					for(i = 1, pf = pfa[0]; i < wcc; i++){
						pf += pfa[i];
						}
					if(pf > 1.0) pf = 1.0;
					dbl_to_str1(TmpTxt, TMP_TXT_SIZE, "P(one sided) = %.4lf", pf);
					Dlg->SetText(410, pf >= 0.001 ? TmpTxt : (char*)"P < 0.001");
					}
				else Dlg->SetText(410, "- - -");
				dn = (v[2]*v[5]*v[6]*v[7]);
				chi2 = dn > 0.0  ? (chi2*chi2*v[8])/dn : 0.0;
				p = chi_dist(chi2, 1.0, 1.0);
				dbl_to_str1(TmpTxt, TMP_TXT_SIZE, "Chi2 = %g", chi2);
				Dlg->SetText(407, TmpTxt);
				if(p >= 0.001) {
					dbl_to_str1(TmpTxt, TMP_TXT_SIZE, "P = %g", p);
					Dlg->SetText(408, TmpTxt);
					}
				else Dlg->SetText(408, "P < 0.001");
				Dlg->Command(CMD_REDRAW, 0L, 0L);
				res= -1;
				break;
			}
		}while (res < 0);
	CloseDlgWnd(hDlg);
	delete Dlg;		free(twDlg);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// compare means / medians of two groups
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void rep_compmeans(GraphObj *parent, DataObj *data)
{
	TabSHEET tab1 = {0, 42, 10, "Data Input"};
	double ci = 95.0;
	DlgInfo *MeanDlg;
	void *dyndata[] = {(void*)&tab1, (void*)"range for first variable",
		(void*)"range for second variable", (void*)"confidence interval:",
		(void*)&ci, (void*)" "};
	char *ttest[] = {"Student's t = %g", "P = %g", "P(corr.) = %g"};
	char *utest[] = {"Mann-Whitney U = %g", "z = %g", "P = %g", "z(corr.) = %g", "P(corr.) = %g"};
	char g1_nam[30], g2_nam[30], *c_name;
	DlgRoot *Dlg;
	void *hDlg;
	int i, j, res, n1, n2, r, c, *ny;
	bool bContinue = false;
	double *d1, *d2, dtmp, *rs, cx, cy, min1,max1, min2, max2;
	scaleINFO scale = {{0.0, 0.9}, {0.0, 0.9}, {0.0, 0.9}};
	char *txt_obj;
	anyResult ares;
	AccRange *rD;
	Graph *graph;
	Page *page;

	if(!parent || !data) return;
	if(!(MeanDlg = CompileDialog(RegrDlg_Tmpl, dyndata))) return;
	UseRangeMark(data, 1, TmpTxt, TmpTxt+100);
	if(!(Dlg = new DlgRoot(MeanDlg, data)))return;
	Dlg->ShowItem(107, false);
	d1 = d2 = 0L;
	hDlg = CreateDlgWnd("Compare Means", 50, 50, 420, 260, Dlg, 0x4L);
	do {
		LoopDlgWnd();
		res = Dlg->GetResult();
		switch (res) {
		case 0:
			if(bContinue) res = -1;
			else if(Dlg->GetCheck(10)) res = -1;
			break;
		case -1:
			bContinue = false;
			break;
		case 1:
			if(d1) free(d1);		if(d2) free(d2);		d1 = d2 = 0L;
			min1 = min2 = dBounds.Ymin = HUGE_VAL;		max1 = max2 = dBounds.Ymax = -HUGE_VAL;
			if(Dlg->GetText(101,TmpTxt,TMP_TXT_SIZE)&&(rD=new AccRange(TmpTxt))&&(n1=rD->CountItems())&&(d1=(double*)malloc(n1*sizeof(double)))){
				if(c_name = rD->RangeDesc(data, 2)) {
					rlp_strcpy(g1_nam, 30, c_name);		g1_nam[0] = toupper(g1_nam[0]);
					free(c_name);
					}
				else rlp_strcpy(g1_nam, 30, "Group 1");
				for(n1 = 0, rD->GetFirst(&c, &r); rD->GetNext(&c, &r); ) {
					if(data->GetResult(&ares, r, c, false) && ares.type == ET_VALUE){
						if(dBounds.Ymin > ares.value) dBounds.Ymin = ares.value;
						if(dBounds.Ymax < ares.value) dBounds.Ymax = ares.value;
						if(min1 > ares.value) min1 = ares.value;		if(max1 < ares.value) max1 = ares.value;
						d1[n1++] = ares.value;
						}
					}
				delete rD;
				}
			if(Dlg->GetText(103,TmpTxt,TMP_TXT_SIZE)&&(rD=new AccRange(TmpTxt))&&(n2=rD->CountItems())&&(d2=(double*)malloc(n2*sizeof(double)))){
				if(c_name = rD->RangeDesc(data, 2)) {
					rlp_strcpy(g2_nam, 30, c_name);		g2_nam[0] = toupper(g2_nam[0]);
					free(c_name);
					}
				else rlp_strcpy(g2_nam, 30, "Group 2");
				for(n2 = 0, rD->GetFirst(&c, &r); rD->GetNext(&c, &r); ) {
					if(data->GetResult(&ares, r, c, false) && ares.type == ET_VALUE){
						if(dBounds.Ymin > ares.value) dBounds.Ymin = ares.value;
						if(dBounds.Ymax < ares.value) dBounds.Ymax = ares.value;
						if(min2 > ares.value) min2 = ares.value;		if(max2 < ares.value) max2 = ares.value;
						d2[n2++] = ares.value;
						}
					}
				delete rD;
				}
			if(g1_nam[0] && g2_nam[0] && 0==strcmp(g1_nam, g2_nam)) {
				rlp_strcpy(g1_nam, 30, "Group 1");		rlp_strcpy(g2_nam, 30, "Group 2");
				}
			if(!d1 || !d2 || n1 < 2 || n2 < 2) {
				InfoBox("Insufficient data to calculate means!");
				bContinue = true;
				res = -1;
				}
			Dlg->GetValue(105, &ci);
			break;
			}
		}while (res < 0);
	if(res == 1 && d1 && d2 && n1>1 && n2>1 && (rs = (double*)malloc(40*sizeof(double))) && (ny = (int*)malloc(2*sizeof(int)))) {
		dBounds.Xmin = 0.5;		rs[0] = 1.0;		dBounds.Xmax = 2.3;		rs[1] = 2.0;
		dtmp = d_variance(n1, d1, &rs[2], 0L);		rs[10] = sqrt(dtmp);
		dtmp = d_variance(n2, d2, &rs[3], 0L);		rs[11] = sqrt(dtmp);
		rs[12] = (double)n1;						rs[13] = (double)n2;
		rs[6] = rs[10]/sqrt(rs[12]);				rs[7] = rs[11]/sqrt(rs[13]);
		rs[4] = rs[2] - rs[6];						rs[5] = rs[3] - rs[7];
		rs[6] += rs[2];								rs[7] += rs[3];
		rs[8] = rs[2] - rs[10];						rs[9] = rs[3] - rs[11];
		rs[10] += rs[2];							rs[11] += rs[3];
		ny[0] = n1;									ny[1] = n2;
		rep_init();									page = new Page(parent, data);
		ci /= 100.0;
		mk_header(page, "<b>Compare Means of Two Groups</b>", data);
		if((graph = new Graph(parent, data, 0L, 0)) && (txt_obj = mk_boxplot(0, rs, rs+2, rs+4, rs+6, rs+8, rs+10,
				ny, 2,"Mean","Std. Err.","Std. Dev."))){
			scale.sx.fx = (txtdef1.fSize*5.0);			scale.sy.fx = (txtdef1.fSize*10.0);
			graph->GRect.Xmax = defs.GetSize(SIZE_GRECT_BOTTOM);
			graph->DRect.Xmin *= 0.8;					graph->moveable = 0;
			graph->DRect.Xmax = graph->GRect.Xmax - (txtdef1.fSize*2.0);
			OpenGraph(graph, 0L, (unsigned char*)txt_obj, false);
			if(LastOpenGO && LastOpenGO->Id == GO_BOXPLOT) {
				if(((BoxPlot*)LastOpenGO)->x_tv = new TextValue()){
					((BoxPlot*)LastOpenGO)->x_tv->GetValue(g1_nam);
					((BoxPlot*)LastOpenGO)->x_tv->GetValue(g2_nam);
					}
				}
			free(txt_obj);						graph->Command(CMD_SCALE, &scale, 0L);
			cx = graph->GetSize(SIZE_GRECT_RIGHT)+txtdef1.fSize*3.0;
			cy = mk_mean_report(page, cx, graph->GetSize(SIZE_GRECT_TOP), d1, n1, ci, g1_nam);
			cy = mk_mean_report(page, cx, cy + txtdef1.fSize, d2, n2, ci, g2_nam);
			cy += linsp1;
			rep_DrawText(page, graph->GetSize(SIZE_GRECT_RIGHT)+txtdef1.fSize*3.0, cy, false, TXA_HLEFT, &txtdef1, "<b>t-Test:</b>");
			cy += linsp1;						d_ttest(d1, d2, n1, n2, 0L, 0L, rs+15);
			for(i = 0; i < 3; i++) {
				switch(i) {
					case 0:			dtmp = rs[24];			break;
					case 1:			dtmp = rs[21];			break;
					case 2:			dtmp = rs[23];			break;
					}
#ifdef USE_WIN_SECURE
				j = sprintf_s(TmpTxt, 80, ttest[i], dtmp);
#else
				j = sprintf(TmpTxt, ttest[i], dtmp);
#endif
				if(i && dtmp < 0.0001) {
					while(TmpTxt[j] != '=' && j) j--;
					rlp_strcpy(TmpTxt+j, 10, "< 0.0001");
					}
				rep_DrawText(page, cx+(txtdef1.fSize*3.0), cy, false, TXA_HLEFT, &txtdef1, TmpTxt);
				cy += linsp1/1.2;
				}
			page->Command(CMD_DROP_GRAPH, graph, 0L);
			}
		d_quartile(n1, d1, &rs[6], &rs[2], &rs[4]);
		d_quartile(n2, d2, &rs[7], &rs[3], &rs[5]);
		rs[8] = min1;	rs[9] = min2;	rs[10] = max1;	rs[11] = max2;
		cy = graph->GetSize(SIZE_GRECT_BOTTOM)+txtdef2.fSize*3.0;
		if((graph = new Graph(parent, data, 0L, 0)) && (txt_obj = mk_boxplot(0, rs, rs+2, rs+4, rs+6, rs+8, rs+10,
				ny, 2,"Median","25-75%","Min./Max."))){
			scale.sy.fx = cy;
			graph->GRect.Xmax = defs.GetSize(SIZE_GRECT_BOTTOM);
			graph->DRect.Xmin *= 0.8;					graph->moveable = 0;
			graph->DRect.Xmax = graph->GRect.Xmax - (txtdef1.fSize*2.0);
			OpenGraph(graph, 0L, (unsigned char*)txt_obj, false);
			if(LastOpenGO && LastOpenGO->Id == GO_BOXPLOT) {
				if(((BoxPlot*)LastOpenGO)->x_tv = new TextValue()){
					((BoxPlot*)LastOpenGO)->x_tv->GetValue(g1_nam);
					((BoxPlot*)LastOpenGO)->x_tv->GetValue(g2_nam);
					}
				}
			free(txt_obj);								graph->Command(CMD_SCALE, &scale, 0L);
			cy = mk_median_report(page, cx, graph->GetSize(SIZE_GRECT_TOP), d1, n1, .95, g1_nam);
			cy = mk_median_report(page, cx, cy + txtdef1.fSize, d2, n2, .95, g2_nam);
			cy += linsp1;
			rep_DrawText(page, graph->GetSize(SIZE_GRECT_RIGHT)+txtdef1.fSize*3.0, cy, false, TXA_HLEFT, &txtdef1, "<b>u-Test:</b>");
			cy += linsp1;			d_utest(d1, d2, n1, n2, 0L, 0L, rs+15);
			for(i = 0; i < 5; i++) {
				switch(i) {
					case 0:			dtmp = rs[17];			break;
					case 1:			dtmp = rs[18];			break;
					case 2:			dtmp = rs[21];			break;
					case 3:			dtmp = rs[22];			break;
					case 4:			dtmp = rs[23];			break;
					}
#ifdef USE_WIN_SECURE
				j = sprintf_s(TmpTxt, 80, utest[i], dtmp);
#else
				j = sprintf(TmpTxt, utest[i], dtmp);
#endif
				if(i && dtmp < 0.0001) {
					while(TmpTxt[j] != '=' && j) j--;
					rlp_strcpy(TmpTxt+j, 10, "< 0.0001");
					}
				rep_DrawText(page, cx+(txtdef1.fSize*3.0), cy, false, TXA_HLEFT, &txtdef1, TmpTxt);
				cy += linsp1/1.2;
				}
			page->Command(CMD_DROP_GRAPH, graph, 0L);
			}
		parent->Command(CMD_DROP_GRAPH, page, 0L);	
		free(rs);			free(ny);
		}
	CloseDlgWnd(hDlg);		delete Dlg;				free(MeanDlg);
	if(d1) free(d1);		if(d2) free(d2);
}
