//PlotObs.cpp, Copyright (c) 2001-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
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This modules contains code for the differnt Plot objects. Plots are
// graphic objects containing more objects, which represent the data.
// Several Plots may be contained in a Graph: Plots are the different layers
// of a Graph.
// Most part of this module has been moved here from rlplot.cpp of
// earlier versions. 
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#include "rlplot.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

extern char TmpTxt[];
extern Default defs;
extern int cPlots;
extern GraphObj *CurrGO, *TrackGO;			//Selected Graphic Objects
extern Axis **CurrAxes;						//axes of current graph
extern UndoObj Undo;

int AxisTempl3D = 0;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Plot::Plot(GraphObj *par, DataObj *d):GraphObj(par, d)
{
	int pos, nsize;

	Id = GO_PLOT;
	Bounds.Xmin = Bounds.Ymin = HUGE_VAL;	Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
	if(name = (char*)malloc((nsize = 20)*sizeof(char))){
		pos = rlp_strcpy(name, nsize, (char*)"Plot");
		add_int_to_buff(&name, &pos, &nsize, ++cPlots, true, 0);
		}
	use_xaxis = use_yaxis = use_zaxis = 0;	hidden = 0;
	x_info = y_info = z_info = data_desc = 0L;
	x_tv = y_tv = 0L;	x_dtype = y_dtype = z_dtype = 0;
}

double
Plot::GetSize(int select)
{
	switch(select){
	case SIZE_MINE:				return 0.0;
	//The Bounds values must be returned by every plot:
	//   they are necessary for scaling !
	case SIZE_BOUNDS_XMIN:		
		return parent ? parent->GetSize(SIZE_BOUNDS_XMIN) : Bounds.Xmin;
	case SIZE_BOUNDS_XMAX:		
		return parent ? parent->GetSize(SIZE_BOUNDS_XMAX) : Bounds.Xmax;
	case SIZE_BOUNDS_YMIN:		
		return parent ? parent->GetSize(SIZE_BOUNDS_YMIN) : Bounds.Ymin;	
	case SIZE_BOUNDS_YMAX:		
		return parent ? parent->GetSize(SIZE_BOUNDS_YMAX) : Bounds.Ymax;
	case SIZE_BARMINX:
	case SIZE_BARMINY:
		return 1.0f;
	default:
		return DefSize(select);
		}
}

DWORD
Plot::GetColor(int select)
{
	if(parent) return parent->GetColor(select);
	else return defs.Color(select);
}

void
Plot::CheckBounds(double x, double y)
{
	if(x < Bounds.Xmin) Bounds.Xmin = x;	if(x > Bounds.Xmax) Bounds.Xmax = x;
	if(y < Bounds.Ymin) Bounds.Ymin = y;	if(y > Bounds.Ymax) Bounds.Ymax = y;
}

bool
Plot::UseAxis(int idx)
{
	if(CurrAxes && CurrAxes[idx]) {
		switch(CurrAxes[idx]->type & 0xf) {
		case 1:									// x-axis
			Undo.ValInt(parent, &use_xaxis, 0L);
			use_xaxis = idx;			return true;
		case 2:									// y-axis
			Undo.ValInt(parent, &use_yaxis, 0L);
			use_yaxis = idx;			return true;
		case 3:									// z-axis
			Undo.ValInt(parent, &use_zaxis, 0L);
			use_zaxis = idx;			return true;
			}
		}
	return false;
}

void
Plot::ApplyAxes(anyOutput *o)
{
	if(!o || !CurrAxes || !parent) return;
	if(use_xaxis && CurrAxes[use_xaxis]) {
		o->UseAxis(CurrAxes[use_xaxis]->axis, CurrAxes[use_xaxis]->type & 0xf);
		}
	else use_xaxis = 0;
	if(use_yaxis && CurrAxes[use_yaxis]) {
		o->UseAxis(CurrAxes[use_yaxis]->axis, CurrAxes[use_yaxis]->type & 0xf);
		}
	else use_yaxis = 0;
	if(use_zaxis && CurrAxes[use_zaxis]) {
		o->UseAxis(CurrAxes[use_zaxis]->axis, CurrAxes[use_zaxis]->type & 0xf);
		}
	else use_zaxis = 0;
	return;
}

void
Plot::CheckBounds3D(double x, double y, double z)
{
	if(x < xBounds.fx) xBounds.fx = x;	if(x > xBounds.fy) xBounds.fy = x;
	if(y < yBounds.fx) yBounds.fx = y;	if(y > yBounds.fy) yBounds.fy = y;
	if(z < zBounds.fx) zBounds.fx = z;	if(z > zBounds.fy) zBounds.fy = z;
	CheckBounds(x, y);
}

bool
Plot::SavVarObs(GraphObj **gol, long ngo, DWORD flags)
{
	int i;
	void *ptr;

	if(!gol || !ngo) return false;
	SavVarInit(150 * ngo);
	for(i = 0; i < ngo; i++) 
		if(gol[i]) gol[i]->FileIO(SAVE_VARS);
	ptr = SavVarFetch();
	Undo.SavVarBlock(this, &ptr, flags);
	return true;
}

DataObj *
Plot::CreaCumData(char *xr, char *yr, int mode, double base)
{
	char **yranges;
	int i, j, nc, nr, ir, ic, n, c_num, c_txt, c_datetime;
	double value, old_val;
	DataObj *CumData = 0L;
	anyResult ares;
	AccRange *ax = 0L, **ayy = 0L;
	TextValue *tv = 0L;
	bool *validRows;

	if(!xr || !yr || !mode || !data) return 0L;
	if(!(CumData = new DataObj()))return 0L;
	//count valid data lines
	if(!(ax = new AccRange(xr))) {
		delete CumData;		CumData = 0L;	return 0L;
		}
	ax->DataTypes(data, &c_num, &c_txt, &c_datetime);
	nr = ax->CountItems();
	if(!(yranges = split(yr, '&', &nc))){
		delete CumData;		delete ax;		return 0L;
		}
	if(x_tv) x_tv->Reset();		if(y_tv) y_tv->Reset();
	j = mode == 1 || mode == 2 ? nr : nr * 2;
	if(CumData->Init(j , nc+2) && (validRows = (bool*)calloc(j, sizeof(bool)))){
		if(!c_num && (c_txt + c_datetime) > 0 ) {
			if(x_tv) tv = x_tv;
			else if(y_tv) tv = y_tv;
			else tv = x_tv = new TextValue();
			}
		//setup all ranges
		if(!(ayy = (AccRange**)calloc(nc, sizeof(AccRange*))))return 0L;
		for(i = 0; i < nc; i++) {
			if(yranges[i] && *yranges[i] && (ayy[i] = new AccRange(yranges[i]))) {
				if(!ayy[i]->GetFirst(&ic, &ir)) return 0L;
				}
			}
		// set x values as first column
		for(i = n = 0, ax->GetFirst(&ic, &ir); ax->GetNext(&ic, &ir); i++, n++) {
			if(data->GetResult(&ares, ir, ic, false)) {
				if(tv) {
					switch(ares.type) {
					case ET_TEXT:
						value = tv->GetValue(ares.text);			break;
					default:
						TranslateResult(&ares);
						value = tv->GetValue(ares.text);			break;
						}
					CumData->SetValue(n, 0, value);		CumData->SetValue(n, 1, base);
					}
				else if(ares.type == ET_VALUE && ares.value > -HUGE_VAL && ares.value < HUGE_VAL) {
					CumData->SetValue(n, 0, value = ares.value);	CumData->SetValue(n, 1, base);
					}
				else {
					CumData->SetValue(n, 0, value = 0.0);	CumData->SetValue(n, 1, base);
					}
				if(mode == 3 || mode == 4){				//complete polygon data
					CumData->SetValue((nr<<1)-i-1, 0, value);
					}
				for(j = 0; j < nc; j++) {
					if(CumData->GetValue(n, j+1, &value)) CumData->SetValue(n, j+2, value);
					if(ayy[j]->GetNext(&ic, &ir) && data->GetResult(&ares, ir, ic, false)){
						if(ares.type == ET_VALUE && ares.value > -HUGE_VAL && ares.value < HUGE_VAL){
							value = ares.value;		validRows[i] = true;
							}
						else value = 0.0;			old_val = 0.0;
						CumData->GetValue(n, j+2, &old_val);
						switch (mode) {
						case 1:	case 3:	value += old_val;			break;
						case 2:	case 4: value = old_val -value;		break;
							}
						CumData->SetValue(n, j+2, value);
						}
					if(mode == 3 || mode == 4)			//complete polygon data
						if(CumData->GetValue(n, j+1, &value)){
							if(validRows[n]) validRows[(nr<<1)-i-1] = true;
							CumData->SetValue((nr<<1)-i-1, j+2, value);
							}
					}
				}
			else {
				for(j = 0; j < nc; j++) ayy[j]->GetNext(&ic, &ir);
				}
			}
		for(i = 0; i < nc; i++) delete ayy[i];		free(ayy);
		for(i = 0; i < CumData->cRows; i++) {
			if(!validRows[i]) {
				CumData->cRows--;
				for(j = 0; j < CumData->cCols; j++) {
					if(CumData->etRows[i][j]) delete CumData->etRows[i][j];
					}
				free(CumData->etRows[i]);
				for(j = i; j < CumData->cRows; j++) {
					CumData->etRows[j] = CumData->etRows[j+1];
					validRows[j] = validRows[j+1];
					}
				if(!validRows[i] && i < CumData->cRows) i--;
				}
			}
		free(validRows);
		}
	for(i = 0; i < nc; i++) if(yranges[i]) free(yranges[i]);
	if(ax) delete ax;		free(yranges);
	return CumData;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// PlotScatt handles most XY-Plots: its a Plot-Class
PlotScatt::PlotScatt(GraphObj *par, DataObj *d, DWORD presel):Plot(par, d)
{
	FileIO(INIT_VARS);
	DefSel = presel;
	Id = GO_PLOTSCATT;
	if (!d) {
		if(parent && parent->Command(CMD_DELOBJ, this, NULL)) return;
		ErrorBox("Attempt to create plot\nwithout any data.");
		return;
		}
}

PlotScatt::PlotScatt(GraphObj *par, DataObj *d, int cBars, Bar **bars, ErrorBar **errs):Plot(par, d)
{
	int i;

	FileIO(INIT_VARS);
	if(cBars && bars) {
		if((Bars = (Bar**)calloc(cBars, sizeof(Bar*)))) {
			nPoints = cBars;
			for(i = 0; i < cBars; i++) {
				if((Bars[i] = bars[i])) Bars[i]->parent = this;
				bars[i] = 0L;
				}
			}
		}
	if(cBars && errs) {
		if((Errors = (ErrorBar**)calloc(cBars, sizeof(Bar*)))) {
			nPoints = cBars;
			for(i = 0; i < cBars; i++) {
				if((Errors[i] = errs[i])) Errors[i]->parent = this;
				errs[i] = 0L;
				}
			}
		}
	Id = GO_PLOTSCATT;
}

PlotScatt::PlotScatt(GraphObj *par, DataObj *d, int nPts, Symbol **sym, DataLine *lin):
	Plot(par, d)
{
	int i;

	FileIO(INIT_VARS);
	nPoints = nPts;
	if(Symbols = sym) for(i = 0; i < nPts; i++) if(Symbols[i]) Symbols[i]->parent = this;
	if(TheLine = lin) TheLine->parent = this;
	Id = GO_PLOTSCATT;
}

PlotScatt::PlotScatt(int src):Plot(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
}

PlotScatt::~PlotScatt()
{
	ForEach(FE_FLUSH, 0L, 0L);
	if(name) free(name);			name=0L;
	if(x_info) free(x_info);		x_info = 0L;
	if(y_info) free(x_info);		y_info = 0L;
	if(data_desc) free(data_desc);	data_desc = 0L;
	if(x_tv) delete(x_tv);			x_tv = 0L;
	if(y_tv) delete(y_tv);			y_tv = 0L;
	Undo.InvalidGO(this);
}

double
PlotScatt::GetSize(int select)
{
	int i;
	double ft1, ft2, d;

	switch(select){
	case SIZE_BARMINX:
		if(BarDist.fx >= 0.0001) return BarDist.fx;
		if((!Bars) || (nPoints < 2)) return BarDist.fx = 1.0;
		ft1 = -HUGE_VAL;	ft2 = HUGE_VAL;		BarDist.fx= HUGE_VAL;
		for(i = 0; i < nPoints; i++) {
			if(Bars[i]) {
				ft2 = Bars[i]->GetSize(SIZE_XPOS);
				d = fabs(ft2-ft1);
				if(d != 0.0 && d < BarDist.fx) BarDist.fx = d;
				}
			ft1 = ft2;
			}
		return BarDist.fx = BarDist.fx > 0.0001 && BarDist.fx != HUGE_VAL  ? BarDist.fx : 1.0;
	case SIZE_BARMINY:
		if(BarDist.fy >= 0.0001) return BarDist.fy;
		if((!Bars) || (nPoints < 2)) return BarDist.fy = 1.0;
		ft1 = -HUGE_VAL;	ft2 = HUGE_VAL;		BarDist.fy= HUGE_VAL;
		for(i = 0; i < nPoints; i++) {
			if(Bars[i]) {
				ft2 = Bars[i]->GetSize(SIZE_YPOS);
				d = fabs(ft2-ft1);
				if(d != 0.0 && d < BarDist.fy) BarDist.fy = d;
				}
			ft1 = ft2;
			}
		return BarDist.fy = BarDist.fy > 0.0001 && BarDist.fy != HUGE_VAL  ? BarDist.fy : 1.0;
	default:
		return Plot::GetSize(select);
		}
}

bool
PlotScatt::SetSize(int select, double value)
{
	int i;

	switch(select & 0xfff){
	case SIZE_BARMINX:
		BarDist.fx = value;
		return true;
	case SIZE_BARMINY:
		BarDist.fy = value;
		return true;
	case SIZE_SYMBOL:		case SIZE_SYM_LINE:
		if(Symbols)	for(i = 0; i < nPoints; i++) 
			if(Symbols[i]) Symbols[i]->SetSize(select, value);
		return true;
	case SIZE_WHISKER:		case SIZE_WHISKER_LINE:
	case SIZE_ERRBAR:		case SIZE_ERRBAR_LINE:
		if(Errors)	for(i = 0; i < nPoints; i++) 
			if(Errors[i]) Errors[i]->SetSize(select, value);
		return true;
	case SIZE_BAR_LINE:		case SIZE_BAR:		case SIZE_XBASE:		case SIZE_YBASE:
		if(Bars) for(i = 0; i < nPoints; i++) 
			if(Bars[i]) Bars[i]->SetSize(select, value);
		return true;
	case SIZE_LB_XDIST:		case SIZE_LB_YDIST:
		if(Labels) for(i = 0; i < nPoints; i++)
			if(Labels[i]) Labels[i]->SetSize(select, value);
		return true;
	case SIZE_ARROW_LINE:	case SIZE_ARROW_CAPWIDTH:	case SIZE_ARROW_CAPLENGTH:
		if(Arrows) for(i = 0; i < nPoints; i++)
			if(Arrows[i]) Arrows[i]->SetSize(select, value);
		return true;
	}
	return false;
}

bool
PlotScatt::SetColor(int select, DWORD col)
{
	int i;
	GraphObj **go = 0L;

	switch(select) {
	case COL_SYM_LINE:
	case COL_SYM_FILL:		go = (GraphObj**)Symbols;		break;
	case COL_WHISKER:
	case COL_ERROR_LINE:	go = (GraphObj**)Errors;		break;
	case COL_BAR_LINE:
	case COL_BAR_FILL:		go = (GraphObj**)Bars;			break;
	case COL_ARROW:			go = (GraphObj**)Arrows;		break;
	default:				return false;
		}
	if(go) for(i = 0; i < nPoints; i++)
		if(go[i]) go[i]->SetColor(select, col);
	return true;
}

void
PlotScatt::DoPlot(anyOutput *o)
{
	if(!parent) return;
	parent->Command(CMD_REG_AXISPLOT, (void*)this, o);
	if(use_xaxis || use_yaxis) {
		ApplyAxes(o);
		ForEach(FE_PLOT, 0L, o);
		parent->Command(CMD_AXIS, 0L, o);
		}
	else {
		ForEach(FE_PLOT, 0L, o);
		}
	dirty = false;
}

bool
PlotScatt::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i;

	switch (cmd) {
	case CMD_MOUSE_EVENT:
		if(hidden) return false;
		if(!CurrGO && ((MouseEvent*)tmpl)->Action == MOUSE_LBUP)
			return ForEach(cmd, tmpl, o);
		return false;
	case CMD_LEGEND:
		if(((GraphObj*)tmpl)->Id != GO_LEGEND) return false;
		if(Bars) for (i = 0; i < nPoints; i++)
			if(Bars[i]) Bars[i]->Command(cmd, tmpl, o);
		if(Symbols) {
			if(TheLine && TheLine->Id == GO_DATALINE) {
				for (i = 0; i < nPoints && i < 100; i++)
					if(Symbols[i]) ((Legend*)tmpl)->HasSym(&TheLine->LineDef, Symbols[i], 0L);
				}
			else {
				for (i = 0; i < nPoints && i < 100; i++)
					if(Symbols[i]) ((Legend*)tmpl)->HasSym(0L, Symbols[i], 0L);
				}
			if(TheLine && TheLine->Id == GO_DATAPOLYGON) TheLine->Command(cmd, tmpl, o);
			}
		else if(TheLine) TheLine->Command(cmd, tmpl, o);
		if(Errors) for (i = 0; i < nPoints; i++)
			if(Errors[i]) Errors[i]->Command(cmd, tmpl, o);
		break;
	case CMD_MRK_DIRTY:
		dirty = true;
	case CMD_SETSCROLL:		case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		return false;
	case CMD_USEAXIS:
		return UseAxis(*((int*)tmpl));
	case CMD_FLUSH:
		return ForEach(FE_FLUSH, 0L, 0L);
	case CMD_TEXTTHERE:
		if(Labels) for(i = 0; i < nPoints; i++)	if(Labels[i] &&  Labels[i]->Command(cmd, tmpl, o))	return true;
		return false;
	case CMD_AUTOSCALE:
		if(hidden) return false;
		if(dirty){
			Bounds.Xmin = Bounds.Ymin = HUGE_VAL;
			Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
			}
		else{
			if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH && 
				Bounds.Xmax > Bounds.Xmin && Bounds.Ymax > Bounds.Ymin) {
				((Plot*)parent)->CheckBounds(Bounds.Xmin, Bounds.Ymin);
				((Plot*)parent)->CheckBounds(Bounds.Xmax, Bounds.Ymax);
				return true;
				}
			}
		dirty = false;
	case CMD_UPDATE:
		if(cmd == CMD_UPDATE){
			if (Symbols) SavVarObs((GraphObj **)Symbols, nPoints, UNDO_CONTINUE);
			if (Bars) SavVarObs((GraphObj **)Bars, nPoints, UNDO_CONTINUE);
			if (Errors) SavVarObs((GraphObj **)Errors, nPoints, UNDO_CONTINUE);
			if (Arrows) SavVarObs((GraphObj **)Arrows, nPoints, UNDO_CONTINUE);
			if (DropLines) SavVarObs((GraphObj **)DropLines, nPoints, UNDO_CONTINUE);
			if (Labels) SavVarObs((GraphObj **)Labels, nPoints, UNDO_CONTINUE);
			dirty = true;
			}
	case CMD_SET_DATAOBJ:
		if(cmd == CMD_SET_DATAOBJ) {
			Id = GO_PLOTSCATT;
			if(data && data == (DataObj *) tmpl) return true;
			data = (DataObj *)tmpl;	
			}
		ForEach(cmd, tmpl, o);
		if(cmd == CMD_AUTOSCALE) {
			if(x_tv) {
				Bounds.Xmin = 0.5;		Bounds.Xmax = ((double)x_tv->Count())+0.5;
				}
			if(y_tv) {
				Bounds.Ymin = 0.5;		Bounds.Xmax = ((double)y_tv->Count())+0.5;
				}
			}
		if(cmd == CMD_AUTOSCALE && parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH
			&& Bounds.Xmax > Bounds.Xmin && Bounds.Ymax > Bounds.Ymin) {
			((Plot*)parent)->CheckBounds(Bounds.Xmin, Bounds.Ymin);
			((Plot*)parent)->CheckBounds(Bounds.Xmax, Bounds.Ymax);
			}
		return true;
	case CMD_SCALE:
		return ForEach(cmd, tmpl, o);
	case CMD_MUTATE:		case CMD_REPL_GO:
		dirty = true;
		return ForEach(cmd == CMD_REPL_GO ? FE_REPLGO : FE_MUTATE, tmpl, o);
	case CMD_SYMTEXT:		case CMD_SYMTEXT_UNDO:	case CMD_SYM_RANGETEXT:
	case CMD_SYMTEXTDEF:	case CMD_SYM_TYPE:
		if(Symbols) for(i = 0; i < nPoints; i++)
			if(Symbols[i]) Symbols[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_SETTEXTDEF:
		if(Labels) for(i = 0; i < nPoints; i++)
			if(Labels[i]) Labels[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_DL_LINE:		case CMD_DL_TYPE:
		if(DropLines) for(i = 0; i < nPoints; i++)
			if(DropLines[i]) DropLines[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_ERR_TYPE:		case CMD_WHISKER_STYLE:		case CMD_ERRDESC:
		if(Errors) for(i = 0; i < nPoints; i++) {
			if(Errors[i]) Errors[i]->Command(cmd, tmpl, o);
			}
	case CMD_BAR_TYPE:		case CMD_BAR_FILL:
		if(Bars) for(i = 0; i < nPoints; i++) {
			if(Bars[i]) Bars[i]->Command(cmd, tmpl, o);
			}
		return true;
	case CMD_ARROW_TYPE:	case CMD_ARROW_ORG:
		if(Arrows) for(i = 0; i < nPoints; i++) {
			if(Arrows[i]) Arrows[i]->Command(cmd, tmpl, o);
			}
		return true;
	case CMD_DELOBJ:
		dirty = true;
		if(parent && tmpl && o) return ForEach(FE_DELOBJ, tmpl, o);
		break;
	case CMD_SAVE_SYMBOLS:
		return SavVarObs((GraphObj **)Symbols, nPoints, 0L);
	case CMD_SAVE_BARS:
		return SavVarObs((GraphObj **)Bars, nPoints, 0L);
	case CMD_SAVE_ERRS:
		return SavVarObs((GraphObj **)Errors, nPoints, 0L);
	case CMD_SAVE_ARROWS:
		return SavVarObs((GraphObj **)Arrows, nPoints, 0L);
	case CMD_SAVE_DROPLINES:
		return SavVarObs((GraphObj **)DropLines, nPoints, 0L);
	case CMD_SAVE_LABELS:
		return SavVarObs((GraphObj **)Labels, nPoints, 0L);
		}
	return false;
}

bool
PlotScatt::ForEach(int cmd, void *tmp, anyOutput *o)
{
	int i, j;
	GraphObj **obs[] = {(GraphObj**)Symbols, (GraphObj**)Errors, (GraphObj**)Arrows,
		(GraphObj**)DropLines, (GraphObj**)Labels, (GraphObj**)Bars};
	GraphObj ***go = 0L;
	GraphObj **tmpPlots;
	bool bRedraw;

	switch(cmd) {
	case FE_MUTATE:
	case FE_REPLGO:
		if((tmpPlots = (GraphObj **)tmp) && tmpPlots[0] && tmpPlots[1]) {
			for(j = 0; j < 6; j++){
				if(obs[j]) for(i = 0; i < nPoints; i++){
					if(obs[j][i] && obs[j][i] == tmpPlots[0]) {
						if(cmd == FE_REPLGO) return ReplaceGO(&obs[j][i], tmpPlots);
						else {
							Undo.MutateGO(&obs[j][i], tmpPlots[1], 0L, o);
							return true;
							}
						}
					}
				}
			if(TheLine == tmpPlots[0]){
				if(cmd == FE_REPLGO) return ReplaceGO((GraphObj**)&TheLine, tmpPlots);
				else {
					Undo.MutateGO((GraphObj**)&TheLine, tmpPlots[1], 0L, o);
					return true;
					}
				}
			}
		return false;
	case FE_PARENT:
		for(j = 0; j < 6; j++){
			if(obs[j]) for(i = 0; i < nPoints; i++){
				if(obs[j][i]) obs[j][i]->parent = this;
				}
			}
		if(TheLine) TheLine->parent = this;
		return true;
	case CMD_UPDATE:	case CMD_SET_DATAOBJ:	case CMD_AUTOSCALE:		case CMD_SCALE:
		for(j = 0; j < 6; j++){
			if(obs[j]) for(i = 0; i < nPoints; i++){
				if(obs[j][i]) obs[j][i]->Command(cmd, tmp, o);
				}
			}
		if(TheLine) TheLine->Command(cmd, tmp, o);
		return true;
	case FE_PLOT:
		if(TheLine) TheLine->DoPlot(o);
		for(j = 5; j >= 0; j--){
			if(obs[j]) for(i = 0; i < nPoints; i++){
				if(obs[j][i]) obs[j][i]->DoPlot(o);
				}
			}
		return true;
	case FE_FLUSH:
		for(j = 0; j < 6; j++){
			if(obs[j]) {
				for(i = 0; i < nPoints; i++) if(obs[j][i]) DeleteGO(obs[j][i]);
				free(obs[j]);	obs[j] = 0L;
				}
			}
		if(ErrRange) free(ErrRange);	if(yRange) free(yRange);
		if(xRange) free(xRange);		if(LbRange) free(LbRange);
		ErrRange = yRange = xRange = LbRange = 0L;
		if(TheLine) DeleteGO(TheLine);
		Bars = 0L;		Symbols = 0L;		Errors = 0L; 
		Arrows = 0L;	DropLines = 0L;		Labels = 0L;	TheLine = 0L;
		return true;
	case FE_DELOBJ:
		if(!o) return false;
		for(j = 0, bRedraw = false, go = 0L; j < 6 && !bRedraw; j++) {
			if(obs[j]) for(i = 0; i < nPoints; i++){
				if(obs[j][i]){
					if(tmp == (void*)obs[j][i]) {
						o->MrkMode = MRK_NONE;
						o->MouseCursor(MC_WAIT, true);
						Undo.DeleteGO(&obs[j][i], 0L, o);
						switch(j) {
						case 0: go = (GraphObj***)&Symbols;		break;
						case 1: go = (GraphObj***)&Errors;		break;
						case 2: go = (GraphObj***)&Arrows;		break;
						case 3: go = (GraphObj***)&DropLines;	break;
						case 4: go = (GraphObj***)&Labels;		break;
						case 5: go = (GraphObj***)&Bars;		break;
							}
						bRedraw = true;
						break;
						}
					}
				}
			}
		if(!bRedraw && TheLine && tmp == (void *) TheLine) {
			o->MrkMode = MRK_NONE;
			Undo.DeleteGO((GraphObj**)(&TheLine), 0L, o);
			bRedraw = true;
			}
		if(bRedraw && go) for(i = j = 0; i < nPoints; i++) if(go[0][i]) j++;
		if(!j) Undo.DropMemory(this, (void**)go, UNDO_CONTINUE);
		if(bRedraw && dirty) Command(CMD_AUTOSCALE, 0L, o); 
		if(!Bars && !Symbols && !Errors && !Arrows && !TheLine && !DropLines
			&& !Labels) parent->Command(CMD_DELOBJ_CONT, this, o);
		else if(bRedraw) parent->Command(CMD_REDRAW, NULL, o);
		return bRedraw;
	default:							//pass command to all objects
		for(j = 0; j < 6; j++){
			if(obs[j]) for(i = 0; i < nPoints; i++){
				if(obs[j][i]) if(obs[j][i]->Command(cmd, tmp, o)) return true;
				}
			}
		if(TheLine) return (TheLine->Command(cmd, tmp, o));
		return false;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// xyStat is based on scatterplot
xyStat::xyStat(GraphObj *par, DataObj *d):PlotScatt(par, d, 0L)
{
	FileIO(INIT_VARS);
	Id = GO_XYSTAT;
}

xyStat::xyStat(int src):PlotScatt(0)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
}

xyStat::~xyStat()
{
	ForEach(FE_FLUSH, 0L, 0L);
	if(curr_data) delete curr_data;			curr_data = 0L;
	if(case_prefix) free(case_prefix);		case_prefix = 0L;
	if(yRange) free(yRange);				yRange = 0L;
	if(xRange) free(xRange);				xRange = 0L;
	if(name) free(name);					name=0L;
	if(x_info) free(x_info);				x_info = 0L;
	if(y_info) free(x_info);				y_info = 0L;
	if(x_tv) delete(x_tv);					x_tv = 0;
	Undo.InvalidGO(this);
}

bool
xyStat::Command(int cmd, void *tmpl, anyOutput *o)
{
	switch (cmd) {
	case CMD_UPDATE:
		if (Symbols) SavVarObs((GraphObj **)Symbols, nPoints, UNDO_CONTINUE);
		if (Bars) SavVarObs((GraphObj **)Bars, nPoints, UNDO_CONTINUE);
		if (Errors) SavVarObs((GraphObj **)Errors, nPoints, UNDO_CONTINUE);
		if (Labels) SavVarObs((GraphObj **)Labels, nPoints, UNDO_CONTINUE);
		CreateData();
		ForEach(CMD_SET_DATAOBJ, curr_data, o);
		ForEach(CMD_UPDATE, tmpl, o);
		return dirty = true;
	case CMD_SET_DATAOBJ:
		if(cmd == CMD_SET_DATAOBJ) {
			Id = GO_XYSTAT;
			if(data && data == (DataObj *) tmpl) return true;
			if(curr_data) delete curr_data;		curr_data = 0L;
			data = (DataObj *)tmpl;
			if(data && !curr_data) CreateData();
			tmpl = curr_data;
			}
		ForEach(cmd, tmpl, o);
		return true;
	default:
		return PlotScatt::Command(cmd, tmpl, o);
		}
	return false;
}

void
xyStat::CreateData()
{
	int i, j, k, l, m, n, *ny, c_num, c_txt, c_dattim;
	double y, ss, d, lo, hi, **ay, *ax, *tay, *q1, *q2, *q3;
	lfPOINT *xy;
	AccRange *rX, *rY;
	anyResult x_res, y_res;

	if(!data || !xRange || !yRange || !xRange[0] || !yRange[0]) return;
	if(!(rX = new AccRange(xRange)) || !(rY = new AccRange(yRange))) return;
	if(!x_info) x_info = rX->RangeDesc(data, 0);	if(!y_info) y_info = rY->RangeDesc(data, 0);
	m = rX->CountItems();	n = 0;
	if(m < 2 || !(xy = (lfPOINT*) malloc(m * sizeof(lfPOINT)))) {
		delete rX;	delete rY;
		return;
		}
	if(x_tv) delete x_tv;					x_tv = 0L;
	ny = (int*) calloc(m, sizeof(int));
	ay = (double**) calloc(m, sizeof(double*));
	ax = (double*) calloc(m, sizeof(double));
	tay = (double*)malloc(m * sizeof(double));
	if(!ny || !ay || !ax || !tay) {
		if(ny) free(ny);	if(ay) free(ay);
		if(ax) free(ax);	if(tay) free(tay);
		delete rX;	delete rY;
		return;
		}
	rX->DataTypes(data, &c_num, &c_txt, &c_dattim);
	if(c_num < 5 && (c_txt + c_dattim) > 5) {
		x_tv = new TextValue();	
		}
	rX->GetFirst(&i, &j);	rY->GetFirst(&k, &l);	dirty = true;
	rX->GetNext(&i, &j);	rY->GetNext(&k, &l);	n=0;
	do {
		if(data->GetResult(&x_res, j, i, false) && data->GetResult(&y_res, l, k, false) && y_res.type == ET_VALUE) {
			xy[n].fy = y_res.value;
			if(x_tv){ 
				switch(x_res.type) {
				case ET_TEXT:
					xy[n++].fx = x_tv->GetValue(x_res.text);
					break;
				case ET_VALUE:	case ET_BOOL:	case ET_DATE:	case ET_TIME:	case ET_DATETIME:
					TranslateResult(&x_res);
					xy[n++].fx = x_tv->GetValue(x_res.text);
					break;
					}
				}
			else if(x_res.type == ET_VALUE) xy[n++].fx = x_res.value;
			}
		}while(rX->GetNext(&i, &j) && rY->GetNext(&k, &l));
	delete rX;			delete rY;
	if(!n) {
		if(ny) free(ny);	if(ay) free(ay);
		if(ax) free(ax);	if(tay) free(tay);
		return;
		}
	SortFpArray(n, xy);
	for(i = j = 0; i < (n-1); i++, j++) {
		ax[j] = xy[i].fx;		tay[0] = xy[i].fy;
		ny[j] = 1;
		for(k = 1; xy[i+1].fx == xy[i].fx; k++) {
			tay[k] = xy[i+1].fy;
			i++;		ny[j]++;
			}
		ay[j] = (double*)memdup(tay, k * sizeof(double), 0);
		}
	if(xy[i].fx > xy[i-1].fx) {
		ax[j] = xy[i].fx;		tay[0] = xy[i].fy;
		ny[j] = 1;
		ay[j++] = (double*)memdup(tay, sizeof(double), 0);
		}
	if(type & 0x0480) {		//medians and/or percentiles required
		q1 = (double *)malloc(j * sizeof(double));
		q2 = (double *)malloc(j * sizeof(double));
		q3 = (double *)malloc(j * sizeof(double));
		if(q1 && q2 && q3) {
			for(i = 0; i < j; i++) {
				if(ny[i] > 1) d_quartile(ny[i], ay[i], q1+i, q2+i, q3+i);
				else q1[i] = q2[i] = q3[i] = *ay[i];
				}
			}
		else type &= (~0x0480);
		}
	else q1 = q2 = q3 = 0L;
	if((curr_data = curr_data ? curr_data : new DataObj()) && curr_data->Init(j, 6)) {
		for(i = 0; i < j; i++) curr_data->SetValue(i,0,ax[i]);	// set x-values
		for(i = 0; i < j; i++) {								// set y-values
			if(ny[i] > 1) switch(type & 0x00f0) {
				case 0x0010:	default:
					curr_data->SetValue(i, 1, y=d_amean(ny[i], ay[i]));
					break;
				case 0x0020:
					curr_data->SetValue(i, 1, y=d_gmean(ny[i], ay[i]));
					break;
				case 0x0040:
					curr_data->SetValue(i, 1, y=d_hmean(ny[i], ay[i]));
					break;
				case 0x0080:
					curr_data->SetValue(i, 1, y=q2[i]);
					break;
				}
			else curr_data->SetValue(i, 1, y= *ay[i]);
			curr_data->SetValue(i, 4, y);
			}
		for(i = 0; i < j; i++) {								// set errors
			switch(type & 0x1f00) {
			case 0x0100:	case 0x0200:	case 0x1000:	//SD, SEM, conf. int.
				if(ny[i] > 1) {
					ss = d_variance(ny[i], ay[i], &y);
					switch(type & 0x1f00) {
					case 0x0100:
						curr_data->SetValue(i, 2, sqrt(ss));
						break;
					case 0x0200:
						curr_data->SetValue(i, 2, sqrt(ss)/sqrt((double)ny[i]));
						break;
					case 0x1000:
						d = distinv(t_dist, ny[i]-1, 1, 1.0-(ci/100.0), 2.0);
						curr_data->SetValue(i, 2, d * sqrt(ss)/sqrt((double)ny[i]));
						break;
						}
					}
				else curr_data->SetValue(i, 2, 0.0);
				if(curr_data->GetValue(i, 1, &y) && curr_data->GetValue(i, 2, &hi))
					curr_data->SetValue(i, 4, hi+y);
				break;
			case 0x0400:								//percentiles
				curr_data->SetValue(i, 2, q1[i]);	curr_data->SetValue(i, 3, q3[i]);
				curr_data->SetValue(i, 4, q3[i]);
				break;
			case 0x0800:								//min-max
				lo = hi = *ay[i];
				for(k = 1; k < ny[i]; k++) {
					if(ay[i][k] < lo) lo = ay[i][k];
					if(ay[i][k] > hi) hi = ay[i][k];
					}
				curr_data->SetValue(i, 2, lo);		curr_data->SetValue(i, 3, hi);
				curr_data->SetValue(i, 4, hi);
				break;
				}
			}
		if(type & 0x6000) for(i = 0; i < j; i++) {				// number of cases
#ifdef USE_WIN_SECURE
			sprintf_s(TmpTxt, TMP_TXT_SIZE, "%s%d", case_prefix ? case_prefix : "", ny[i]);
#else
			sprintf(TmpTxt, "%s%d", case_prefix ? case_prefix : "", ny[i]);
#endif
			curr_data->SetText(i, 5, TmpTxt);
			}
		}
	if(q1) free(q1);	if(q2) free(q2);	if(q3) free(q3);
	for(i = 0; i < m; i++) if(ay[i]) free(ay[i]);
	free(tay);	free(ay);	free(ax);	free(ny);	free(xy);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// BarChart is based on scatterplot
BarChart::BarChart(GraphObj *par, DataObj *d):PlotScatt(par, d, 0L)
{
	Id = GO_BARCHART;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Frequenc distribution: bar chart with function
FreqDist::FreqDist(GraphObj *par, DataObj *d):Plot(par, d)
{
	FileIO(INIT_VARS);
	Id = GO_FREQDIST;
}

FreqDist::FreqDist(GraphObj *par, DataObj *d, char* range, bool bOnce):Plot(par, d)
{
	FileIO(INIT_VARS);
	if(range && range[0]) {
		ssRef = (char*)memdup(range, (int)strlen(range)+1, 0);
		plots = (GraphObj**)calloc(nPlots=3, sizeof(GraphObj*));
		ProcData(-1);
		if(bOnce && ssRef) {
			free(ssRef);		ssRef = 0L;
			}
		}
	Id = GO_FREQDIST;
}

FreqDist::FreqDist(GraphObj *par, DataObj *d, double *vals, int nvals, int nclasses):Plot(par, d)
{
	int i, j;
	int *cl_data;
	Bar **bars;

	FileIO(INIT_VARS);
	ssRef = 0L;
	plots = (GraphObj**)calloc(nPlots=3, sizeof(GraphObj*));
	for(i = 0, dmin = HUGE_VAL, dmax = -HUGE_VAL; i < nvals; i++) {
		if(vals[i] < dmin) dmin = vals[i];
		if(vals[i] > dmax) dmax = vals[i];
		}
	start = dmin;		step = 1.00001*(dmax-dmin)/((double)nclasses);
	if(!(cl_data = (int*)calloc(nclasses+1, sizeof(int)))) return;
	for(i = 0; i < nvals; i++) {
		j = (int)(floor((vals[i] - start)/step));
		if(j >= 0 && j <= nclasses) cl_data[j]++;
		}
	if(cl_data[nclasses]) nclasses++;
	if(bars = (Bar**)calloc(nclasses, sizeof(Bar*))) for(i = 0; i < nclasses; i++) {
		if(bars[i] = new Bar(this, 0L, i*step+start, (double)cl_data[i], BAR_VERTB | BAR_RELWIDTH,
			-1, -1, -1, -1, "Count")) bars[i]->SetSize(SIZE_BAR, 100.0);
		}
	//create bar chart
	if(bars && (plots[0] = new PlotScatt(this, data, nclasses, bars, 0L))){
		plots[0]->Command(CMD_BAR_FILL, &BarFill, 0L);
		plots[0]->SetColor(COL_BAR_LINE, BarLine.color);
		plots[0]->SetSize(SIZE_BAR_LINE, BarLine.width);
		}
	if(plots[0]){
		Bounds.Xmin = dmin;		Bounds.Xmax = dmax;
		plots[0]->Command(CMD_AUTOSCALE, 0L, 0L);
		}
	free(cl_data);
	Id = GO_FREQDIST;
}

FreqDist::FreqDist(int src):Plot(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
}

FreqDist::~FreqDist()
{
	int i;

	if(curr_data) delete(curr_data);		curr_data = 0L;
	if(ssRef) free(ssRef);					ssRef = 0L;
	if(plots) {
		for(i = 0; i < nPlots; i++) if(plots[i]) DeleteGO(plots[i]);
		free(plots);						plots=0L;
		}
	if(name) free(name);					name=0L;
	if(x_info) free(x_info);				x_info = 0L;
	if(y_info) free(y_info);				y_info = 0L;
	if(x_tv) delete x_tv;					x_tv = 0L;
}

void
FreqDist::DoPlot(anyOutput *o)
{
	int i;

	if(!plots || !o || !data || !parent) return;
	parent->Command(CMD_REG_AXISPLOT, (void*)this, o);
	if(use_xaxis || use_yaxis) ApplyAxes(o);
	for(i = 0; i < nPlots; i++) {
		if(plots[i]) {
			if(plots[i]->Id >= GO_PLOT && plots[i]->Id < GO_GRAPH){
				if(((Plot*)plots[i])->hidden == 0) plots[i]->DoPlot(o);
				}
			else plots[i]->DoPlot(o);
			}
		}
	if(use_xaxis || use_yaxis) parent->Command(CMD_AXIS, 0L, o);
}

bool
FreqDist::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i;

	switch (cmd) {
	case CMD_MOUSE_EVENT:
		if(hidden || ((MouseEvent*)tmpl)->Action != MOUSE_LBUP || CurrGO) return false;
	case CMD_LEGEND:
		if(plots) for(i = 0; i < nPlots; i++) {
			if(plots[i]){
				if(plots[i]->Id >= GO_PLOT && plots[i]->Id < GO_GRAPH){
					if(((Plot*)plots[i])->hidden == 0) plots[i]->Command(cmd, tmpl, o);
					}
				else plots[i]->Command(cmd, tmpl, o);
				}
			}
		return false;
	case CMD_SCALE:
		if(plots) for(i = 0; i < nPlots; i++) if(plots[i]) plots[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_DELOBJ:
		if(plots && tmpl && parent) for(i = 0; i < nPlots; i++) if(plots[i]){
			if(tmpl == (void*)plots[i]) {
				DeleteGO(plots[i]);	plots[i]=0L;
				if(i == 1) type=0;
				parent->Command(CMD_REDRAW, 0L, o);
				return true;
				}
			}
		return false;
	case CMD_UPDATE:
		if(data && parent && plots) {
			ProcData(0);
			if(!curr_data) return false;
			for(i = 0; i < nPlots; i++) if(plots[i]) {
				plots[i]->Command(CMD_SET_DATAOBJ, curr_data, o);
				plots[i]->Command(CMD_UPDATE, 0L, o);
				}
			}
		return false;
	case CMD_SET_DATAOBJ:
		Id = GO_FREQDIST;
		data = (DataObj *)tmpl;
		return true;
	case CMD_MRK_DIRTY:
		dirty = true;
	case CMD_SETSCROLL:		case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		return false;
	case CMD_USEAXIS:
		return UseAxis(*((int*)tmpl));
	case CMD_AUTOSCALE:
		if(hidden) return false;
		if(dirty){
			Bounds.Xmin = HUGE_VAL;		Bounds.Ymin = 0;
			Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
			if(dmax > dmin) {
				Bounds.Xmin = dmin;		Bounds.Xmax = dmax;
				}
			}
		else{
			if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH && 
				Bounds.Xmax > Bounds.Xmin && Bounds.Ymax > Bounds.Ymin) {
				((Plot*)parent)->CheckBounds(Bounds.Xmin, Bounds.Ymin);
				((Plot*)parent)->CheckBounds(Bounds.Xmax, Bounds.Ymax);
				return true;
				}
			}
		dirty = false;
		if(plots) for(i = 0; i < nPlots; i++) {
			if(plots[i]){
				if(plots[i]->Id >= GO_PLOT && plots[i]->Id < GO_GRAPH){
					if(((Plot*)plots[i])->hidden == 0) plots[i]->Command(cmd, tmpl, o);
					}
				else plots[i]->Command(cmd, tmpl, o);
				}
			}
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH && 
			Bounds.Xmax > Bounds.Xmin && Bounds.Ymax > Bounds.Ymin) {
			((Plot*)parent)->CheckBounds(Bounds.Xmin, Bounds.Ymin);
			((Plot*)parent)->CheckBounds(Bounds.Xmax, Bounds.Ymax);
			return true;
			}
	case CMD_DROP_PLOT:		case CMD_DROP_GRAPH:
		if(tmpl && plots && nPlots >1) {
			if(plots[1]) DeleteGO(plots[1]);
			plots[1] = (GraphObj*)tmpl;		dirty = true;		plots[1]->parent = this;
			plots[1]->Command(CMD_SET_DATAOBJ, curr_data, o);
			Command(CMD_AUTOSCALE, 0L, o);
			return true;
			}
		return false;
		}
	return false;
}

void
FreqDist::ProcData(int sel)
{
	AccRange *ar;
	int nv, i, j, r, c, ncl, *f_data, cb_f, size_fo, pos_fo, c_num, c_txt, c_dattim;
	double min, max, sum, mean, sd, tmp, *s_data, *t_data, lstep;
	double chi2, df, x, y;
	anyResult *result;
	bool bValid = false;
	Bar **bars = 0L;
	anyResult res;
	TextDEF td;
	char *fo, *fdesc = 0L, formula[500];

	if(!parent || !data || !ssRef || !plots) return;
	if(curr_data) delete(curr_data);		curr_data = 0L;
	if(x_tv) delete x_tv;					x_tv = 0L;
	if((curr_data = new DataObj()) && (ar = new AccRange(ssRef))) {
		if(!y_info && (y_info = (char*)malloc(25*sizeof(char)))) rlp_strcpy(y_info, 25, "No. of observations");
		dmin = HUGE_VAL, dmax = -HUGE_VAL;
		ar->DataTypes(data, &c_num, &c_txt, &c_dattim);
		if(c_num < 5 && (c_txt + c_dattim) > 5) {
			x_tv = new TextValue();		dmin = 0.0;
			}
		//copy spreadsheet data into array
		nv = ar->CountItems();			ar->GetFirst(&c, &r);
		if(!(s_data = (double*)malloc(nv * sizeof(double))) 
			|| !(t_data = (double*)malloc(nv * sizeof(double)))) {
			delete(ar);					return;
			}
		for(sum = 0.0, nv = 0; ar->GetNext(&c, &r); ) if(data->GetResult(&res, r, c, false)) {
			if(x_tv){
				switch(res.type) {
				case ET_TEXT:
					if((tmp = x_tv->GetValue(res.text))> 0.0)bValid = true;
					else bValid = false;				break;
				case ET_VALUE:	case ET_DATE:	case ET_TIME:	case ET_DATETIME:	case ET_BOOL:
					TranslateResult(&res);
					if((tmp = x_tv->GetValue(res.text))> 0.0)bValid = true;
					else bValid = false;				break;
				default: 
					bValid = false;						break;
					}
				}
			else {
				if(res.type == ET_VALUE) {
					tmp = res.value;		bValid = true;
					}
				else bValid = false;
				}
			if(bValid) {
				if(tmp > dmax) dmax = tmp;	if(tmp < dmin) dmin = tmp;
				s_data[nv] = tmp;
				switch (type & 0xff){
				case 2:
					if(tmp > 0.0) t_data[nv] = log(tmp);
					else nv--;
					break;
				default:	t_data[nv] = tmp;		break;
					}
				nv++;
				}
			}
		delete(ar);
		if(!nv || dmin >= dmax) {
			free(s_data);		s_data = 0L;		free(t_data);		t_data = 0L;
			return;
			}
		min = dmin;		max = dmax;
		lstep = (max-min)/100.0;
		d_variance(nv, t_data, &mean, &sd);
		sd = sqrt(sd/((double)(nv-1)));
		step = fabs(step);
		if(x_tv) {
			start = 0.5;	step = 1.0;		max+= 0.5;
			}
		else if(sel == -1) {
			start = min;	step = (max - min)/(step != 0.0 ? step : 7.0);
			}
		else if(sel == -2) {
			min = start;
			}
		ncl = (int)(floor((max-start)/step))+1;
		if(plots[0] &&	(max > (Bounds.Xmax+step/2.0) || min < (Bounds.Xmin-step/2.0))) {
			DeleteGO(plots[0]);		plots[0] = 0L;
			}
		if(!plots[0])bars = (Bar**)calloc(ncl, sizeof(Bar*));
		f_data = (int*)calloc(ncl+1, sizeof(int));
		for(i = 0; i < nv; i++) {
			j = (int)(floor((s_data[i] - start)/step));
			if(j >= 0 && j < ncl) f_data[j]++;
			else if(s_data[i] == max) f_data[j-1]++;
			}
		if(f_data[ncl]) ncl++;
		curr_data->Init(ncl, 2);
		//create data object containg the counts / bin and bars
		for(i = 0; i< ncl; i++) {
			curr_data->SetValue(i, 0, tmp = start + i * step + step * .5);
			curr_data->SetValue(i, 1, (double)f_data[i]);
			if(bars) {
				if(bars[i] = new Bar(this, 0L, tmp, (double)f_data[i], BAR_VERTB | BAR_RELWIDTH,
					0, i, 1, i, "Count")) bars[i]->SetSize(SIZE_BAR, 100.0);
				}
			}
		free(s_data);		free(t_data);		free(f_data);
		//create bar chart
		if(bars && (plots[0] = new PlotScatt(this, data, ncl, bars, 0L))){
			plots[0]->Command(CMD_BAR_FILL, &BarFill, 0L);
			plots[0]->SetColor(COL_BAR_LINE, BarLine.color);
			plots[0]->SetSize(SIZE_BAR_LINE, BarLine.width);
			}
		if(plots[0]){
			Bounds.Xmin = dmin;		Bounds.Xmax = dmax;
			plots[0]->Command(CMD_SET_DATAOBJ, curr_data, 0L);
			plots[0]->Command(CMD_AUTOSCALE, 0L, 0L);
			}
		//create function
		if((type & 0xff) && (fo = (char*)malloc(size_fo = 1000))) {
			pos_fo = rlp_strcpy(fo, 1000, (char*) "[1=Function]\nx1=");
			add_dbl_to_buff(&fo, &pos_fo, &size_fo, min, true);
			add_to_buff(&fo, &pos_fo, &size_fo,(char*)"\nx2=", 4);
			add_dbl_to_buff(&fo, &pos_fo, &size_fo, max, true);
			add_to_buff(&fo, &pos_fo, &size_fo,(char*)"\nxstep=", 7);
			add_dbl_to_buff(&fo, &pos_fo, &size_fo, lstep, true);
			add_to_buff(&fo, &pos_fo, &size_fo,(char*)"\nLine=", 6);
			add_dbl_to_buff(&fo, &pos_fo, &size_fo, DefSize(SIZE_DATA_LINE), true);
			add_to_buff(&fo, &pos_fo, &size_fo,(char*)" 6 0x000000ff 0x0\n", 18);
			cb_f = 0;
			switch (type & 0xff){
			case 2:				//lognormal
#ifdef USE_WIN_SECURE
				cb_f = sprintf_s(formula, 500, "%g*lognormfreq(x,%g,%g)",nv*step, mean, sd);
#else
				cb_f = sprintf(formula,"%g*lognormfreq(x,%g,%g)",nv*step, mean, sd);
#endif
				fdesc = (char*)"Desc=\"Lognormal Dist.\"\n";
				break;
			case 3:				//exponential
#ifdef USE_WIN_SECURE
				cb_f = sprintf_s(formula, 500, "%g*expfreq(x,%g)",nv*step, 1.0/mean);
#else
				cb_f = sprintf(formula,"%g*expfreq(x,%g)",nv*step, 1.0/mean);
#endif
				fdesc = (char*)"Desc=\"Exponential Dist.\"\n";
				break;
			case 4:				//rectangular
#ifdef USE_WIN_SECURE
				cb_f = sprintf_s(formula, 500, "%g*%g*(x>=%g&&x<=%g)",nv*step, 1.0/(dmax-dmin), dmin, dmax);
#else
				cb_f = sprintf(formula,"%g*%g*(x>=%g&&x<=%g)",nv*step, 1.0/(dmax-dmin), dmin, dmax);
#endif
				fdesc = (char*)"Desc=\"Rectangular Dist.\"\n";
				break;
			case 5:				//chi-square
#ifdef USE_WIN_SECURE
				cb_f = sprintf_s(formula, 500, "%g*chifreq(x,%g)",nv*step, mean);
#else
				cb_f = sprintf(formula,"%g*chifreq(x,%g)",nv*step, mean);
#endif
				fdesc = (char*)"Desc=\"Chi<sup>2</sup> Dist.\"\n";
				break;
			case 10:			//binomial
#ifdef USE_WIN_SECURE
				cb_f = sprintf_s(formula, 500, "%g*binomfreq(x,%g,%g)*(x>0)",nv*step, dmax, mean/dmax);
#else
				cb_f = sprintf(formula,"%g*binomfreq(x,%g,%g)*(x>0)",nv*step, dmax, mean/dmax);
#endif
				fdesc = (char*)"Desc=\"Binomial Dist.\"\n";
				break;
			case 11:			//poisson
#ifdef USE_WIN_SECURE
				cb_f = sprintf_s(formula, 500, "%g*poisfreq(x,%g)*(x>0)",nv*step, mean);
#else
				cb_f = sprintf(formula,"%g*poisfreq(x,%g)*(x>0)",nv*step, mean);
#endif
				fdesc = (char*)"Desc=\"Poisson Dist.\"\n";
				break;
			default:			//normal
#ifdef USE_WIN_SECURE
				cb_f = sprintf_s(formula, 500, "%g*normfreq(x,%g,%g)",nv*step, mean, sd);
#else
				cb_f = sprintf(formula,"%g*normfreq(x,%g,%g)",nv*step, mean, sd);
#endif
				fdesc = (char*)"Desc=\"Normal Dist.\"\n";
				break;
				}
			if(cb_f) {
				add_to_buff(&fo, &pos_fo, &size_fo, "f_xy=\"y=" , 8);
				add_to_buff(&fo, &pos_fo, &size_fo, formula , cb_f);
				add_to_buff(&fo, &pos_fo, &size_fo, "\\n\"\n" , 4);
				}
			if(fdesc)add_to_buff(&fo, &pos_fo, &size_fo, fdesc, 0);
			OpenGraph(this, 0L, (unsigned char *)fo, false);
			free(fo);							chi2 = df = 0.0;
			//calculate chi-square test of fit
			if(curr_data) for(i = 0; i< ncl; i++) {
				if(curr_data->GetValue(i,0, &x) && curr_data->GetValue(i,1, &y)){
#ifdef USE_WIN_SECURE
					sprintf_s(TmpTxt, TMP_TXT_SIZE, "x=%g;%s", x, formula);
#else
					sprintf(TmpTxt, "x=%g;%s", x, formula);
#endif
					result = do_formula(curr_data, TmpTxt);
					if(result->type == ET_VALUE && fabs(result->value) > 0.0) {
						tmp = y-result->value;	
						tmp = (tmp*tmp)/result->value;
						chi2 += tmp;			df += 1.0;
						}
					}
				}
			//report result of the chi-square test
			if(chi2 > 0.0 && parent && (fo = (char*)malloc(size_fo = 1000))) {
				tmp = chi_dist(chi2, df-1.0, 1.0);
				pos_fo = rlp_strcpy(fo, 1000, (char*)"chi<sup> 2</sup> =");
				add_dbl_to_buff(&fo, &pos_fo, &size_fo, chi2, true);
				add_to_buff(&fo, &pos_fo, &size_fo, (char*)", n =", 5);
				add_dbl_to_buff(&fo, &pos_fo, &size_fo, df, true);
				add_to_buff(&fo, &pos_fo, &size_fo, (char*)", df =", 6);
				add_dbl_to_buff(&fo, &pos_fo, &size_fo, df-1.0, true);
				add_to_buff(&fo, &pos_fo, &size_fo, (char*)", p =", 5);
				if(tmp < 0.0001) {
					pos_fo--;	add_to_buff(&fo, &pos_fo, &size_fo, (char*)"< 0.0001", 8);
					}
				else add_dbl_to_buff(&fo, &pos_fo, &size_fo, tmp, true);
				if(!plots[2]) {
					x = (parent->GetSize(SIZE_GRECT_RIGHT) - parent->GetSize(SIZE_GRECT_LEFT))/2.0;
					y = parent->GetSize(SIZE_GRECT_BOTTOM) - DefSize(SIZE_TEXT)*5;
					y -= parent->GetSize(SIZE_DRECT_TOP);
					td.Align = TXA_VTOP | TXA_HCENTER;			td.ColBg = 0x00ffffffL;
					td.ColTxt = 0x00ff0000L;					td.Font = FONT_HELVETICA;
					td.fSize = DefSize(SIZE_TEXT);			td.iSize = 0;
					td.Mode = TXM_TRANSPARENT;					td.RotBL = td.RotCHAR = 0.0;
					td.Style = TXS_NORMAL;						td.text = 0L;
					plots[2] = new Label(this, data, x, y, &td, 0x0L);
					plots[2]->moveable = 1;
					}
				plots[2]->Command(CMD_SETTEXT, fo, 0L);			free(fo);
				}
			}
		}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Regression line and symbols
Regression::Regression(GraphObj *par, DataObj *d):Plot(par, d)
{
	FileIO(INIT_VARS);
	Id = GO_REGRESSION;
}

Regression::Regression(int src):Plot(0L, 0L)
{
	int i;

	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		//now set parent in all children
		if(rLine) rLine->parent = this;
		if(sde) sde->parent = this;
		if(Symbols)
			for(i = 0; i < nPoints; i++) if(Symbols[i]) Symbols[i]->parent = this;
		}
}

Regression::~Regression()
{
	Command(CMD_FLUSH, 0L, 0L);
	if(name) free(name);		name=0L;
	Undo.InvalidGO(this);
}

double
Regression::GetSize(int select)
{
	return Plot::GetSize(select);
}

bool
Regression::SetSize(int select, double value)
{
	int i;

	switch(select & 0xfff){
	case SIZE_SYMBOL:
	case SIZE_SYM_LINE:
		if(Symbols)	for(i = 0; i < nPoints; i++) 
			if(Symbols[i]) Symbols[i]->SetSize(select, value);
		return true;
	}
	return false;
}

bool
Regression::SetColor(int select, DWORD col)
{
	int i;
	switch(select) {
	case COL_SYM_LINE:
	case COL_SYM_FILL:
		if(Symbols) for(i = 0; i < nPoints; i++) 
			if(Symbols[i]) Symbols[i]->SetColor(select, col);
		return true;
	}
	return false;
}

void
Regression::DoPlot(anyOutput *o)
{
	int i;
	

	parent->Command(CMD_REG_AXISPLOT, (void*)this, o);
	if(use_xaxis || use_yaxis) ApplyAxes(o);
	if(sde){
		if(rLine && sde->Command(CMD_DROP_OBJECT, rLine, o)) rLine = 0L;
		sde->DoPlot(o);
		}
	if(rLine) rLine->DoPlot(o);
	if(Symbols)	for(i = 0; i < nPoints; i++) 
		if(Symbols[i]) Symbols[i]->DoPlot(o);
	if(use_xaxis || use_yaxis) parent->Command(CMD_AXIS, 0L, o);
	dirty = false;
}

bool
Regression::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i, j;
	static MouseEvent *mev;
	bool bEmpty,bRedraw = false;
	LineDEF *ld;

	switch (cmd) {
	case CMD_MOUSE_EVENT:
		if(hidden) return false;
		mev = (MouseEvent *) tmpl;
		switch(mev->Action) {
		case MOUSE_LBUP:
			if(Symbols) for (i = nPoints-1; i >= 0; i--)
				if(Symbols[i] && Symbols[i]->Command(cmd, tmpl, o))return true;
			if(rLine && rLine->Command(cmd, tmpl, o)) return true;
			if(sde && sde->Command(cmd, tmpl, o)) return true;
			break;
			}
		break;
	case CMD_LEGEND:
		if(tmpl && ((GraphObj*)tmpl)->Id == GO_LEGEND && rLine && rLine->Id == GO_REGLINE) {
			ld = rLine->GetLine();
			if(Symbols) {
				for (i = 0; i < nPoints && i < 100; i++)
					if(Symbols[i]) ((Legend*)tmpl)->HasSym(ld, Symbols[i], "Regression");
				}
			else ((Legend*)tmpl)->HasFill(ld, 0L, "Regression");
			return true;
			}
		return false;
	case CMD_MRK_DIRTY:
		if(rLine || sde) Recalc();
		dirty = true;
	case CMD_SETSCROLL:
	case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		return false;
	case CMD_USEAXIS:
		UseAxis(*((int*)tmpl));
		return true;
	case CMD_DROP_OBJECT:
		if(tmpl && ((GraphObj*)tmpl)->Id == GO_REGLINE && !rLine) {
			rLine = (RegLine *)tmpl;
			rLine->parent = this;
			return true;
			}
		break;
	case CMD_FLUSH:
		if(yRange) free(yRange);		if(xRange) free(xRange);
		yRange = xRange = 0L;
		if(rLine) DeleteGO(rLine);
		if(sde) DeleteGO(sde);
		rLine = 0L;		sde = 0L;
		if(Symbols) for (i = nPoints-1; i >= 0; i--)
			if(Symbols[i]) DeleteGO(Symbols[i]);
		if(Symbols) free(Symbols);
		Symbols = 0L;
		return true;
	case CMD_AUTOSCALE:
		if(dirty){
			Bounds.Xmin = Bounds.Ymin = HUGE_VAL;
			Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
			}
		else return true;
		dirty = false;
	case CMD_SET_DATAOBJ:
		if(cmd == CMD_SET_DATAOBJ) {
			Id = GO_REGRESSION;
			data = (DataObj *)tmpl;	
			}
		if(rLine) rLine->Command(cmd, tmpl, o);
		if(Symbols) for (i = 0; i < nPoints; i++)
			if(Symbols[i]) Symbols[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_SCALE:
		if(rLine) rLine->Command(cmd, tmpl, o);
		if(sde) sde->Command(cmd, tmpl, o);
	case CMD_SYMTEXT:		case CMD_SYM_RANGETEXT:
	case CMD_SYMTEXTDEF:	case CMD_SYM_TYPE:
		if(Symbols) for(i = 0; i < nPoints; i++)
			if(Symbols[i]) Symbols[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_SAVE_SYMBOLS:
		return SavVarObs((GraphObj **)Symbols, nPoints, 0L);
	case CMD_UPDATE:
		if(Symbols) {
			SavVarObs((GraphObj**)Symbols, nPoints, UNDO_CONTINUE);
			for(i = 0; i < nPoints; i++)
				if(Symbols[i]) Symbols[i]->Command(cmd, tmpl, o);
			if(rLine || sde) Recalc();
			return true;
			}
		return false;
	case CMD_DELOBJ:
		if(!parent || !o) return false;
		dirty = bEmpty = bRedraw = false;
		if(Symbols) for(i = 0; i < nPoints; i++)
			if(Symbols[i] && (void*)Symbols[i] == tmpl) {
				bRedraw = true;
				o->HideMark();
				Undo.DeleteGO((GraphObj**)(&Symbols[i]), 0L, o);
				for(j = 0, bEmpty = true; j < nPoints; j++) {
					if(Symbols[j]) {
						bEmpty = false;
						break;
						}
					}
				if(!bEmpty && dirty) Command(CMD_AUTOSCALE, 0L, o);
				break;
				}
		if(rLine && (void*)rLine == tmpl) {
			Undo.DeleteGO((GraphObj**)(&rLine), 0L, o);
			if(!Symbols && !sde) parent->Command(CMD_DELOBJ_CONT, this, o);
			else bRedraw = true;
			}
		if(sde && (void*)sde == tmpl) {
			sde->Command(CMD_RMU, 0L, 0L);
			Undo.DeleteGO((GraphObj**)(&sde), 0L, o);
			if(!Symbols && !rLine) parent->Command(CMD_DELOBJ_CONT, this, o);
			else bRedraw = true;
			}
		if(bEmpty && Symbols) {
			Undo.DropMemory(this, (void**)(&Symbols), UNDO_CONTINUE);
			bRedraw = false;
			if(!rLine && !sde) parent->Command(CMD_DELOBJ_CONT, this, o);
			else bRedraw = true;
			}
		if(bRedraw)parent->Command(CMD_REDRAW, 0L, o);
		return bRedraw;
		}
	return false;
}

void
Regression::Recalc()
{
	int i, j;
	long n;
	bool dValid;
	lfPOINT *val;

	if(nPoints <2 || !Symbols || 
		!(val = (lfPOINT*)calloc(nPoints, sizeof(lfPOINT)))) return;
	for(i = 0, n = 0; i < nPoints; i++){
		if(Symbols[i] && Symbols[i]->Id == GO_SYMBOL) {
			val[j = (int)n].fx = Symbols[i]->GetSize(SIZE_XPOS);
			val[j].fy = Symbols[i]->GetSize(SIZE_YPOS);
			dValid = true;
			switch(type & 0x700) {
			case 0x100:					//logarithmic x
				if(dValid = val[j].fx > defs.min4log) val[j].fx = log10(val[j].fx);
				break;
			case 0x200:					//reciprocal x
				if(dValid = fabs(val[j].fx) >defs.min4log) val[j].fx = 1.0/val[j].fx;
				break;
			case 0x300:					//square root x
				if(dValid = fabs(val[j].fx) >defs.min4log) val[j].fx = sqrt(val[j].fx);
				break;
				}
			if(dValid) switch(type & 0x7000) {
			case 0x1000:				//logarithmic y
				if(dValid = val[j].fy > defs.min4log) val[j].fy = log10(val[j].fy);
				break;
			case 0x2000:				//reciprocal y
				if(dValid = fabs(val[j].fy) >defs.min4log) val[j].fy = 1.0/val[j].fy;
				break;
			case 0x3000:				//square root y
				if(dValid = fabs(val[j].fy) >defs.min4log) val[j].fy = sqrt(val[j].fy);
				break;
				}
			if(dValid) n++;
			}
		}
	if(sde && sde->Id == GO_SDELLIPSE) sde->Recalc(val, n);
	if(rLine && rLine->Id == GO_REGLINE) rLine->Recalc(val, n);
	free(val);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// BubblePlot is a Plot-Class
BubblePlot::BubblePlot(GraphObj *par, DataObj *d):Plot(par, d)
{
	FileIO(INIT_VARS);
	Id = GO_BUBBLEPLOT;
	if (!d && parent) parent->Command(CMD_DELOBJ, this, NULL);
}

BubblePlot::BubblePlot(int src):Plot(0L, 0L)
{
	long i;

	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		BubbleFill.hatch = &BubbleFillLine;
		if(Bubbles)for(i = 0; i< nPoints; i++) {
			if(Bubbles[i])Bubbles[i]->parent = this;
			}
		}
}

BubblePlot::~BubblePlot()
{
	int i;

	if(Bubbles) {
		for(i = 0; i < nPoints; i++) if(Bubbles[i]) DeleteGO(Bubbles[i]);
		free (Bubbles);
		}
	if(name) free(name);		name=0L;
	Undo.InvalidGO(this);
}

DWORD
BubblePlot::GetColor(int select)
{
	switch(select) {
	case COL_BUBBLE_FILL:			return BubbleFill.color;
	case COL_BUBBLE_LINE:			return BubbleLine.color;
	case COL_BUBBLE_FILLLINE:	   	return BubbleFillLine.color;
	default:
		return Plot::GetColor(select);
	}
}

void
BubblePlot::DoPlot(anyOutput *o)
{
	int i;

	parent->Command(CMD_REG_AXISPLOT, (void*)this, o);
	if(use_xaxis || use_yaxis) {
		ApplyAxes(o);
		if(Bubbles) for(i = 0; i < nPoints; i++) 
			if(Bubbles[i]) Bubbles[i]->DoPlot(o);
		parent->Command(CMD_AXIS, 0L, o);
		}
	else {
		if(Bubbles) for(i = 0; i < nPoints; i++) 
			if(Bubbles[i]) Bubbles[i]->DoPlot(o);
		}
	dirty = false;
}

bool
BubblePlot::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i;
	static MouseEvent *mev;
	GraphObj **tmpPlots;

	switch (cmd) {
	case CMD_MOUSE_EVENT:
		if(hidden) return false;
		mev = (MouseEvent *) tmpl;
		switch(mev->Action) {
		case MOUSE_LBUP:
			//select objects invers to plot order
			if(Bubbles && !CurrGO) for(i = nPoints-1; i >=0; i--)
				if(Bubbles[i]) if(Bubbles[i]->Command(cmd, tmpl, o))break;
			break;
			}
		break;
	case CMD_REPL_GO:
		if((tmpPlots = (GraphObj **)tmpl) && tmpPlots[0] && tmpPlots[1] && Bubbles) {
			for(i = 0; i < nPoints; i++) if(Bubbles[i] && Bubbles[i] == tmpPlots[0]) { 
				return ReplaceGO((GraphObj**)&Bubbles[i], tmpPlots);
				}
			}
		return false;
	case CMD_LEGEND:
		if(((GraphObj*)tmpl)->Id != GO_LEGEND) return false;
		if(Bubbles) for (i = 0; i < nPoints; i++)
			if(Bubbles[i]) Bubbles[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_SCALE:
		if(!tmpl) return false;
		BubbleLine.width *= ((scaleINFO*)tmpl)->sy.fy;
		BubbleLine.patlength *= ((scaleINFO*)tmpl)->sy.fy;
		BubbleFillLine.width *= ((scaleINFO*)tmpl)->sy.fy;
		BubbleFillLine.patlength *= ((scaleINFO*)tmpl)->sy.fy;
		BubbleFill.scale *= ((scaleINFO*)tmpl)->sy.fy;
		if(Bubbles) for(i = 0; i < nPoints; i++)
			if(Bubbles[i]) Bubbles[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_MRK_DIRTY:
		dirty = true;
	case CMD_SETSCROLL:		case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		return false;
	case CMD_USEAXIS:
		UseAxis(*((int*)tmpl));
		return true;
	case CMD_SET_DATAOBJ:
		Id = GO_BUBBLEPLOT;
		data = (DataObj *)tmpl;
	case CMD_UPDATE:
		if(cmd == CMD_UPDATE && Bubbles) SavVarObs((GraphObj **)Bubbles, nPoints, UNDO_CONTINUE);
	case CMD_AUTOSCALE:
		if(cmd == CMD_AUTOSCALE && Bubbles) {
			Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;		Bounds.Xmin = Bounds.Ymin = HUGE_VAL;
			}
	case CMD_BUBBLE_ATTRIB:		case CMD_BUBBLE_TYPE:		case CMD_BUBBLE_FILL:
	case CMD_BUBBLE_LINE:
		if(Bubbles) for(i = 0; i < nPoints; i++)
			if(Bubbles[i]) Bubbles[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_DELOBJ:
		if(Bubbles && parent) for(i = 0; i < nPoints; i++) {
			o->HideMark();
			if(Bubbles[i] && tmpl == (void *)Bubbles[i]) {
				Undo.DeleteGO((GraphObj**)(&Bubbles[i]), 0L, o);
				parent->Command(CMD_REDRAW, 0L, o);
				return true;
				}
			}
		break;
	case CMD_SAVE_SYMBOLS:
		return SavVarObs((GraphObj **)Bubbles, nPoints, 0L);
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// PolarPlot is a Plot-Class
PolarPlot::PolarPlot(GraphObj *par, DataObj *d):Plot(par, d)
{
	FileIO(INIT_VARS);
	Id = GO_POLARPLOT;
	if (!d && parent) parent->Command(CMD_DELOBJ, this, NULL);
}

PolarPlot::PolarPlot(int src):Plot(0L, 0L)
{
	int i;

	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		Fill.hatch = &FillLine;
		//now set parent in all children
		if(Plots) 
			for(i = 0; i < nPlots; i++) if(Plots[i]) Plots[i]->parent = this;
		if(Axes) 
			for(i = 0; i < nAxes; i++) if(Axes[i]) Axes[i]->parent = this;
		}
}

PolarPlot::~PolarPlot()
{
	int i;

	if(Plots){
		for(i = 0; i < nPlots; i++) if(Plots[i]) DeleteGO(Plots[i]);
		free(Plots);		Plots = 0L;
		}
	if(Axes){
		for(i = 0; i < nAxes; i++) if(Axes[i]) DeleteGO(Axes[i]);
		free(Axes);			Axes = 0L;
		}
	if(name) free(name);	name=0L;
	Undo.InvalidGO(this);
}

double
PolarPlot::GetSize(int select)
{
	switch(select) {
	case SIZE_BOUNDS_XMIN:		return Bounds.Xmin;
	case SIZE_BOUNDS_XMAX:		return Bounds.Xmax;
	case SIZE_BOUNDS_YMIN:		return Bounds.Ymin;
	case SIZE_BOUNDS_YMAX:		return Bounds.Ymax;
	case SIZE_BOUNDS_LEFT:		return (Axes && Axes[0])?(((Axis*)Axes[0])->GetAxis())->min:0.0;
	case SIZE_BOUNDS_RIGHT:		return (Axes && Axes[0])?(((Axis*)Axes[0])->GetAxis())->max:0.0;
	case SIZE_BOUNDS_TOP:		return (Axes && Axes[1])?(((Axis*)Axes[1])->GetAxis())->max:0.0;
	case SIZE_BOUNDS_BOTTOM:	return (Axes && Axes[1])?(((Axis*)Axes[1])->GetAxis())->min:0.0;
	case SIZE_YAXISX:
		if(!CurrDisp) return 0.0;
		if((((Axis*)Axes[1])->GetAxis())->flags & AXIS_X_DATA) 
			return CurrDisp->fx2fix((((Axis*)Axes[1])->GetAxis())->loc[0].fx);
		else return CurrDisp->co2fix((((Axis*)Axes[1])->GetAxis())->loc[0].fx);
	case SIZE_XAXISY:
		if(!CurrDisp) return 0.0;
		if((((Axis*)Axes[0])->GetAxis())->flags & AXIS_Y_DATA) 
			return CurrDisp->fy2fiy((((Axis*)Axes[0])->GetAxis())->loc[0].fy);
		else return CurrDisp->co2fiy((((Axis*)Axes[0])->GetAxis())->loc[0].fy);
	case SIZE_XCENTER:			return (((Axis*)Axes[0])->GetAxis())->Center.fx;
	case SIZE_YCENTER:			return (((Axis*)Axes[0])->GetAxis())->Center.fy;
	default:
		if(parent) return parent->GetSize(select);
		}
	return DefSize(select);
}

void
PolarPlot::DoPlot(anyOutput *o)
{
	int i;

	if(o) CurrDisp = o;
	else return;
	if(!parent) return;
	CurrRect.Xmin = CurrRect.Xmax = (((Axis*)Axes[1])->GetAxis())->Center.fx + 
		parent->GetSize(SIZE_GRECT_LEFT);
	CurrRect.Xmin -= (((Axis*)Axes[0])->GetAxis())->Radius;	
	CurrRect.Xmax += (((Axis*)Axes[0])->GetAxis())->Radius;
	CurrRect.Ymin = CurrRect.Ymax = (((Axis*)Axes[0])->GetAxis())->Center.fy +
		parent->GetSize(SIZE_GRECT_TOP);
	CurrRect.Ymin -= (((Axis*)Axes[0])->GetAxis())->Radius;
	CurrRect.Ymax += (((Axis*)Axes[0])->GetAxis())->Radius;
	(((Axis*)Axes[0])->GetAxis())->Start = ((((Axis*)Axes[0])->GetAxis())->flags & AXIS_INVERT) ? -offs : offs;
	o->SetRect(CurrRect, defs.cUnits, ((Axis*)Axes[0])->GetAxis(), ((Axis*)Axes[1])->GetAxis());
	o->SetFill(&Fill);
	if(Axes) for(i = 0; i < nAxes; i++) {
		if(i == 1) {
			if(!(type & 0x01) && Axes[i]) Axes[i]->DoPlot(o);
			}
		else if(Axes[i]) Axes[i]->DoPlot(o);
		}
	if(Plots) for(i = 0; i < nPlots; i++) if(Plots[i]) {
		if(Plots[i]->Id >= GO_PLOT && Plots[i]->Id < GO_GRAPH) {
			if(((Plot*)Plots[i])->hidden == 0) Plots[i]->DoPlot(o);
			}
		else Plots[i]->DoPlot(o);
		}
	rDims.left = o->co2ix(CurrRect.Xmin);	rDims.right = o->co2ix(CurrRect.Xmax);
	rDims.top = o->co2iy(CurrRect.Ymin);	rDims.bottom = o->co2iy(CurrRect.Ymax);
	if(parent) parent->Command(CMD_AXIS, 0L, o);
}

bool
PolarPlot::Command(int cmd, void *tmpl, anyOutput *o)
{
	GraphObj **tmpPlots;
	int i;
	AxisDEF *ad0, *ad1;
	double tmp;

	switch (cmd) {
	case CMD_CONFIG:
		Config();
		return true;
	case CMD_SCALE:
		FillLine.width *= ((scaleINFO*)tmpl)->sy.fy;
		FillLine.patlength *= ((scaleINFO*)tmpl)->sy.fy;
		Fill.scale *= ((scaleINFO*)tmpl)->sy.fy;
		if(Axes) for(i = 0; i< nAxes; i++) if(Axes[i]) Axes[i]->Command(cmd, tmpl, o);
		if(Plots) for(i = 0; i < nPlots; i++) if(Plots[i]) Plots[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_SET_DATAOBJ:
		Id = GO_POLARPLOT;
		data = (DataObj *)tmpl;	
	case CMD_UPDATE:
		if(Axes) for(i = 0; i< nAxes; i++) if(Axes[i]) Axes[i]->Command(cmd, tmpl, o);
	case CMD_LEGEND:
		if(Plots) for(i = 0; i < nPlots; i++) if(Plots[i]) Plots[i]->Command(cmd, tmpl, o);
		return false;
	case CMD_OBJTREE:
		if(!tmpl) return false;
		if(Plots) for(i = 0; i < nPlots; i++) if(Plots[i]) 
			((ObjTree*)tmpl)->Command(CMD_UPDATE, Plots[i], 0L);
		return true;
	case CMD_MOUSE_EVENT:
		if(hidden) return false;
		if(o) switch(((MouseEvent*)tmpl)->Action) {
		case MOUSE_LBUP:
			if(Axes) for(i = 0; i< nAxes; i++) if(Axes[i])
				if(Axes[i]->Command(cmd, tmpl, o))return true;
			if(Plots) for(i = 0; i < nPlots; i++) if(Plots[i]) 
				Plots[i]->Command(cmd, tmpl, o);
			if(!CurrGO && IsInRect(&rDims, ((MouseEvent*)tmpl)->x, ((MouseEvent*)tmpl)->y)){
				CurrGO = this;
				o->ShowMark(&rDims, MRK_INVERT);
				return true;
				}
			break;
			}
		return false;
	case CMD_REPL_GO:
		if(!(tmpPlots = (GraphObj **)tmpl) || !tmpPlots[0] || !tmpPlots[1]) return false;
		if(Axes) for(i = 0; i < nAxes; i++) if(Axes[i] && Axes[i] == tmpPlots[0]){
			return ReplaceGO(&Axes[i], tmpPlots);
			}
		break;
	case CMD_SETSCROLL:		case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		return false;
	case CMD_DELOBJ_CONT:	case CMD_DELOBJ:
		if(Plots && nPlots) for(i = 0; i < nPlots; i++) if(tmpl == (void*)Plots[i]) {
			Undo.DeleteGO((GraphObj**)(&Plots[i]), cmd == CMD_DELOBJ_CONT ? UNDO_CONTINUE : 0L, o);
			if(parent)parent->Command(CMD_REDRAW, NULL, o);
			return true;
			}
		if(Axes && nAxes > 1 && (tmpl == (void*)Axes[0] || tmpl == (void*)Axes[1]))
			InfoBox("Axes required for scaling\ncannot be deleted.");
		break;
	case CMD_AXIS:		//axis changed: reconstruct corresponding axis
		if(Axes && nAxes >1 && tmpl && Axes[0] && Axes[1]) {
			ad0 = ((Axis*)Axes[0])->GetAxis();		ad1 = ((Axis*)Axes[1])->GetAxis();
			if(tmpl == Axes[0]) {
				CheckNewFloat(&ad1->loc[1].fy, ad1->loc[1].fy, ad0->Center.fy, this, UNDO_CONTINUE);
				tmp = ad1->loc[1].fy - ad0->Radius;
				CheckNewFloat(&ad1->loc[0].fy, ad1->loc[0].fy, tmp, this, UNDO_CONTINUE);
				}
			if(tmpl == Axes[1]) {
				CheckNewFloat(&ad0->Center.fy, ad0->Center.fy, ad1->loc[1].fy, this, UNDO_CONTINUE);
				tmp = fabs(ad1->loc[1].fy - ad1->loc[0].fy);
				CheckNewFloat(&ad0->Radius, ad0->Radius, tmp, this, UNDO_CONTINUE);
				}
			}
		break;	
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// BoxPlot is a Plot-Class
BoxPlot::BoxPlot(GraphObj *par, DataObj *d):Plot(par, d)
{
	FileIO(INIT_VARS);
	Id = GO_BOXPLOT;
}

BoxPlot::BoxPlot(int src):Plot(0L, 0L)
{
	int i;

	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		//now set parent in all children
		if(Boxes) 
			for(i = 0; i < nPoints; i++) if(Boxes[i]) Boxes[i]->parent = this;
		if(Whiskers) 
			for(i = 0; i < nPoints; i++) if(Whiskers[i]) Whiskers[i]->parent = this;
		if(Symbols) 
			for(i = 0; i < nPoints; i++) if(Symbols[i]) Symbols[i]->parent = this;
		if(Labels) 
			for(i = 0; i < nPoints; i++) if(Labels[i]) Labels[i]->parent = this;
		if(TheLine) TheLine->parent = this;
		}
}

BoxPlot::BoxPlot(GraphObj *par, DataObj *dt, int mode, int c1, int c2, int c3, char *box_name):Plot(par, dt)
{
	int i, nr, cb;
	lfPOINT fp;

	FileIO(INIT_VARS);		Id = GO_BOXPLOT;	fp.fx = fp.fy = 0.0;	cb = 0;
	if(data && data->GetSize(&i, &nr)) {
		nPoints = nr;		if(box_name) cb = (int)strlen(box_name);
		Bounds.Xmin = Bounds.Ymin = HUGE_VAL;
		Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
		if(Boxes = (Box**)calloc(nr, sizeof(Box*))) for(i = 0; i < nr; i++) {
			if(mode == 1) Boxes[i] = new Box(this, data, fp, fp, BAR_RELWIDTH, c1, i, c2, i, c1, i, c3, i);
			else Boxes[i] = new Box(this, data, fp, fp, BAR_RELWIDTH, c2, i, c1, i, c3, i, c1, i);
			if(box_name && box_name[0] && Boxes[i]) Boxes[i]->name = (char*)memdup(box_name, cb+1, 0);
			}
		}
}

BoxPlot::~BoxPlot()
{
	int i;

	if(Whiskers) {
		for(i = 0; i < nPoints; i++) if(Whiskers[i]) DeleteGO(Whiskers[i]);
		free (Whiskers);
		}
	if(Boxes) {
		for(i = 0; i < nPoints; i++) if(Boxes[i]) DeleteGO(Boxes[i]);
		free (Boxes);
		}
	if(Symbols) {
		for(i = 0; i < nPoints; i++) if(Symbols[i]) DeleteGO(Symbols[i]);
		free (Symbols);
		}
	if(Labels) {
		for(i = 0; i < nPoints; i++) if(Labels[i]) DeleteGO(Labels[i]);
		free (Labels);
		}
	if(TheLine) DeleteGO(TheLine);
	if(curr_data) delete curr_data;		curr_data = 0L;
	if(xRange) free(xRange);			xRange = 0L;
	if(yRange) free(yRange);			yRange = 0L;
	if(x_info) free(x_info);			x_info = 0L;
	if(y_info) free(y_info);			y_info = 0L;
	if(case_prefix) free(case_prefix);	case_prefix = 0L;
	if(name) free(name);				name = 0L;
	if(x_tv) delete(x_tv);				x_tv = 0;
	if(y_tv) delete(y_tv);				y_tv = 0;
	Undo.InvalidGO(this);
}

double
BoxPlot::GetSize(int select)
{
	int i;
	double ft1, ft2, d;

	switch(select){
	case SIZE_BOXMINX:
		if(BoxDist.fx >= 0.0001) return BoxDist.fx;
		if((!Boxes) || (nPoints < 2)) return BoxDist.fx = 1.0;
		ft1 = -HUGE_VAL;	ft2 = HUGE_VAL;		BoxDist.fx= HUGE_VAL;
		for(i = 0; i < nPoints; i++) {
			if(Boxes[i]) {
				ft2 = Boxes[i]->GetSize(SIZE_XPOS);
				d = fabs(ft2-ft1);
				if(d != 0.0 && d < BoxDist.fx) BoxDist.fx = d;
				}
			ft1 = ft2;
			}
		return BoxDist.fx = BoxDist.fx > 0.0001 && BoxDist.fx != HUGE_VAL  ? BoxDist.fx : 1.0;
	case SIZE_BOXMINY:
		if(BoxDist.fy >= 0.0001) return BoxDist.fy;
		if((!Boxes) || (nPoints < 2)) return BoxDist.fy = 1.0;
		ft1 = -HUGE_VAL;	ft2 = HUGE_VAL;		BoxDist.fy= HUGE_VAL;
		for(i = 0; i < nPoints; i++) {
			if(Boxes[i]) {
				ft2 = Boxes[i]->GetSize(SIZE_YPOS);
				d = fabs(ft2-ft1);
				if(d != 0.0 && d < BoxDist.fy) BoxDist.fy = d;
				}
			ft1 = ft2;
			}
		return BoxDist.fy = BoxDist.fy > 0.0001 && BoxDist.fy != HUGE_VAL  ? BoxDist.fy : 1.0;
	default:
		return Plot::GetSize(select);
		}
}

bool
BoxPlot::SetSize(int select, double value)
{
	int i;

	switch(select & 0xfff){
	case SIZE_SYMBOL:		case SIZE_SYM_LINE:
		if(Symbols) for(i = 0; i < nPoints; i++) 
			if(Symbols[i]) Symbols[i]->SetSize(select, value);
		return true;
	case SIZE_WHISKER:		case SIZE_WHISKER_LINE:
		if(Whiskers) for(i = 0; i < nPoints; i++) 
			if(Whiskers[i]) Whiskers[i]->SetSize(select, value);
		return true;
	case SIZE_BOX:			case SIZE_BOX_LINE:
		if(Boxes) for(i = 0; i < nPoints; i++) 
			if(Boxes[i]) Boxes[i]->SetSize(select, value);
		return true;
	case SIZE_LB_XDIST:		case SIZE_LB_YDIST:
		if(Labels) for(i = 0; i < nPoints; i++)
			if(Labels[i]) Labels[i]->SetSize(select, value);
		return true;
	}
	return false;
}

bool
BoxPlot::SetColor(int select, DWORD col)
{
	int i;

	switch(select) {
	case COL_SYM_LINE:		case COL_SYM_FILL:
		if(Symbols)	for(i = 0; i < nPoints; i++) 
			if(Symbols[i]) Symbols[i]->SetColor(select, col);
		return true;
	case COL_WHISKER:
		if(Whiskers) for(i = 0; i < nPoints; i++)
			if(Whiskers[i]) Whiskers[i]->SetColor(select, col);
		return true;
	case COL_BOX_LINE:
		if(Boxes) for(i = 0; i < nPoints; i++)
			if(Boxes[i]) Boxes[i]->SetColor(select, col);
		return true;
	default:
		return false;
		}
}

void
BoxPlot::DoPlot(anyOutput *o)
{
	if(!parent || !o) return;
	parent->Command(CMD_REG_AXISPLOT, (void*)this, o);
	if(use_xaxis || use_yaxis) ApplyAxes(o);
	ForEach(FE_PLOT, 0L, o);
	dirty = false;
	if(use_xaxis || use_yaxis)parent->Command(CMD_AXIS, 0L, o);
}

bool
BoxPlot::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i;

	switch (cmd) {
	case CMD_MOUSE_EVENT:
		if(hidden) return false;
		if(!CurrGO && ((MouseEvent*)tmpl)->Action == MOUSE_LBUP) return ForEach(cmd, tmpl, o);
		return false;
	case CMD_TEXTTHERE:
		if(Labels) for(i = 0; i < nPoints; i++)	if(Labels[i] &&  Labels[i]->Command(cmd, tmpl, o))	return true;
		return false;
	case CMD_LEGEND:
		if(((GraphObj*)tmpl)->Id != GO_LEGEND) return false;
		if(Symbols) {
			if(TheLine && TheLine->Id == GO_DATALINE) {
				for (i = 0; i < nPoints && i < 100; i++)
					if(Symbols[i]) ((Legend*)tmpl)->HasSym(&TheLine->LineDef, Symbols[i], 0L);
				}
			else {
				for (i = 0; i < nPoints && i < 100; i++)
					if(Symbols[i]) ((Legend*)tmpl)->HasSym(0L, Symbols[i], 0L);
				}
			if(TheLine && TheLine->Id == GO_DATAPOLYGON) TheLine->Command(cmd, tmpl, o);
			}
		else if(TheLine) TheLine->Command(cmd, tmpl, o);
		if(Boxes) for (i = 0; i < nPoints; i++)	if(Boxes[i]) Boxes[i]->Command(cmd, tmpl, o);
		if(Whiskers) for (i = 0; i < nPoints; i++)	if(Whiskers[i]) Whiskers[i]->Command(cmd, tmpl, o);
		break;
	case CMD_SET_DATAOBJ:
		Id = GO_BOXPLOT;		data = (DataObj *)tmpl;		dirty = true;
		if(type && xRange && yRange && data) {		//Stat. - Plot ?
			CreateData();
			return ForEach(CMD_SET_DATAOBJ, curr_data, o);
			}
	case CMD_SCALE:
		return ForEach(cmd, tmpl, o);
	case CMD_AUTOSCALE:
		if(hidden) return false;
		if(dirty) {
			Bounds.Xmin = Bounds.Ymin = HUGE_VAL;
			Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
			}
		else{
			if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH && 
				Bounds.Xmax > Bounds.Xmin && Bounds.Ymax > Bounds.Ymin) {
				((Plot*)parent)->CheckBounds(Bounds.Xmin, Bounds.Ymin);
				((Plot*)parent)->CheckBounds(Bounds.Xmax, Bounds.Ymax);
				return true;
				}
			}
		dirty = false;
		ForEach(cmd, tmpl, o);
		if(x_tv) {
			Bounds.Xmin = 0.5;		Bounds.Xmax = ((double)x_tv->Count())+0.5;
			}
		if(parent && parent->Id > GO_PLOT && parent->Id < GO_GRAPH
			&& Bounds.Xmax > Bounds.Xmin && Bounds.Ymax > Bounds.Ymin){
			((Plot*)parent)->CheckBounds(Bounds.Xmin, Bounds.Ymin);
			((Plot*)parent)->CheckBounds(Bounds.Xmax, Bounds.Ymax);
			}
		return true;
	case CMD_UPDATE:
		if(parent) {
			if(Boxes) SavVarObs((GraphObj **)Boxes, nPoints, UNDO_CONTINUE);
			if(Whiskers) SavVarObs((GraphObj **)Whiskers, nPoints, UNDO_CONTINUE);
			if(Symbols) SavVarObs((GraphObj **)Symbols, nPoints, UNDO_CONTINUE);
			if(Labels) SavVarObs((GraphObj **)Labels, nPoints, UNDO_CONTINUE); 
			}
		if(type && xRange && yRange) {		//Stat. - Plot ?
			CreateData();
			ForEach(CMD_SET_DATAOBJ, curr_data, o);
			}
		ForEach(cmd, tmpl, o);
		return dirty = true;
	case CMD_USEAXIS:
		UseAxis(*((int*)tmpl));
		return true;
	case CMD_MRK_DIRTY:
		dirty = true;
	case CMD_SETSCROLL:		case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		return false;
	case CMD_SETTEXTDEF:
		if(Labels) for(i = 0; i < nPoints; i++)
			if(Labels[i]) Labels[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_DELOBJ:
		if(ForEach(FE_DELOBJ, tmpl, o)) {
			parent->Command(CMD_REDRAW, 0L, o);
			return true;
			}
		return false;
	case CMD_SYMTEXT:		case CMD_SYM_RANGETEXT:
	case CMD_SYMTEXTDEF:	case CMD_SYM_TYPE:
		if(Symbols) for(i = 0; i < nPoints; i++)
			if(Symbols[i]) Symbols[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_SAVE_SYMBOLS:
		return SavVarObs((GraphObj **)Symbols, nPoints, 0L);
	case CMD_SAVE_BARS:
		return SavVarObs((GraphObj **)Boxes, nPoints, 0L);
	case CMD_SAVE_BARS_CONT:
		return SavVarObs((GraphObj **)Boxes, nPoints, UNDO_CONTINUE);
	case CMD_SAVE_ERRS:
		return SavVarObs((GraphObj **)Whiskers, nPoints, 0L);
	case CMD_SAVE_LABELS:
		return SavVarObs((GraphObj **)Labels, nPoints, 0L);
	case CMD_BOX_TYPE:
		BoxDist.fy = BoxDist.fx = 0.0;
	case CMD_BOX_FILL:
		if(Boxes) for (i = 0; i < nPoints; i++)
			if(Boxes[i]) Boxes[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_WHISKER_STYLE:		case CMD_ERRDESC:
		if(Whiskers) for (i = 0; i < nPoints; i++)
			if(Whiskers[i]) Whiskers[i]->Command(cmd, tmpl, o);
		return true;
		}
	return false;
}

bool
BoxPlot::ForEach(int cmd, void *tmpl, anyOutput *o)
{
	GraphObj ***pobs[] = {(GraphObj***)&Boxes, (GraphObj***)&Whiskers, 
		(GraphObj***)&Symbols, (GraphObj***)&Labels};
	GraphObj **p;
	int i, j;
	bool bRet;

	switch(cmd) {
	case FE_DELOBJ:
		if(!o || !parent || !tmpl) return false;
		for(i = 0; i < 4; i++) {
			if(DeleteGOL(pobs[i], nPoints, (GraphObj*) tmpl, o)) return true;
			}
		if(TheLine && tmpl == (void *) TheLine) {
			Undo.DeleteGO((GraphObj**)(&TheLine), 0L, o);
			return true;
			}
		break;
	case FE_PLOT:
		if(TheLine) TheLine->DoPlot(o);
		for(i = 0; i < 4; i++){
			if(p= *pobs[i]) for(j = 0; j < nPoints; j++) {
				if(p[j]) p[j]->DoPlot(o);
				}
			}
		return true;
	case CMD_MOUSE_EVENT:				//invers to plot order
		for(i = 3; i >= 0; i--){
			if(p= *pobs[i]) for(j = nPoints-1; j >= 0; j--) {
				if(p[j]) {
					bRet = p[j]->Command(cmd, tmpl, o);
					if(bRet && cmd == CMD_MOUSE_EVENT) return true;
					}
				}
			}
		if(TheLine) return TheLine->Command(cmd, tmpl, o);
		return false;
	default:							//pass command to all objects
		for(i = 0; i < 4; i++){
			if(p= *pobs[i]) for(j = 0; j < nPoints; j++) {
				if(p[j]) {
					bRet = p[j]->Command(cmd, tmpl, o);
					}
				}
			}
		if(TheLine) return TheLine->Command(cmd, tmpl, o);
		return false;
		}
	return false;
}

void
BoxPlot::CreateData()
{
	int i, j, k, l, m, n, *ny, c_num, c_txt, c_dattim;
	double y, ss, d, lo, hi, **ay, *ax, *tay, *q1, *q2, *q3;
	lfPOINT *xy;
	AccRange *rX, *rY;
	anyResult x_res, y_res;

	if(curr_data) delete curr_data;			curr_data = 0L;
	if(!data || !xRange || !yRange || !xRange[0] || !yRange[0]) return;
	if(!(rX = new AccRange(xRange)) || !(rY = new AccRange(yRange))) return;
	m = rX->CountItems();	n = 0;
	if(m < 2 || !(xy = (lfPOINT*) malloc(m * sizeof(lfPOINT)))) {
		delete rX;	delete rY;
		return;
		}
	if(x_tv) delete x_tv;					x_tv = 0L;
	ny = (int*) calloc(m, sizeof(int));
	ay = (double**) calloc(m, sizeof(double*));
	ax = (double*) calloc(m, sizeof(double));
	tay = (double*)malloc(m * sizeof(double));
	if(!ny || !ay || !ax || !tay) {
		if(ny) free(ny);	if(ay) free(ay);
		if(ax) free(ax);	if(tay) free(tay);
		delete rX;	delete rY;
		return;
		}
	rX->DataTypes(data, &c_num, &c_txt, &c_dattim);
	if(c_num < 5 && (c_txt + c_dattim) > 5) {
		x_tv = new TextValue();	
		}
	rX->GetFirst(&i, &j);	rY->GetFirst(&k, &l);
	rX->GetNext(&i, &j);	rY->GetNext(&k, &l);	n=0;
	do {
		if(data->GetResult(&x_res, j, i, false) && data->GetResult(&y_res, l, k, false) && y_res.type == ET_VALUE) {
			xy[n].fy = y_res.value;
			if(x_tv){ 
				switch(x_res.type) {
				case ET_TEXT:
					xy[n++].fx = x_tv->GetValue(x_res.text);
					break;
				case ET_VALUE:	case ET_BOOL:	case ET_DATE:	case ET_TIME:	case ET_DATETIME:
					TranslateResult(&x_res);
					xy[n++].fx = x_tv->GetValue(x_res.text);
					break;
					}
				}
			else if(x_res.type == ET_VALUE) xy[n++].fx = x_res.value;
			}
		}while(rX->GetNext(&i, &j) && rY->GetNext(&k, &l));
	delete rX;			delete rY;
	if(!n) {
		if(ny) free(ny);	if(ay) free(ay);
		if(ax) free(ax);	if(tay) free(tay);
		return;
		}
	SortFpArray(n, xy);
	for(i = j = 0; i < (n-1); i++, j++) {
		ax[j] = xy[i].fx;		tay[0] = xy[i].fy;
		ny[j] = 1;
		for(k = 1; xy[i+1].fx == xy[i].fx; k++) {
			tay[k] = xy[i+1].fy;
			i++;		ny[j]++;
			}
		ay[j] = (double*)memdup(tay, k * sizeof(double), 0);
		}
	if(xy[i].fx > xy[i-1].fx) {
		ax[j] = xy[i].fx;		tay[0] = xy[i].fy;
		ny[j] = 1;
		ay[j++] = (double*)memdup(tay, sizeof(double), 0);
		}
	if((type & 0x0004) == 0x0004 || (type & 0x0030) == 0x0030 || (type & 0x0300) == 0x0300) {
		//medians and/or percentiles required
		q1 = (double *)malloc(j * sizeof(double));
		q2 = (double *)malloc(j * sizeof(double));
		q3 = (double *)malloc(j * sizeof(double));
		if(q1 && q2 && q3) {
			for(i = 0; i < j; i++) {
				if(ny[i] > 1) d_quartile(ny[i], ay[i], q1+i, q2+i, q3+i);
				else q1[i] = q2[i] = q3[i] = *ay[i];
				}
			}
		else type = 0;
		}
	else q1 = q2 = q3 = 0L;
	if(type && (curr_data = new DataObj()) && curr_data->Init(j, 8)) {
		for(i = 0; i < j; i++) curr_data->SetValue(i,0,ax[i]);	// set x-values
		for(i = 0; i < j; i++) {								// set means
			if(ny[i] > 1) switch(type & 0x000f) {
				case 0x0001:	default:
					curr_data->SetValue(i, 1, y=d_amean(ny[i], ay[i]));
					break;
				case 0x0002:
					curr_data->SetValue(i, 1, y=d_gmean(ny[i], ay[i]));
					break;
				case 0x0003:
					curr_data->SetValue(i, 1, y=d_hmean(ny[i], ay[i]));
					break;
				case 0x0004:
					curr_data->SetValue(i, 1, y=q2[i]);
					break;
				}
			else curr_data->SetValue(i, 1, y= *ay[i]);
			curr_data->SetValue(i, 6, y);						//label's y
			}
		if((type & 0x00f0) == 0x0010 || (type & 0x00f0) == 0x0020 || (type & 0x00f0) == 0x0050
			|| (type & 0x0f0f) == 0x0201 || (type & 0x0f0f) == 0x0501) for(i = 0; i < j; i++) {
			// set SD, SE, Conf. Intervall
			if(ny[i] > 1) {
				ss = sqrt(d_variance(ny[i], ay[i], &y));
				}
			else {
				y = *ay[i];		ss = 0.0;
				}
			//Box info is in cols 2 & 3
			if((type & 0x00f0) == 0x0010) {
				curr_data->SetValue(i, 2, y - ss);	curr_data->SetValue(i, 3, y + ss);
				}
			else if((type & 0x00f0) == 0x0020) {
				curr_data->SetValue(i, 2, y - ss/sqrt((double)ny[i]));	
				curr_data->SetValue(i, 3, y + ss/sqrt((double)ny[i]));
				}
			else if((type & 0x00f0) == 0x0050) {
				d = ny[i] > 1 ? distinv(t_dist, ny[i]-1, 1, 1.0-(ci_box/100.0), 2.0) : 0;
				curr_data->SetValue(i, 2, y - d*ss/(double)sqrt((double)ny[i]));	
				curr_data->SetValue(i, 3, y + d*ss/(double)sqrt((double)ny[i]));
				}
			//Whisker info is in cols 4 & 5
			if((type & 0x0f0f) == 0x0101) {
				curr_data->SetValue(i, 4, y - ss);	curr_data->SetValue(i, 5, y + ss);
				}
			else if((type & 0x0f0f) == 0x0201) {
				curr_data->SetValue(i, 4, y - ss/sqrt((double)ny[i]));
				curr_data->SetValue(i, 5, y + ss/sqrt((double)ny[i]));
				}
			else if((type & 0x0f0f) == 0x0501) {
				d = ny[i] > 1 ? distinv(t_dist, ny[i]-1, 1, 1.0-(ci_err/100.0), 2.0) : 0;
				curr_data->SetValue(i, 4, y - d*ss/sqrt((double)ny[i]));
				curr_data->SetValue(i, 5, y + d*ss/sqrt((double)ny[i]));
				}
			}
		if((type & 0x00f0) == 0x0040 || (type & 0x0f00) == 0x0400) for(i = 0; i < j; i++) {
			// set min and max
			lo = hi = *ay[i];
 			if(ny[i] > 1) {
				for(k = 1; k < ny[i]; k++) {
					if(ay[i][k] < lo) lo = ay[i][k];
					if(ay[i][k] > hi) hi = ay[i][k];
					}
				}
			if((type & 0x00f0) == 0x0040) {
				curr_data->SetValue(i, 2, lo);	curr_data->SetValue(i, 3, hi);
				}
			if((type & 0x0f00) == 0x0400) {
				curr_data->SetValue(i, 4, lo);	curr_data->SetValue(i, 5, hi);
				}
			}
		if(q1 && q3 && ((type & 0x00f0) == 0x0030 || (type & 0x0f00) == 0x0300)) for(i = 0; i < j; i++) {
			// percentiles ....
			if((type & 0x00f0) == 0x0030) {
				curr_data->SetValue(i, 2, q1[i]);	curr_data->SetValue(i, 3, q3[i]);
				}
			if((type & 0x0f00) == 0x0300) {
				curr_data->SetValue(i, 4, q1[i]);	curr_data->SetValue(i, 5, q3[i]);
				}
			}
		if(type & 0xc000) for(i = 0; i < j; i++) {
			//labels ...
			if((type & 0x4000) && curr_data->GetValue(i, 5, &y)) curr_data->SetValue(i, 6, y);
#ifdef USE_WIN_SECURE
			sprintf_s(TmpTxt, TMP_TXT_SIZE, "%s%d", case_prefix ? case_prefix : "", ny[i]);
#else
			sprintf(TmpTxt, "%s%d", case_prefix ? case_prefix : "", ny[i]);
#endif
			curr_data->SetText(i, 7, TmpTxt);
			}
		}
	else {
		if(curr_data) delete curr_data;
		curr_data = 0L;
		}
	if(q1) free(q1);	if(q2) free(q2);	if(q3) free(q3);
	for(i = 0; i < m; i++) if(ay[i]) free(ay[i]);
	free(tay);	free(ay);	free(ax);	free(ny);	free(xy);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Density distribution plot
DensDisp::DensDisp(GraphObj *par, DataObj *d):Plot(par, d)
{
	FileIO(INIT_VARS);
	Id = GO_DENSDISP;
	if (!d && parent) parent->Command(CMD_DELOBJ, this, NULL);
}

DensDisp::DensDisp(int src):Plot(0L, 0L)
{
	int i;

	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		//now set parent in all children
		if(Boxes) 
			for(i = 0; i < nPoints; i++) if(Boxes[i]) Boxes[i]->parent = this;
		}
}

DensDisp::~DensDisp()
{
	int i;

	if(Boxes) {
		for(i = 0; i < nPoints; i++) if(Boxes[i]) DeleteGO(Boxes[i]);
		free (Boxes);
		}
	if(yRange) free(yRange);		if(xRange) free(xRange);
	yRange = xRange = 0L;
	if(name) free(name);			name=0L;
	if(x_info) free(x_info);		x_info = 0L;
	if(y_info) free(y_info);		y_info = 0L;
	Undo.InvalidGO(this);
}

bool
DensDisp::SetSize(int select, double value)
{
	int i;

	switch(select & 0xfff){
	case SIZE_BOX_LINE:
		if(Boxes) for(i = 0; i < nPoints; i++) 
			if(Boxes[i]) Boxes[i]->SetSize(select, value);
		return true;
	}
	return false;
}

bool
DensDisp::SetColor(int select, DWORD col)
{
	int i;

	switch(select) {
	case COL_BOX_LINE:
		if(Boxes) for(i = 0; i < nPoints; i++)
			if(Boxes[i]) Boxes[i]->SetColor(select, col);
		return true;
	default:
		return false;
		}
}

void
DensDisp::DoPlot(anyOutput *o)
{
	int i;

	if(!parent) return;
	parent->Command(CMD_REG_AXISPLOT, (void*)this, o);
	if(use_xaxis || use_yaxis) {
		ApplyAxes(o);
		if(Boxes) for(i = 0; i < nPoints; i++) 
			if(Boxes[i]) Boxes[i]->DoPlot(o);
		parent->Command(CMD_AXIS, 0L, o);
		}
	else {
		if(Boxes) for(i = 0; i < nPoints; i++) 
			if(Boxes[i]) Boxes[i]->DoPlot(o);
		}
	dirty = false;
}

bool
DensDisp::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i;
	MouseEvent *mev;

	switch (cmd) {
	case CMD_MRK_DIRTY:
		dirty = true;
	case CMD_SETSCROLL:		case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		return false;
	case CMD_SCALE:
		DefLine.width *= ((scaleINFO*)tmpl)->sy.fy;		DefLine.patlength *= ((scaleINFO*)tmpl)->sy.fy;
		DefFillLine.width *= ((scaleINFO*)tmpl)->sy.fy;	DefFillLine.patlength *= ((scaleINFO*)tmpl)->sy.fy;
		DefFill.scale *= ((scaleINFO*)tmpl)->sy.fy;
		for(i = 0; i < nPoints; i++){
			if(Boxes[i]) Boxes[i]->Command(cmd, tmpl, o);
			}
		return true;
	case CMD_USEAXIS:
		UseAxis(*((int*)tmpl));
		return true;
	case CMD_MOUSE_EVENT:
		if(hidden) return false;
		mev = (MouseEvent *) tmpl;
		switch(mev->Action) {
		case MOUSE_LBUP:
			for(i = nPoints-1; i >= 0 && !CurrGO; i--) {
				if(Boxes[i] && Boxes[i]->Command(cmd, tmpl, o))return true;
				}
			break;
			}
		break;
	case CMD_SET_DATAOBJ:
		for(i = 0; i < nPoints; i++) if(Boxes[i]) Boxes[i]->Command(cmd, tmpl, o);
		Id = GO_DENSDISP;
		data = (DataObj *)tmpl;
		return true;
	case CMD_DELOBJ:
		if(!parent || !o) return false;
		if(DeleteGOL((GraphObj***)&Boxes, nPoints, (GraphObj*)tmpl, o)) 
			return parent->Command(CMD_REDRAW, 0L, o);
		break;
	case CMD_AUTOSCALE:
		if(dirty){
			Bounds.Xmin = Bounds.Ymin = HUGE_VAL;
			Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
			}
		else return true;
		dirty = false;
	case CMD_BOX_TYPE:		case CMD_BOX_FILL:
		if(Boxes) for (i = 0; i < nPoints; i++)
			if(Boxes[i]) Boxes[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_UPDATE:
		if(Boxes) SavVarObs((GraphObj **)Boxes, nPoints, UNDO_CONTINUE);
		DoUpdate();
		return true;
		}
	return false;
}

void
DensDisp::DoUpdate()
{
	AccRange *rX, *rY;
	int i, j, k, l, ic, n;
	double v, w;
	lfPOINT fp1, fp2;
	Box **op = Boxes;

	if(xRange && yRange && (rX = new AccRange(xRange)) && (rY = new AccRange(yRange))) {
		if((n=rX->CountItems()) == rY->CountItems()) {
			Bounds.Xmin = Bounds.Ymin = HUGE_VAL;		Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
			if(!(Boxes = (Box**)realloc(Boxes, n * sizeof(Box*)))) return;
			if(op && op != Boxes) Undo.InvalidGO(this);
			for(i = nPoints; i < n; i++) {
				Boxes[i] = 0L;
				}
			nPoints = n;
			rX->GetFirst(&i, &j);	rY->GetFirst(&k, &l);
			rX->GetNext(&i, &j);	rY->GetNext(&k, &l);
			for(ic = 0; ic < n && !data->GetValue(j, i, &v); ic++) {
				rX->GetNext(&i, &j);	rY->GetNext(&k, &l);
				}
			rX->GetNext(&i, &j);	rY->GetNext(&k, &l);
			if(type & 0x10){			//vertical ?
				fp2.fx = 0;		fp2.fy = v;
				}
			else {
				fp2.fx = v;		fp2.fy = 0.0;
				}
			ic = 0;
			do {
				if(data->GetValue(j, i, &v) && data->GetValue(l, k, &w)){
					fp1.fx = fp2.fx;	fp1.fy = fp2.fy;
					if(type & 0x10) {
						CheckBounds(w, fp1.fy);			CheckBounds(-w, v);
						fp2.fy = v;		
						switch(type & 0x3) {
						case 1:		fp1.fx = fp2.fx = w/2.0;		break;
						case 2:		fp1.fx = fp2.fx = -w/2.0;		break;
						default:	fp2.fx = 0.0;					break;
							}
						}
					else {
						CheckBounds(fp1.fx, w);			CheckBounds(v, -w);
						fp2.fx = v;
						switch(type & 0x3) {
						case 1:		fp1.fy = fp2.fy = w/2.0;		break;
						case 2:		fp1.fy = fp2.fy = -w/2.0;		break;
						default:	fp2.fy = 0.0;					break;
							}
						}
					if(op && Boxes[ic]) {
						Boxes[ic]->SetSize(SIZE_XPOS, fp1.fx);	Boxes[ic]->SetSize(SIZE_XPOS+1, fp2.fx);
						Boxes[ic]->SetSize(SIZE_YPOS, fp1.fy);	Boxes[ic]->SetSize(SIZE_YPOS+1, fp2.fy);
						Boxes[ic]->SetSize(SIZE_BOX, (type &0x03) ? w/2.0 : w);
						}
					else if(!op && (Boxes[ic] = new Box(this, data, fp1, fp2, BAR_WIDTHDATA)))
						Boxes[ic]->SetSize(SIZE_BOX, (type &0x03) ? w/2.0 : w);
					}
				ic++;
				}while(rX->GetNext(&i, &j) && rY->GetNext(&k, &l));
			if(!op) {
				SetSize(SIZE_BOX_LINE, DefLine.width);
				SetColor(COL_BOX_LINE, DefLine.color);
				Command(CMD_BOX_FILL, (void*)&DefFill, 0L);
				}
			}
		delete(rX);		delete(rY);
		}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Stacked bars consist of several box-plots
StackBar::StackBar(GraphObj *par, DataObj *d):Plot(par, d)
{
	FileIO(INIT_VARS);
	Id = GO_STACKBAR;
	if (!d && parent) parent->Command(CMD_DELOBJ, this, NULL);
}

StackBar::StackBar(int src):Plot(0L, 0L)
{
	int i;

	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		//now set parent in all children
		if(Boxes) for(i = 0; i < numPlots; i++)
			if(Boxes[i]) Boxes[i]->parent = this;
		if(xyPlots) for(i = 0; i < numXY; i++)
			if(xyPlots[i]) xyPlots[i]->parent = this;
		if(Polygons) for(i = 0; i < numPG; i++)
			if(Polygons[i]) Polygons[i]->parent = this;
		if(Lines) for(i = 0; i < numPL; i++)
			if(Lines[i]) Lines[i]->parent = this;
		}
}

StackBar::~StackBar()
{
	int i;

	if(Boxes){
		for(i = 0; i < numPlots; i++) if(Boxes[i]) DeleteGO(Boxes[i]);
		free(Boxes);
		}
	if(xyPlots){
		for(i = 0; i < numXY; i++) if(xyPlots[i]) DeleteGO(xyPlots[i]);
		free(xyPlots);
		}
	if(Polygons) {
		for(i = 0; i < numPG; i++) if(Polygons[i]) DeleteGO(Polygons[i]);
		free(Polygons);
		}
	if(Lines) {
		for(i = 0; i < numPL; i++) if(Lines[i]) DeleteGO(Lines[i]);
		free(Lines);
		}
	if(ssXrange) free(ssXrange);	if(ssYrange) free(ssYrange);
	if(CumData) delete CumData;		CumData = 0L;
	if(name) free(name);			name=0L;
	if(x_tv) delete x_tv;			x_tv = 0L;
	if(y_tv) delete y_tv;			y_tv = 0L;
	Undo.InvalidGO(this);
}

bool
StackBar::SetSize(int select, double value)
{
	int i;

	switch(select & 0xfff){
	case SIZE_BAR:
		if(xyPlots) for(i = 0; i < numXY; i++)
			if(xyPlots[i]) xyPlots[i]->SetSize(select, value);
		return true;
	case SIZE_BOX:		case SIZE_BOX_LINE:
		if(Boxes) for(i = 0; i < numPlots; i++) 
			if(Boxes[i]) Boxes[i]->SetSize(select, value);
		return true;
	}
	return false;
}

void
StackBar::DoPlot(anyOutput *o)
{
	int i;
	double dx, dy;
	fRECT oldREC;

	if(!o || !parent) return;
	parent->Command(CMD_REG_AXISPLOT, (void*)this, o);
	if(use_xaxis || use_yaxis) ApplyAxes(o);
	dx = o->un2fix(dspm.fx);		dy = o->un2fiy(dspm.fy);
	memcpy(&oldREC, &o->Box1, sizeof(fRECT));
	if(Boxes) for(i = 0; i < numPlots; i++) if(Boxes[i]) {
		if(Boxes[i]->Id >= GO_PLOT && Boxes[i]->Id < GO_GRAPH) {
			if(((Plot*)Boxes[i])->hidden == 0) Boxes[i]->DoPlot(o);
			}
		else Boxes[i]->DoPlot(o);
		}
	if(xyPlots) for(i = 0; i < numXY; i++) if(xyPlots[i]) {
		if(xyPlots[i]->Id >= GO_PLOT && xyPlots[i]->Id < GO_GRAPH) {
			if(((Plot*)xyPlots[i])->hidden == 0) xyPlots[i]->DoPlot(o);
			}
		else xyPlots[i]->DoPlot(o);
		}
	if(Polygons) for(i = 0; i < numPG; i++)
		if(Polygons[i]) Polygons[i]->DoPlot(o);
	if(Lines) for(i = numPL-1; i >= 0; i--){
		o->Box1.Xmin = oldREC.Xmin + dx*i;
		o->Box1.Ymin = oldREC.Ymin + dy*i;
		o->Box1.Xmax = oldREC.Xmax + dx*i;
		o->Box1.Ymax = oldREC.Ymax + dy*i;
		if(Lines[i]) Lines[i]->DoPlot(o);
		}
	dirty = false;
	memcpy(&o->Box1, &oldREC, sizeof(fRECT));
	if(use_xaxis || use_yaxis) parent->Command(CMD_AXIS, 0L, o);
}

bool
StackBar::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i;
	static MouseEvent *mev;

	switch (cmd) {
	case CMD_MOUSE_EVENT:
		if(hidden) return false;
		mev = (MouseEvent *) tmpl;
		switch(mev->Action) {
		case MOUSE_LBUP:
			if(Boxes && !CurrGO) for(i = 0; i < numPlots; i++)
				if(Boxes[i] && Boxes[i]->Command(cmd, tmpl, o))return true;
			if(xyPlots && !CurrGO) for(i = 0; i < numXY; i++)
				if(xyPlots[i] && xyPlots[i]->Command(cmd, tmpl, o))return true;
			if(Polygons && !CurrGO) for(i = 0; i < numPG; i++)
				if(Polygons[i] && Polygons[i]->Command(cmd, tmpl, o))return true;
			if(Lines && !CurrGO) for(i = 0; i < numPL; i++)
				if(Lines[i] && Lines[i]->Command(cmd, tmpl, o))return true;
			break;
			}
		break;
	case CMD_OBJTREE:
		if(!tmpl) return false;
		if(Boxes) for(i = 0; i < numPlots; i++) if(Boxes[i]) 
			((ObjTree*)tmpl)->Command(CMD_UPDATE, Boxes[i], 0L);
		if(xyPlots) for(i = 0; i < numXY; i++) if(xyPlots[i]) 
			((ObjTree*)tmpl)->Command(CMD_UPDATE, xyPlots[i], 0L);
		return true;
	case CMD_SCALE:
		dspm.fx *= ((scaleINFO*)tmpl)->sx.fy;
		dspm.fy *= ((scaleINFO*)tmpl)->sy.fy;
	case CMD_LEGEND:
		if(Boxes) for (i = 0; i < numPlots; i++)
			if(Boxes[i]) Boxes[i]->Command(cmd, tmpl, o);
		if(Polygons) for (i = numPG-1; i >= 0; i--)
			if(Polygons[i]) Polygons[i]->Command(cmd, tmpl, o);
		if(Lines) for (i = numPL-1; i >= 0; i--)
			if(Lines[i]) Lines[i]->Command(cmd, tmpl, o);
		if(xyPlots) for (i = 0; i < numXY; i++)
			if(xyPlots[i]) xyPlots[i]->Command(cmd, tmpl, o);
		break;
	case CMD_USEAXIS:
		UseAxis(*((int*)tmpl));
		return true;
	case CMD_MRK_DIRTY:
		dirty = true;
	case CMD_SETSCROLL:		case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		return false;
	case CMD_BAR_TYPE:
		if(xyPlots) for(i = 0; i < numXY; i++)
			if(xyPlots[i]) xyPlots[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_SAVE_BARS:		case CMD_SAVE_BARS_CONT:
		if(Boxes) for(i = 0; i < numPlots; i++)
			if(Boxes[i])Boxes[i]->Command(CMD_SAVE_BARS_CONT, tmpl, o);
		return true;
	case CMD_SET_DATAOBJ:
		Id = GO_STACKBAR;
		if(data == tmpl) return true;
		data = (DataObj *)tmpl;
	case CMD_AUTOSCALE:		case CMD_UPDATE:
		if(cmd == CMD_AUTOSCALE) {
			if(hidden) return false;
			Bounds.Xmin = Bounds.Ymin = HUGE_VAL;
			Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
			dirty = false;
			}
		if(cum_data_mode){
			if(CumData) delete CumData;
			if(CumData = CreaCumData(ssXrange, ssYrange, cum_data_mode, StartVal)) {
				if(Polygons) for(i = 0; i < numPG; i++)
					if(Polygons[i]) Polygons[i]->Command(CMD_SET_DATAOBJ, CumData, o);
				if(Lines) for(i = 0; i < numPL; i++)
					if(Lines[i]) Lines[i]->Command(CMD_SET_DATAOBJ, CumData, o);
				if(xyPlots) for(i = 0; i < numXY; i++)
					if(xyPlots[i]) xyPlots[i]->Command(CMD_SET_DATAOBJ, CumData, o);
				if(Boxes) for (i = 0; i < numPlots; i++) 
					if(Boxes[i]) Boxes[i]->Command(CMD_SET_DATAOBJ, CumData, o);
				}
			}
		if(cmd == CMD_SET_DATAOBJ) tmpl = (void*) CumData;
		if(Polygons) for(i = 0; i < numPG; i++)
			if(Polygons[i]) Polygons[i]->Command(cmd, tmpl, o);
		if(Lines) for(i = 0; i < numPL; i++)
			if(Lines[i]) Lines[i]->Command(cmd, tmpl, o);
		if(xyPlots) for(i = 0; i < numXY; i++)
			if(xyPlots[i]) {
				if(cmd == CMD_AUTOSCALE && xyPlots[i]->Id >= GO_PLOT && xyPlots[i]->Id < GO_GRAPH) {
					if(((Plot*)xyPlots[i])->hidden == 0) xyPlots[i]->Command(cmd, tmpl, o);
					}
				else xyPlots[i]->Command(cmd, tmpl, o);
				}
	case CMD_BOX_TYPE:
		if(Boxes) for (i = 0; i < numPlots; i++) 
			if(Boxes[i]) Boxes[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_DELOBJ:
		if(o) o->HideMark();
		if(!tmpl || !parent) return false;
		if(DeleteGOL((GraphObj***)&Polygons, numPG, (GraphObj*)tmpl, o)) 
			return parent->Command(CMD_REDRAW, 0L, o);
		if(DeleteGOL((GraphObj***)&Lines, numPL, (GraphObj*)tmpl, o)) 
			return parent->Command(CMD_REDRAW, 0L, o);
		if(DeleteGOL((GraphObj***)&xyPlots, numXY, (GraphObj*)tmpl, o)) 
			return parent->Command(CMD_REDRAW, 0L, o);
		if(DeleteGOL((GraphObj***)&Boxes, numPlots, (GraphObj*)tmpl, o)) 
			return parent->Command(CMD_REDRAW, 0L, o);
		if(xyPlots) for(i = 0; i < numXY; i++)
			if(xyPlots[i] && xyPlots[i]->Command(cmd, tmpl, o)) return true;
		if(Boxes) for(i = 0; i < numPlots; i++)
			if(Boxes[i] && Boxes[i]->Command(cmd, tmpl, o)) return true;
		return false;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Stacked polygons is based on stacked bar
StackPG::StackPG(GraphObj *par, DataObj *d):StackBar(par, d)
{
	Id = GO_STACKPG;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// waterfall plot is based on stacked bar
Waterfall::Waterfall(GraphObj *par, DataObj *d):StackBar(par, d)
{
	Id = GO_WATERFALL;
	dspm.fx = dspm.fy = 0.0;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// multi data line plot is based on stacked bar
MultiLines::MultiLines(GraphObj *par, DataObj *d):StackBar(par, d)
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// simple pie chart
PieChart::PieChart(GraphObj *par, DataObj *d):Plot(par, d)
{
	FileIO(INIT_VARS);
	Id = GO_PIECHART;
	if (!d && parent) parent->Command(CMD_DELOBJ, this, NULL);
}

PieChart::PieChart(int src):Plot(0L, 0L)
{
	int i;
	
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		//now set parent in  children
		if(Segments) 
			for(i = 0; i < nPts; i++) if(Segments[i]) Segments[i]->parent = this;
		}
}

PieChart::~PieChart()
{
	int i;

	if(Segments) {
		for(i = 0; i < nPts; i++) if(Segments[i]) DeleteGO(Segments[i]);
		free(Segments);		Segments = 0L;
		}
	if(ssRefA) free(ssRefA);	if(ssRefR) free(ssRefR);
	ssRefA = ssRefR = 0L;
	if(name) free(name);		name=0L;
	Undo.InvalidGO(this);
}

bool 
PieChart::SetSize(int select, double value)
{
	int i;

	switch(select & 0xfff) {
	case SIZE_XPOS:
	case SIZE_YPOS:
	case SIZE_RADIUS1:
	case SIZE_RADIUS2:
		if(Segments) for(i = 0; i < nPts; i++) {
			if(Segments[i]) Segments[i]->SetSize(select, value);
			}
		return true;
	default:
		return false;
		}
	return true;
}

void
PieChart::DoPlot(anyOutput *o)
{
	int i;

	if(Segments) for(i = 0; i < nPts; i++) if(Segments[i]) Segments[i]->DoPlot(o);
}

bool
PieChart::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i;
	MouseEvent *mev;

	switch (cmd) {
	case CMD_MOUSE_EVENT:
		if(hidden) return false;
		mev = (MouseEvent *) tmpl;
		switch(mev->Action) {
		case MOUSE_LBUP:
			//select objects invers to plot order
			if(Segments && !CurrGO) for(i = nPts-1; i>=0; i--)
				if(Segments[i]) if(Segments[i]->Command(cmd, tmpl, o))break;
			break;
			}
		break;
	case CMD_SETSCROLL:		case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		return false;
	case CMD_UPDATE:
		DoUpdate();
		return true;
	case CMD_SET_DATAOBJ:
		Id = GO_PIECHART;
		data = (DataObj *)tmpl;
	case CMD_SCALE:
		if(cmd == CMD_SCALE) {
			CtDef.fx *= ((scaleINFO*)tmpl)->sx.fy;	CtDef.fy *= ((scaleINFO*)tmpl)->sx.fy;
			}
	case CMD_SHIFT_OUT:		case CMD_SEG_FILL:		case CMD_SEG_LINE:
	case CMD_SEG_MOVEABLE:	case CMD_LEGEND:
		if(Segments) for(i = 0; i < nPts; i++)
			if(Segments[i]) Segments[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_DELOBJ:
		o->HideMark();
		if(Segments && parent) for(i = 0; i < nPts; i++) {
			if(Segments[i] && tmpl == (void *)Segments[i]) {
				Undo.DeleteGO((GraphObj**)(&Segments[i]), 0L, o);
				parent->Command(CMD_REDRAW, NULL, o);
				return true;
				}
			}
		break;
	case CMD_SAVE_SYMBOLS:
		return SavVarObs((GraphObj **)Segments, nPts, 0L);
		}
	return false;
}

void
PieChart::DoUpdate()
{
	AccRange *rY = 0L, *rR = 0L;
	double sum, fv, dang1, dang2;
	int i, ix, iy, rix, riy;

	if(ssRefA && (rY = new AccRange(ssRefA))) {
		SavVarObs((GraphObj **)Segments, nPts, UNDO_CONTINUE);
		if(ssRefR) rR = new AccRange(ssRefR);
		rY->GetFirst(&ix, &iy);				rY->GetNext(&ix, &iy);
		for(i = 0, sum = 0.0; i < nPts; i++){
			if(data->GetValue(iy, ix, &fv)) sum += fv;
			rY->GetNext(&ix, &iy);
			}
		sum /= CtDef.fy;
		dang1 = dang2 = CtDef.fx;
		rY->GetFirst(&ix, &iy);				rY->GetNext(&ix, &iy);
		if(rR) {
			rR->GetFirst(&rix, &riy);		rR->GetNext(&rix, &riy);
			}
		for(i = 0; i < nPts; i++){
			if(data->GetValue(iy, ix, &fv)) {
				dang2 -= (double)fv / sum;
				if(dang2 < 0.0) dang2 += 360.0;
				if(Segments[i]) {
					Segments[i]->SetSize(SIZE_ANGLE1, dang1);
					Segments[i]->SetSize(SIZE_ANGLE2, dang2);
					if(rR && data->GetValue(riy, rix, &fv)){
						fv *= FacRad;
						Segments[i]->SetSize(SIZE_RADIUS2, fv);
						}
					}
				dang1 = dang2;
				}
			rY->GetNext(&ix, &iy);
			if(rR) rR->GetNext(&rix, &riy);
			}
		}
	if(rY) delete rY;		if(rR) delete rR;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ring chart is based on piechart
RingChart::RingChart(GraphObj *par, DataObj *d):PieChart(par, d)
{
	Id = GO_RINGCHART;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// a GoGroup contains objects NOT referring to data (e.g. drawing objects)
GoGroup::GoGroup(GraphObj *par, DataObj *d):Plot(par, d)
{
	Objects = 0L;
	nObs = 0;
	fPos.fx = fPos.fy = 0.0;
	Id = GO_GROUP;
}

GoGroup::GoGroup(int src):Plot(0L, 0L)
{
	int i;
	
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		//now set parent in  children
		if(Objects) 
			for(i = 0; i < nObs; i++) if(Objects[i]) Objects[i]->parent = this;
		}
}

GoGroup::~GoGroup()
{
	int i;

	if(Objects && nObs) {
		for(i = 0; i < nObs; i++) if(Objects[i]) DeleteGO(Objects[i]);
		free(Objects);
		}
	if(name) free(name);		name=0L;
	Undo.InvalidGO(this);
}

double
GoGroup::GetSize(int select)
{
	if(parent) switch(select){
	case SIZE_GRECT_TOP:
	case SIZE_GRECT_BOTTOM:
		return parent->GetSize(select)-fPos.fy;
	case SIZE_GRECT_LEFT:
	case SIZE_GRECT_RIGHT:
		return parent->GetSize(select)-fPos.fx;
	case SIZE_XPOS:
		return fPos.fx;
	case SIZE_YPOS:
		return fPos.fy;
		}
	return 0.0f;
}

void 
GoGroup::DoPlot(anyOutput *o)
{
	int i;
	double dx, dy;
	
	dx = o->un2fix(fPos.fx + (parent ? parent->GetSize(SIZE_GRECT_LEFT) : 0.0));
	dy = o->un2fiy(fPos.fy + (parent ? parent->GetSize(SIZE_GRECT_TOP) : 0.0));
	o->VPorg.fx += dx;				o->VPorg.fy += dy;
	for(i = 0; i < nObs; i++) if(Objects[i]) Objects[i]->DoPlot(o);
	o->VPorg.fx -= dx;				o->VPorg.fy -= dy;
}

bool
GoGroup::Command(int cmd, void *tmpl, anyOutput *o)
{
	GraphObj **tmp_go;
	MouseEvent *mev;
	int i;

	switch(cmd) {
	case CMD_MOUSE_EVENT:
		if(hidden) return false;
		mev = (MouseEvent *) tmpl;
		switch(mev->Action) {
		case MOUSE_LBUP:
			//select objects invers to plot order
			if(Objects && !CurrGO) for(i = nObs-1; i>=0; i--)
				if(Objects[i]) if(Objects[i]->Command(cmd, tmpl, o))break;
			break;
			}
		break;
	case CMD_SET_DATAOBJ:
		Id = GO_GROUP;
		if(Objects) for(i = 0; i < nObs; i++)
			if(Objects[i]) Objects[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_DROP_OBJECT:
		if(!Objects || nObs<1) {
			if((Objects = (GraphObj **)calloc(1, sizeof(GraphObj*)))){
				Objects[0] = (GraphObj *)tmpl;
				nObs = 1;
				return true;
				}
			}
		else if((tmp_go = (GraphObj **)realloc(Objects, (nObs+1)*sizeof(GraphObj*)))) {
			Objects = tmp_go;
			Objects[nObs++] = (GraphObj *)tmpl;
			return true;
			}
		break;
	case CMD_SETSCROLL:
	case CMD_REDRAW:
		if(parent) return parent->Command(CMD_REDRAW, tmpl, o);
		return false;
	case CMD_DELOBJ:
		if(Objects && parent) for(i = 0; i < nObs; i++) {
			o->HideMark();
			if(Objects[i] && tmpl == (void *)Objects[i]) {
				Undo.DeleteGO((GraphObj**)(&Objects[i]), 0L, o);
				parent->Command(CMD_REDRAW, NULL, o);
				return true;
				}
			}
		break;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// star chart
StarChart::StarChart(GraphObj *par, DataObj *d):GoGroup(par, d)
{
	Id = GO_STARCHART;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// three dimensional scatterplot
Scatt3D::Scatt3D(GraphObj *par, DataObj *d, DWORD flags):Plot(par, d)
{
	FileIO(INIT_VARS);
	c_flags = flags;
	Id = GO_SCATT3D;
}

Scatt3D::Scatt3D(GraphObj *par, DataObj *d, Brick **cols, long nob):Plot(par, d)
{
	int i;

	FileIO(INIT_VARS);
	c_flags = 0L;		Id = GO_SCATT3D;
	Columns = cols;		nColumns = nob;
	if(Columns) for(i = 0; i < nColumns; i++) if(Columns[i]) Columns[i]->parent=this;
}

Scatt3D::Scatt3D(GraphObj *par, DataObj *d, Sphere **ba, long nob):Plot(par, d)
{
	int i;

	FileIO(INIT_VARS);
	c_flags = 0L;		Id = GO_SCATT3D;
	Balls = ba;			nBalls = nob;
	if(Balls) for(i = 0; i < nBalls; i++) if(Balls[i]) Balls[i]->parent=this;
}

Scatt3D::Scatt3D(int src):Plot(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
}

Scatt3D::~Scatt3D()
{
	long i;

	if(ssRefX) free(ssRefX);	if(ssRefY) free(ssRefY);
	if(ssRefZ) free(ssRefZ);	ssRefX = ssRefY = ssRefZ = 0L;
	Undo.InvalidGO(this);
	if(Balls) {
		for(i = 0; i < nBalls; i++) if(Balls[i]) DeleteGO(Balls[i]);
		free(Balls);					Balls = 0L;
		}
	if(Columns) {
		for(i = 0; i < nColumns; i++) if(Columns[i]) DeleteGO(Columns[i]);
		free(Columns);					Columns = 0L;
		}
	if(DropLines) {
		for(i = 0; i < nDropLines; i++) if(DropLines[i]) DeleteGO(DropLines[i]);
		free(DropLines);				DropLines = 0L;
		}
	if(Arrows) {
		for(i = 0; i < nArrows; i++) if(Arrows[i]) DeleteGO(Arrows[i]);
		free(Arrows);					Arrows = 0L;
		}
	if(Line) {
		DeleteGO(Line);					Line = 0L;
		}
	if(rib) {
		DeleteGO(rib);					 rib = 0L;
		}
	if(name) free(name);				name=0L;
	if(data_desc) free(data_desc);		data_desc = 0L;
}

double
Scatt3D::GetSize(int select)
{
	if(parent) return parent->GetSize(select);
	return 0.0;
}

bool
Scatt3D::SetSize(int select, double value)
{
	int i;

	switch(select & 0xfff) {
	case SIZE_SYM_LINE:
	case SIZE_SYMBOL:
		if(Balls) for (i=0; i < nBalls; i++)
			if(Balls[i]) Balls[i]->SetSize(select, value);
		return true;
	case SIZE_BAR_BASE:		case SIZE_BAR_LINE:		case SIZE_BAR:
	case SIZE_BAR_DEPTH:
		if(Columns) for (i=0; i < nColumns; i++)
			if(Columns[i]) Columns[i]->SetSize(select, value);
		return true;
	case SIZE_ARROW_LINE:	case SIZE_ARROW_CAPWIDTH:
	case SIZE_ARROW_CAPLENGTH:
		if(Arrows) for (i=0; i < nArrows; i++)
			if(Arrows[i]) Arrows[i]->SetSize(select, value);
		return true;
		}
	return false;
}

bool
Scatt3D::SetColor(int select, DWORD col)
{
	int i;

	switch(select) {
	case COL_SYM_LINE:			case COL_SYM_FILL:
		if(Balls) for (i=0; i < nBalls; i++)
			if(Balls[i]) Balls[i]->SetColor(select, col);
		return true;
	case COL_BAR_LINE:			case COL_BAR_FILL:
		if(Columns) for (i=0; i < nColumns; i++)
			if(Columns[i]) Columns[i]->SetColor(select, col);
		return true;
	case COL_ARROW:
		if(Arrows) for (i=0; i < nArrows; i++)
			if(Arrows[i]) Arrows[i]->SetColor(select, col);
		return true;
		}
	return false;
}

void
Scatt3D::DoPlot(anyOutput *o)
{
	long i;
	RECT rc;

	if(!o || !parent) return;
	if(use_xaxis || use_yaxis || use_zaxis) ApplyAxes(o);
	o->GetSize(&rc);
	parent->Command(CMD_REG_AXISPLOT, (void*)this, o);
	rDims.left = rc.right;			rDims.right = rc.left;
	rDims.top = rc.bottom;			rDims.bottom = rc.top;
	if(Balls) {
		for(i = 0; i < nBalls; i++){
			if(Balls[i]){
				Balls[i]->DoPlot(o);
				UpdateMinMaxRect(&rDims, Balls[i]->rDims.right, Balls[i]->rDims.top);
				UpdateMinMaxRect(&rDims, Balls[i]->rDims.left, Balls[i]->rDims.bottom);
				}
			}
		}
	if(Columns) {
		for(i = 0; i < nColumns; i++){
			if(Columns[i]){
				Columns[i]->DoPlot(o);
				UpdateMinMaxRect(&rDims, Columns[i]->rDims.right, Columns[i]->rDims.top);
				UpdateMinMaxRect(&rDims, Columns[i]->rDims.left, Columns[i]->rDims.bottom);
				}
			}
		}
	if(DropLines) {
		for(i = 0; i < nDropLines; i++){
			if(DropLines[i]){
				DropLines[i]->DoPlot(o);
				UpdateMinMaxRect(&rDims, DropLines[i]->rDims.right, DropLines[i]->rDims.top);
				UpdateMinMaxRect(&rDims, DropLines[i]->rDims.left, DropLines[i]->rDims.bottom);
				}
			}
		}
	if(Arrows) {
		for(i = 0; i < nArrows; i++){
			if(Arrows[i]){
				Arrows[i]->DoPlot(o);
				UpdateMinMaxRect(&rDims, Arrows[i]->rDims.right, Arrows[i]->rDims.top);
				UpdateMinMaxRect(&rDims, Arrows[i]->rDims.left, Arrows[i]->rDims.bottom);
				}
			}
		}
	if(Line) {
		Line->DoPlot(o);
		UpdateMinMaxRect(&rDims, Line->rDims.right, Line->rDims.top);
		UpdateMinMaxRect(&rDims, Line->rDims.left, Line->rDims.bottom);
		}
	if(rib) {
		rib->DoPlot(o);
		UpdateMinMaxRect(&rDims, rib->rDims.right, rib->rDims.top);
		UpdateMinMaxRect(&rDims, rib->rDims.left, rib->rDims.bottom);
		}
	if(use_xaxis || use_yaxis || use_zaxis)parent->Command(CMD_AXIS, 0L, o);
	dirty = false;
}

bool
Scatt3D::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i;
	MouseEvent *mev;

	switch (cmd) {
	case CMD_MOUSE_EVENT:
		if(hidden) return false;
		mev = (MouseEvent *) tmpl;
		switch(mev->Action) {
		case MOUSE_LBUP:
			if(Balls && !CurrGO) for(i = 0; i < nBalls; i++)
				if(Balls[i]) if(Balls[i]->Command(cmd, tmpl, o))return true;
			if(Columns && !CurrGO) for(i = 0; i < nColumns; i++)
				if(Columns[i]) if(Columns[i]->Command(cmd, tmpl, o))return true;
			if(DropLines && !CurrGO) for(i = 0; i < nDropLines; i++)
				if(DropLines[i]) if(DropLines[i]->Command(cmd, tmpl, o))return true;
			if(Arrows && !CurrGO) for(i = 0; i < nArrows; i++)
				if(Arrows[i]) if(Arrows[i]->Command(cmd, tmpl, o))return true;
			if(Line && !CurrGO) if(Line->Command(cmd, tmpl, o)) return true;
			if(rib && !CurrGO) if(rib->Command(cmd, tmpl, o)) return true;
			break;
			}
		break;
	case CMD_MRK_DIRTY:
		dirty = true;
	case CMD_SET_GO3D:		case CMD_SETSCROLL:		case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_DL_LINE:		case CMD_DL_TYPE:
		if(DropLines) for(i = 0; i < nDropLines; i++)
			if(DropLines[i]) DropLines[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_ARROW_TYPE:	case CMD_ARROW_ORG3D:
		if(Arrows) for(i = 0; i < nArrows; i++)
			if(Arrows[i]) Arrows[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_USEAXIS:
		return UseAxis(*((int*)tmpl));
	case CMD_LEGEND:
		if(!tmpl) return false;
		if(Balls) {
			if(Line && Line->Id == GO_LINE3D) {
				for (i = 0; i < nBalls && i < 100; i++)
					if(Balls[i]) ((Legend*)tmpl)->HasSym(&Line->Line, Balls[i], 0L);
				}
			else {
				for (i = 0; i < nBalls && i < 100; i++) {
					if(Balls[i]) {
						if(Balls[i]->type) Balls[i]->Command(cmd, tmpl, o);
						else ((Legend*)tmpl)->HasSym(0L, Balls[i], 0L);
						}
					}
				}
			}
		else if(Line) Line->Command(cmd, tmpl, o);
		if(Columns) for(i = 0; i < nColumns; i++)
			if(Columns[i]) Columns[i]->Command(cmd, tmpl, o);
		if(rib) rib->Command(cmd, tmpl, o);
		return true;
	case CMD_SET_DATAOBJ:
		Id = GO_SCATT3D;
		data = (DataObj *)tmpl;
	case CMD_UPDATE:	case CMD_SCALE:
		if(Balls) {
			SavVarObs((GraphObj**)Balls, nBalls, UNDO_CONTINUE);
			for(i = 0; i < nBalls; i++)	if(Balls[i]) Balls[i]->Command(cmd, tmpl, o);
			}
		if(Columns) {
			SavVarObs((GraphObj**)Columns, nColumns, UNDO_CONTINUE);
			for(i = 0; i < nColumns; i++) if(Columns[i]) Columns[i]->Command(cmd, tmpl, o);
			}
		if(DropLines) {
			SavVarObs((GraphObj**)DropLines, nDropLines, UNDO_CONTINUE);
			for(i = 0; i < nDropLines; i++) if(DropLines[i]) DropLines[i]->Command(cmd, tmpl, o);
			}
		if(Arrows) {
			SavVarObs((GraphObj**)Arrows, nArrows, UNDO_CONTINUE);
			for(i = 0; i < nArrows; i++) if(Arrows[i]) Arrows[i]->Command(cmd, tmpl, o);
			}
		if(Line) Line->Command(cmd, tmpl, o);
		if(rib) rib->Command(cmd, tmpl, o);
		return true;
	case CMD_AUTOSCALE:
		if(dirty) {
			Bounds.Xmin = Bounds.Ymin = HUGE_VAL;	Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
			xBounds.fx = yBounds.fx = zBounds.fx = HUGE_VAL;
			xBounds.fy = yBounds.fy = zBounds.fy = -HUGE_VAL;
			if(Balls) for(i = 0; i < nBalls; i++)
				if(Balls[i]) Balls[i]->Command(cmd, tmpl, o);
			if(Columns) for(i = 0; i < nColumns; i++)
				if(Columns[i]) Columns[i]->Command(cmd, tmpl, o);
			if(DropLines) for(i = 0; i < nDropLines; i++)
				if(DropLines[i]) DropLines[i]->Command(cmd, tmpl, o);
			if(Arrows) for(i = 0; i < nArrows; i++)
				if(Arrows[i]) Arrows[i]->Command(cmd, tmpl, o);
			if(Line) Line->Command(cmd, tmpl, o);
			if(rib) rib->Command(cmd, tmpl, o);
			}
		if(parent && parent->Id > GO_PLOT && parent->Id < GO_GRAPH &&
			xBounds.fx <= xBounds.fy && yBounds.fx <= yBounds.fy && zBounds.fx <= zBounds.fy){
			((Plot*)parent)->CheckBounds3D(xBounds.fx, yBounds.fx, zBounds.fx);
			((Plot*)parent)->CheckBounds3D(xBounds.fy, yBounds.fy, zBounds.fy);
			}
		dirty = false;
		return true;
	case CMD_DELOBJ:
		if(o) o->HideMark();
		if(!tmpl || !parent) return false;
		if(rib && rib->Command(cmd, tmpl, o)) return true;
		if(Balls) for(i = 0; i < nBalls; i++) if(Balls[i] == tmpl) {
			Undo.DeleteGO((GraphObj**)(&Balls[i]), 0L, o);
			return parent->Command(CMD_REDRAW, 0L, o);
			}
		if(Columns) for(i = 0; i < nColumns; i++) if(Columns[i] == tmpl) {
			Undo.DeleteGO((GraphObj**)(&Columns[i]), 0L, o);
			return parent->Command(CMD_REDRAW, 0L, o);
			}
		if(DropLines) for(i = 0; i < nDropLines; i++) if(DropLines[i] == tmpl) {
			Undo.DeleteGO((GraphObj**)(&DropLines[i]), 0L, o);
			return parent->Command(CMD_REDRAW, 0L, o);
			}
		if(Arrows) for(i = 0; i < nArrows; i++) if(Arrows[i] == tmpl) {
			Undo.DeleteGO((GraphObj**)(&Arrows[i]), 0L, o);
			return parent->Command(CMD_REDRAW, 0L, o);
			}
		if(Line && Line == tmpl) {
			Undo.DeleteGO((GraphObj**)(&Line), 0L, o);
			return parent->Command(CMD_REDRAW, 0L, o);
			}
		if(rib && rib == tmpl) {
			Undo.DeleteGO((GraphObj**)(&rib), 0L, o);
			return parent->Command(CMD_REDRAW, 0L, o);
			}
	case CMD_SYM_FILL:
		if(Balls) for(i= 0; i < nBalls; i++) if(Balls[i])Balls[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_BAR_FILL:
		if(Columns) for(i= 0; i < nColumns; i++) if(Columns[i])Columns[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_SAVE_SYMBOLS:
		return SavVarObs((GraphObj **)Balls, nBalls, 0L);
	case CMD_SAVE_BARS:
		return SavVarObs((GraphObj **)Columns, nColumns, 0L);
	case CMD_SAVE_ARROWS:
		return SavVarObs((GraphObj **)Arrows, nArrows, 0L);
	case CMD_SAVE_DROPLINES:
		return SavVarObs((GraphObj **)DropLines, nDropLines, 0L);
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// three dimensional ribbon based on list of Plane3D objects
Ribbon::Ribbon(GraphObj *par, DataObj *d, double z, double width, char *xr, char *yr)
	:Plot(par, d)
{
	FileIO(INIT_VARS);		Id = GO_RIBBON;		type = 1;
	if(xr && xr[0]) ssRefX = (char*)memdup(xr, (int)strlen(xr)+1, 0L);
	if(yr && yr[0]) ssRefY = (char*)memdup(yr, (int)strlen(yr)+1, 0L);
	z_value = z;	z_width = width;
}

Ribbon::Ribbon(GraphObj *par, DataObj *d, int which, char *xr, char *yr, char *zr)
	:Plot(par, d)
{
	FileIO(INIT_VARS);		Id = GO_RIBBON;			type = which;
	if(xr && xr[0]) ssRefX = (char*)memdup(xr, (int)strlen(xr)+1, 0L);
	if(yr && yr[0]) ssRefY = (char*)memdup(yr, (int)strlen(yr)+1, 0L);
	if(zr && zr[0]) ssRefZ = (char*)memdup(zr, (int)strlen(zr)+1, 0L);
	CreateObs();
}

Ribbon::Ribbon(GraphObj *par, DataObj *d, GraphObj **go, int ngo)
	:Plot(par, d)
{
	int i;

	FileIO(INIT_VARS);		Id = GO_RIBBON;		type = 3;
	planes = (Plane3D**)go;						nPlanes = ngo;
	for(i = 0; i < ngo; i++) planes[i]->parent = this;
}


Ribbon::Ribbon(int src):Plot(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
}

Ribbon::~Ribbon()
{
	int i;

	if(ssRefX) free(ssRefX);	if(ssRefY) free(ssRefY);
	if(ssRefZ) free(ssRefZ);	ssRefX = ssRefY = ssRefZ = 0L;
	Undo.InvalidGO(this);
	if(planes) {
		for(i = 0; i < nPlanes; i++) if(planes[i]) DeleteGO(planes[i]);
		free(planes);		planes = 0L;
		}
	if(values) free(values);		values = 0L;	nVal = 0;
	if(name) free(name);			name=0L;
	if(data_desc) free(data_desc);	data_desc = 0L;
}

double
Ribbon::GetSize(int select)
{
	switch(select) {
	case SIZE_CELLWIDTH:	return relwidth;
	case SIZE_ZPOS:			return z_value;
		}
	return 0.0;
}

bool
Ribbon::SetSize(int select, double value)
{
	int i;

	switch(select) {
	case SIZE_SYM_LINE:
		if(planes) for (i=0; i < nPlanes; i++)
			if(planes[i]) planes[i]->SetSize(select, value);
		return true;
	case SIZE_CELLWIDTH:
		if(value != relwidth) {
			//assume planes saved already by CMD_SAVE_SYMBOLS
			Undo.ValFloat(this, &relwidth, UNDO_CONTINUE);
			relwidth = value;
			if(planes) UpdateObs(false);
			}
		return true;
	case SIZE_ZPOS:
		if(value != z_value) {
			//assume planes saved already by CMD_SAVE_SYMBOLS
			Undo.ValFloat(this, &z_value, UNDO_CONTINUE);
			z_value = value;
			if(planes) UpdateObs(false);
			}
		return true;
		}
	return false;
}

bool
Ribbon::SetColor(int select, DWORD col)
{
	int i;

	switch(select) {
	case COL_POLYLINE:
		Line.color = col;
	case COL_POLYGON:
		if(select == COL_POLYGON) Fill.color = col;
		if(planes) for (i=0; i < nPlanes; i++)
			if(planes[i]) planes[i]->SetColor(select, col);
		return true;
		}
	return false;
}

void
Ribbon::DoPlot(anyOutput *o)
{
	int i;

	if(!planes) CreateObs();
	if(planes) for(i = 0; i < nPlanes; i++) if(planes[i]) planes[i]->DoPlot(o);
	dirty = false;
}

bool
Ribbon::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i;
	MouseEvent *mev;

	switch (cmd) {
	case CMD_MOUSE_EVENT:
		if(hidden) return false;
		mev = (MouseEvent *) tmpl;
		switch(mev->Action) {
		case MOUSE_LBUP:
			if(planes && !CurrGO) for(i = 0; i < nPlanes; i++)
				if(planes[i]) if(planes[i]->Command(cmd, tmpl, o)) return true;
			break;
			}
		break;
	case CMD_SCALE:
		z_value *= ((scaleINFO*)tmpl)->sz.fy;
		z_width *= ((scaleINFO*)tmpl)->sz.fy;
		for(i = 0; i < nPlanes; i++) if(planes[i]) planes[i]->Command(cmd, tmpl, o);
		break;
	case CMD_MRK_DIRTY:
		dirty = true;
	case CMD_SET_GO3D:		case CMD_SETSCROLL:		case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_SET_DATAOBJ:
		Id = GO_RIBBON;
		data = (DataObj *)tmpl;
		if(planes) for(i = 0; i < nPlanes; i++)
			if(planes[i]) planes[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_UPDATE:
		SavVarObs((GraphObj **)planes, nPlanes, UNDO_CONTINUE);
		Undo.DataMem(this, (void**)&values, nVal * sizeof(fPOINT3D), &nVal, UNDO_CONTINUE);
		UpdateObs(dirty = true);
		if(parent) parent->Command(CMD_MRK_DIRTY, tmpl, o);
		return true;
	case CMD_AUTOSCALE:
		if(!planes) CreateObs();
		if(dirty) {
			Bounds.Xmin = Bounds.Ymin = HUGE_VAL;	Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
			xBounds.fx = yBounds.fx = zBounds.fx = HUGE_VAL;
			xBounds.fy = yBounds.fy = zBounds.fy = -HUGE_VAL;
			if(planes) for(i = 0; i < nPlanes; i++)
				if(planes[i]) planes[i]->Command(cmd, tmpl, o);
			}
		if(parent && parent->Id > GO_PLOT && parent->Id < GO_GRAPH &&
			xBounds.fx <= xBounds.fy && yBounds.fx <= yBounds.fy && zBounds.fx <= zBounds.fy){
			((Plot*)parent)->CheckBounds3D(xBounds.fx, yBounds.fx, zBounds.fx);
			((Plot*)parent)->CheckBounds3D(xBounds.fy, yBounds.fy, zBounds.fy);
			}
		return true;
	case CMD_DELOBJ:
		if(!tmpl || !parent) return false;
		if(planes) for(i = 0; i < nPlanes; i++) if(planes[i] == tmpl) {
			Undo.DeleteGO((GraphObj**)(&planes[i]), 0L, o);
			return parent->Command(CMD_REDRAW, 0L, o);
			}
		return false;
	case CMD_SYM_FILL:		case CMD_LEGEND:
		if(planes) for(i = 0; i < nPlanes; i++) if(planes[i]) planes[i]->Command(cmd, tmpl, o); 
		return true;
	case CMD_SAVE_SYMBOLS:
		return SavVarObs((GraphObj **)planes, nPlanes, 0L);
		}
	return false;
}

void
Ribbon::CreateObs()
{
	int i, j, n, rx, cx, ry, cy, rz, cz;
	double fx, fy, fz, tmp;
	fPOINT3D pg[5];
	AccRange *rX, *rY, *rZ;
	Triangle *trl, *trc, *trn;

	if(planes || !data) return;
	rX = rY = rZ = 0L;
	switch(type) {
	case 1:
		if(!ssRefX || !ssRefY) return;
		if(z_width == 0.0) z_width = 1.0;	if(relwidth == 0.0) relwidth = 0.6;
		if((rX = new AccRange(ssRefX)) && (rY = new AccRange(ssRefY))) {
			if(!data_desc) data_desc = rY->RangeDesc(data, 1);
			tmp = relwidth*z_width/2.0;
			if(!(values = (fPOINT3D*)calloc(i = rX->CountItems(), sizeof(fPOINT3D)))){
				delete rX;	delete rY;	return;
				}
			if(!(planes = (Plane3D**)calloc(i-1, sizeof(Plane3D*)))){
				free(values);	values = 0L;	delete rX;	delete rY;	return;
				}
			for(i = j = 0, rX->GetFirst(&cx, &rx), rY->GetFirst(&cy, &ry);
				rX->GetNext(&cx, &rx) && rY->GetNext(&cy, &ry); i++) {
				if(data->GetValue(rx, cx, &fx) && data->GetValue(ry, cy, &fy)) {
					values[i].fx = fx;	values[i].fy = fy;	values[i].fz = z_value;
					pg[3].fx = pg[2].fx = fx;	pg[3].fy = pg[2].fy = fy;
					pg[2].fz = z_value - tmp;	pg[3].fz = z_value +tmp;
					if(j) {
						pg[4].fx = pg[0].fx;	pg[4].fy = pg[0].fy; pg[4].fz = pg[0].fz;
						planes[i-1] = new Plane3D(this, data, pg, 5);
						if(planes[i-1]) planes[i-1]->Command(CMD_PG_FILL, &Fill, 0L);
						}
					j++;
					pg[0].fx = pg[3].fx;	pg[0].fy = pg[3].fy; pg[0].fz = pg[3].fz;
					pg[1].fx = pg[2].fx;	pg[1].fy = pg[2].fy; pg[1].fz = pg[2].fz;
					}
				}
			nPlanes = i-1;		nVal = i;
			}
		break;
	case 2:
		if(!ssRefX || !ssRefY || !ssRefZ) return;
		if((rX = new AccRange(ssRefX)) && (rY = new AccRange(ssRefY)) && (rZ = new AccRange(ssRefZ))) {
			if(!(values = (fPOINT3D*)calloc(i = rX->CountItems(), sizeof(fPOINT3D)))){
				delete rX;	delete rY;	delete rZ;	return;
				}
			if(!(planes = (Plane3D**)calloc(i-1, sizeof(Plane3D*)))){
				free(values);	values = 0L;	delete rX;	delete rY;	delete rZ;	return;
				}
			if(!data_desc) data_desc = rY->RangeDesc(data, 1);
			for(i = 0, rX->GetFirst(&cx, &rx), rY->GetFirst(&cy, &ry), rZ->GetFirst(&cz, &rz);
				rX->GetNext(&cx, &rx) && rY->GetNext(&cy, &ry) && rZ->GetNext(&cz, &rz); i++) {
				if(data->GetValue(rx, cx, &fx) && data->GetValue(ry, cy, &fy) &&
					data->GetValue(rz, cz, &fz)) {
					values[i].fx = fx;	values[i].fy = fy;	values[i].fz = fz;
					pg[3].fx = pg[2].fx = fx;	pg[2].fz = pg[3].fz = fz;		
					pg[3].fy = 0.0;				pg[2].fy = fy;
					if(i) {
						pg[4].fx = pg[0].fx;	pg[4].fy = pg[0].fy; pg[4].fz = pg[0].fz;
						planes[i-1] = new Plane3D(this, data, pg, 5);
						if(planes[i-1]) planes[i-1]->Command(CMD_PG_FILL, &Fill, 0L);
						}
					pg[0].fx = pg[3].fx;		pg[0].fy = pg[3].fy;	pg[0].fz = pg[3].fz;
					pg[1].fx = pg[2].fx;		pg[1].fy = pg[2].fy;	pg[1].fz = pg[2].fz;
					}
				}
			nPlanes = i-1;		nVal = i;
			}
		break;
	case 3:
		if(!ssRefX || !ssRefY || !ssRefZ) break;
		Undo.InvalidGO(this);
		trl = Triangulate1(ssRefX, ssRefZ, ssRefY, data);
		for(i = 0, trc = trl; trc; i++) trc = trc->next;
		if((n = i) && (planes = (Plane3D**)malloc(n*sizeof(Plane3D*)))) 
			for(i = nPlanes = 0, trc = trl; trc && i < n; i++) {
			for(j = 0; j < 4; j++) {	//swap y and z values;
				tmp = trc->pt[j].fz;	trc->pt[j].fz = trc->pt[j].fy;	trc->pt[j].fy = tmp;
				}
			planes[nPlanes++] = new Plane3D(this, data, trc->pt, 4);
			trn = trc->next;	delete trc;		trc = trn;
			}
		dirty = true;			Command(CMD_AUTOSCALE, 0L, 0L);
		break;
		}
	if(rX) delete rX;	if(rY) delete rY;	if(rZ) delete rZ;
}

void
Ribbon::UpdateObs(bool bNewData)
{
	int i, j, k, rx, cx, ry, cy, rz, cz;
	double fx, fy, fz, tmp, da1, da2;
	AccRange *rX, *rY, *rZ;
	int sel_id[] = {SIZE_XPOS, SIZE_YPOS, SIZE_ZPOS};

	if(!planes || (!values && type < 3)) return;
	rX = rY = rZ = 0L;
	switch(type) {
	case 1:
		if(!ssRefX || !ssRefY || !data) return;
		if(z_width == 0.0) z_width = 1.0;	if(relwidth == 0.0) relwidth = 0.6;
		if((rX = new AccRange(ssRefX)) && (rY = new AccRange(ssRefY))) {
			tmp = relwidth*z_width/2.0;
			for(i = 0, rX->GetFirst(&cx, &rx), rY->GetFirst(&cy, &ry);
				rX->GetNext(&cx, &rx) && rY->GetNext(&cy, &ry) && i < nVal; i++) {
				if(data->GetValue(rx, cx, &fx) && data->GetValue(ry, cy, &fy)) {
					if(bNewData) {
						values[i].fx = fx;	values[i].fy = fy;	values[i].fz = z_value;
						}
					else {
						fx = values[i].fx;	fy = values[i].fy;	values[i].fz = z_value;
						}
					if(i && planes[i-1]) {
						for(j = 0; j < 3; j++){
							for(k = 0; k <5; k++) {
								switch (j) {
								case 0:	
									da1 = values[i-1].fx;		da2 = values[i].fx;
									break;
								case 1:
									da1 = values[i-1].fy;		da2 = values[i].fy;
									break;
								case 2:
									if(k != 1 && k != 2) da1 = da2 = (values[i].fz + tmp);
									else da1 = da2 = (values[i].fz - tmp);
									break;
									}
								planes[i-1]->SetSize(sel_id[j]+k, (k != 2 && k != 3) ? da1 : da2);
								}
							}
						}
					}
				}
			}
		break;
	case 2:
		if(!ssRefX || !ssRefY || !ssRefZ || !data) return;
		if((rX = new AccRange(ssRefX)) && (rY = new AccRange(ssRefY)) && (rZ = new AccRange(ssRefZ))) {
			for(i = 0, rX->GetFirst(&cx, &rx), rY->GetFirst(&cy, &ry), rZ->GetFirst(&cz, &rz);
				rX->GetNext(&cx, &rx) && rY->GetNext(&cy, &ry) && rZ->GetNext(&cz, &rz) && i < nVal; i++) {
				if(data->GetValue(rx, cx, &fx) && data->GetValue(ry, cy, &fy) && data->GetValue(rz, cz, &fz)) {
					values[i].fx = fx;	values[i].fy = fy;	values[i].fz = fz;
					if(i && planes[i-1]) {
						planes[i-1]->SetSize(SIZE_XPOS, values[i-1].fx);	planes[i-1]->SetSize(SIZE_XPOS+3, fx);
						planes[i-1]->SetSize(SIZE_XPOS+1, values[i-1].fx);	planes[i-1]->SetSize(SIZE_XPOS+2, fx);
						planes[i-1]->SetSize(SIZE_XPOS+4, values[i-1].fx);
						planes[i-1]->SetSize(SIZE_YPOS+1, values[i-1].fy);	planes[i-1]->SetSize(SIZE_YPOS+2, fy);
						planes[i-1]->SetSize(SIZE_ZPOS, values[i-1].fz);	planes[i-1]->SetSize(SIZE_ZPOS+3, fz);
						planes[i-1]->SetSize(SIZE_ZPOS+1, values[i-1].fz);	planes[i-1]->SetSize(SIZE_ZPOS+2, fz);
						planes[i-1]->SetSize(SIZE_ZPOS+4, values[i-1].fz);
						}
					}
				}
			}
		break;
	case 3:
		if(planes) {
			for(i = 0; i < nPlanes; i++) if(planes[i]) DeleteGO(planes[i]);
			free(planes);		planes = 0L;	nPlanes = 0;
			}
		CreateObs();
		}
	if(rX) delete rX;	if(rY) delete rY;	if(rZ) delete rZ;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// draw a 3dimensional grid
Grid3D::Grid3D(GraphObj *par, DataObj *d, int sel, double x1, double xstep, double z1, double zstep)
	:Plot(par, d)
{
	FileIO(INIT_VARS);		Id = GO_GRID3D;
	start.fx = x1;			step.fx = xstep;
	start.fz = z1;			step.fz = zstep;
	type = sel;
}

Grid3D::Grid3D(int src):Plot(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
}

Grid3D::~Grid3D()
{
	int i;

	Undo.InvalidGO(this);
	if(lines) {
		for(i = 0; i < nLines; i++) if(lines[i]) DeleteGO(lines[i]);
		free(lines);		lines = 0L;
		}
	if(planes) {
		for(i = 0; i < nPlanes; i++) if(planes[i]) DeleteGO(planes[i]);
		free(planes);		planes = 0L;
		}
	nLines = nPlanes = 0;
	if(name) free(name);	name=0L;
}

bool 
Grid3D::SetSize(int select, double value)
{
	int i;

	switch (select) {
	case SIZE_SYM_LINE:
		if(planes) for(i = 0; i < nPlanes; i++) if(planes[i]) planes[i]->SetSize(select, value); 
		return true;
		}
	return false;
}

bool
Grid3D::SetColor(int select, DWORD col)
{
	int i;

	switch (select) {
	case COL_POLYLINE:
		if(planes) for(i = 0; i < nPlanes; i++) if(planes[i]) planes[i]->SetColor(select, col); 
		return true;
		}
	return false;
}

void
Grid3D::DoPlot(anyOutput *o)
{
	int i;

	if(!lines && !planes) CreateObs(false);
	if(lines) for(i = 0; i < nLines; i++) if(lines[i]) lines[i]->DoPlot(o);
	if(planes) for(i = 0; i < nPlanes; i++) if(planes[i]) planes[i]->DoPlot(o);
	dirty = false;
}

bool
Grid3D::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i;
	MouseEvent *mev;

	switch (cmd) {
	case CMD_MOUSE_EVENT:
		if(hidden) return false;
		mev = (MouseEvent *) tmpl;
		switch(mev->Action) {
		case MOUSE_LBUP:
			if(lines && !CurrGO) for(i = 0; i < nLines; i++)
				if(lines[i]) if(lines[i]->Command(cmd, tmpl, o)) return true;
			if(planes && !CurrGO) for(i = 0; i < nPlanes; i++)
				if(planes[i]) if(planes[i]->Command(cmd, tmpl, o)) return true;
			break;
			}
		break;
	case CMD_SET_LINE:
		if(tmpl) {
			memcpy(&Line, tmpl, sizeof(LineDEF));
			if(lines) {
				SavVarObs((GraphObj**)lines, nLines, 0L);
				for(i = 0; i < nLines; i++)
					if(lines[i]) lines[i]->Command(cmd, tmpl, o);
				}
			if(planes) {
				SavVarObs((GraphObj**)planes, nPlanes, 0L);
				for(i = 0; i < nPlanes; i++)
					if(planes[i]) planes[i]->Command(cmd, tmpl, o);
				}
			}
		break;
	case CMD_LEGEND:
		if(!hidden) ((Legend*)tmpl)->HasFill(&Line, planes ? &Fill : 0L, 0L);
		break;
	case CMD_CONFIG:
		return Configure();
	case CMD_MRK_DIRTY:
		dirty = true;
	case CMD_SET_GO3D:		case CMD_SETSCROLL:		case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_SET_DATAOBJ:
		Id = GO_GRID3D;
		if(tmpl == data) return true;
		data = (DataObj *)tmpl;
	case CMD_UPDATE:
		if(lines) for(i = 0; i < nLines; i++) if(lines[i]) lines[i]->Command(cmd, tmpl, o);
		if(planes) for(i = 0; i < nPlanes; i++) if(planes[i]) planes[i]->Command(cmd, tmpl, o);
		return dirty = true;
	case CMD_AUTOSCALE:
		if(!lines && !planes) CreateObs(false);
		if(dirty) {
			Bounds.Xmin = Bounds.Ymin = HUGE_VAL;	Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
			xBounds.fx = yBounds.fx = zBounds.fx = HUGE_VAL;
			xBounds.fy = yBounds.fy = zBounds.fy = -HUGE_VAL;
			if(lines) for(i = 0; i < nLines; i++)
				if(lines[i]) lines[i]->Command(cmd, tmpl, o);
			if(planes) for(i = 0; i < nPlanes; i++)
				if(planes[i]) planes[i]->Command(cmd, tmpl, o);
			}
		if(zBounds.fx > zBounds.fy) zBounds.fx = zBounds.fy = 0.0;
		if(parent && parent->Id > GO_PLOT && parent->Id < GO_GRAPH &&
			xBounds.fx <= xBounds.fy && yBounds.fx <= yBounds.fy){
			((Plot*)parent)->CheckBounds3D(xBounds.fx, yBounds.fx, zBounds.fx);
			((Plot*)parent)->CheckBounds3D(xBounds.fy, yBounds.fy, zBounds.fy);
			}
		return true;
	case CMD_SYM_FILL:
		if(!tmpl) return false;
		memcpy(&Fill, tmpl, sizeof(FillDEF));
		if(planes) for(i = 0; i < nPlanes; i++) if(planes[i]) planes[i]->Command(cmd, tmpl, o); 
		return true;
	case CMD_SAVE_SYMBOLS:
		return SavVarObs((GraphObj **)planes, nPlanes, 0L);
	case CMD_DELOBJ:
		if(!parent) return false;
		if(DeleteGOL((GraphObj***)&lines,nLines,(GraphObj*)tmpl,o)) return parent->Command(CMD_REDRAW, 0L, o);
		if(DeleteGOL((GraphObj***)&planes,nPlanes,(GraphObj*)tmpl,o)) return parent->Command(CMD_REDRAW, 0L, o);
		break;
		}
	return false;
}

void
Grid3D::CreateObs(bool set_undo)
{
	int i, ir, ic, idx, w, h;
	fPOINT3D *vec;

	if(!parent || !data || lines || planes) return;
	dirty = true;
	if(type == 0) {
		if(!(vec = (fPOINT3D*)malloc(sizeof(fPOINT3D) * 2))) return;
		data->GetSize(&w, &h);
		if(0 >= (nLines = 2 * w * h - w - h)) return;
		if(!(lines =(Line3D**)calloc(nLines, sizeof(Line3D*)))) return;
		vec[0].fz = start.fz;			data->GetValue(0, 0, &vec[0].fy);
		for(ic = 1, idx = 0; ic <= w; ic++) {
			vec[0].fx = start.fx;
			data->GetValue(0, ic-1, &vec[0].fy);
			for(ir = 1; ir <= h; ir++){
				if(ic < w && data->GetValue(ir-1, ic, &vec[1].fy)) {
					vec[1].fz = vec[0].fz + step.fz;	vec[1].fx = vec[0].fx;
					lines[idx++] = new Line3D(this, data, vec, 2, 
						-1, -1, ic-1, ir-1, -1, -1, -1, -1, ic, ir-1, -1, -1);
					}
				if(ir < h && data->GetValue(ir, ic-1, &vec[1].fy)) {
					vec[1].fz = vec[0].fz;	vec[1].fx = vec[0].fx + step.fx;
					lines[idx++] = new Line3D(this, data, vec, 2,
						-1, -1, ic-1, ir-1, -1, -1, -1, -1, ic-1, ir, -1, -1);
					}
				vec[0].fx += step.fx;	vec[0].fy = vec[1].fy;
				}
			vec[0].fz += step.fz;
			}
		for(i = 0; i < nLines; i++) if(lines[i]) lines[i]->Command(CMD_SET_LINE, &Line, 0L);
		free(vec);
		}
	else if(type == 1) {
		if(!(vec = (fPOINT3D*)malloc(sizeof(fPOINT3D) * 5))) return;
		vec[0].fz = vec[4].fz = start.fz;
		vec[3].fz = (start.fz +step.fz);
		data->GetSize(&w, &h);
		if(0 >= (nPlanes = w * h)) return;
		if(!(planes =(Plane3D**)calloc(nPlanes, sizeof(Plane3D*)))) return;
		for(ic = 1, idx = 0; ic <= w; ic++) {
			vec[0].fx = vec[3].fx = vec[4].fx = (start.fx+step.fx);
			vec[1].fx = vec[2].fx = start.fx;
			vec[1].fz = vec[4].fz;	vec[2].fz = vec[3].fz;
			data->GetValue(0, ic-1, &vec[1].fy);	data->GetValue(0, ic, &vec[2].fy);
			for(ir = 1; ir <= h; ir++){
				if(ic < w && ir < h && data->GetValue(ir, ic, &vec[3].fy) 
					&& data->GetValue(ir, ic-1, &vec[4].fy)) {
					vec[0].fz = vec[4].fz;	vec[0].fy = vec[4].fy;	vec[0].fx = vec[4].fx;
					planes[idx++] = new Plane3D(this, 0L, vec, 5);
					}
				vec[1].fz = vec[4].fz;		vec[1].fy = vec[4].fy;		vec[1].fx = vec[4].fx;
				vec[2].fz = vec[3].fz;		vec[2].fy = vec[3].fy;		vec[2].fx = vec[3].fx;
				vec[3].fx += step.fx;		vec[4].fx += step.fx;
				}
			vec[3].fz += step.fz;			vec[4].fz += step.fz;
			}
		nPlanes = idx;
		for(i = 0; i < nPlanes; i++) if(planes[i]){
			planes[i]->Command(CMD_SET_LINE, &Line, 0L);
			planes[i]->Command(CMD_SYM_FILL, &Fill, 0L);
			}
		SetSize(SIZE_SYM_LINE, Line.width);		SetColor(COL_POLYLINE, Line.color);
		free(vec);
		}
	if(set_undo) {
		if(planes && nPlanes)Undo.StoreListGO(parent, (GraphObj***)&planes, &nPlanes, UNDO_CONTINUE);
		if(lines && nLines)Undo.StoreListGO(parent, (GraphObj***)&lines, &nLines, UNDO_CONTINUE);
		}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// define minima and maxima rectangle to be used by graph
Limits::Limits(int src):Plot(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
}

Limits::~Limits()
{
	if(name) free(name);		name=0L;
}

double
Limits::GetSize(int select)
{
	return 0.0;
}

bool
Limits::Command(int cmd, void *tmpl, anyOutput *o)
{
	switch (cmd) {
	case CMD_SET_DATAOBJ:
		Id = GO_LIMITS;
		data = (DataObj *)tmpl;	
		return true;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Calculate and display a user defined function
Function::Function(GraphObj *par, DataObj *d, char *desc):Plot(par, d)
{
	FileIO(INIT_VARS);		cmdxy = (char*)malloc(20*sizeof(char));
	if(parent && parent->Id == GO_POLARPLOT) {
		x1 = 0.0;			x2 = 360.0;			xstep = 0.5;
		if(cmdxy)rlp_strcpy(cmdxy, 20, (char*)"sin(pi*x/30)+1.1");
		}
	else {
		x1 = 0.0;			x2 = 100.0;			xstep = 0.5;
		if(cmdxy)rlp_strcpy(cmdxy, 20, (char*)"sin(x)/x");
		}
	if(desc) name = (char*)memdup(desc, (int)strlen(desc)+1, 0);
	Id = GO_FUNCTION;
}

Function::Function(int src):Plot(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
}

Function::~Function()
{
	if(cmdxy) free(cmdxy);		cmdxy = 0L;
	if(param) free(param);		param = 0L;
	if(dl) DeleteGO(dl);		dl = 0L;
	if(name) free(name);		name=0L;
}

bool
Function::SetSize(int select, double value)
{
	switch(select & 0xfff){
	case SIZE_MIN_X:	x1 = value;		return true;
	case SIZE_MAX_X:	x2 = value;		return true;
	case SIZE_XSTEP:	xstep=value;	return true;
		}
	return false;
}

void
Function::DoPlot(anyOutput *o)
{
	if((!dl || dirty) && cmdxy && cmdxy[0]) Update(o, 0);
	dirty = false;
	if(dl && o) {
		dl->Command(CMD_SET_LINE, &Line, o);
		dl->DoPlot(o);
		}
}

bool
Function::Command(int cmd, void *tmpl, anyOutput *o)
{
	switch (cmd) {
	case CMD_LEGEND:	case CMD_MOUSE_EVENT:
		if(hidden) return false;
		if(dl) return dl->Command(cmd, tmpl, o);
		break;
	case CMD_SCALE:
		Line.patlength *= ((scaleINFO*)tmpl)->sy.fy;		Line.width *= ((scaleINFO*)tmpl)->sy.fy;
		return true;
	case CMD_DELOBJ:
		if(parent && tmpl && tmpl == dl) return parent->Command(CMD_DELOBJ, this, o);
		break;
	case CMD_MRK_DIRTY:
		if(parent) parent->Command(cmd, tmpl, o);
		return dirty = true;
	case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_SET_LINE:
		if(tmpl) memcpy(&Line, tmpl, sizeof(LineDEF));
		break;
	case CMD_SET_DATAOBJ:
		if(dl) dl->Command(cmd, tmpl, o);
		Id = GO_FUNCTION;
		data = (DataObj *)tmpl;
		return true;
	case CMD_SETPARAM:
		if(tmpl) {
			if(param) free(param);			param = 0L;
			if(*((char*)tmpl))param = (char*)memdup(tmpl, (int)strlen((char*)tmpl)+1, 0);
			}
		dirty = true;
		return true;
	case CMD_SETFUNC:
		if(tmpl) {
			if(cmdxy) free(cmdxy);			cmdxy = 0L;
			if(*((char*)tmpl))cmdxy = (char*)memdup(tmpl, (int)strlen((char*)tmpl)+1, 0);
			}
		dirty = true;
		return true;
	case CMD_UPDATE:
		return Update(o, UNDO_CONTINUE);
	case CMD_AUTOSCALE:
		if(!dl) return Update(o, 0L);
		if(dirty) {
			Bounds.Xmin = Bounds.Ymin = HUGE_VAL;
			Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
			xBounds.fx = yBounds.fx = zBounds.fx = HUGE_VAL;
			xBounds.fy = yBounds.fy = zBounds.fy = -HUGE_VAL;
			if(dl) dl->Command(cmd, tmpl, o);
			dirty = false;
			}
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH
			&& Bounds.Xmax > Bounds.Xmin && Bounds.Ymax > Bounds.Ymin) {
			((Plot*)parent)->CheckBounds(Bounds.Xmin, Bounds.Ymin);
			((Plot*)parent)->CheckBounds(Bounds.Xmax, Bounds.Ymax);
			}
		return true;
		}
	return false;
}

bool
Function::Update(anyOutput *o, DWORD flags)
{
	lfPOINT *xydata;
	long ndata;

	if(!parent || !cmdxy) return false;
	LockData(false, false);
	do_xyfunc(data, x1, x2, xstep, cmdxy, &xydata, &ndata, param);
	LockData(false, false);
	if(xydata && ndata >1) {
		if(!dl) dl = new DataLine(this, data, xydata, ndata, name);
		else dl->LineData(xydata, ndata);
		dirty = true;
		Command(CMD_AUTOSCALE, 0L, 0L);
		return true;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Calculate and display a user defined function
static char *lastFunc2D = 0L, *lastParam2D=0L;
FitFunc::FitFunc(GraphObj *par, DataObj *d):Plot(par, d)
{
	FileIO(INIT_VARS);
	x1 = 0.0;			x2 = 100.0;					xstep = 0.5;		dl = 0L;
	if(lastFunc2D && lastFunc2D[0] && lastParam2D && lastParam2D[0]) {
		cmdxy = (char*)memdup(lastFunc2D, (int)strlen(lastFunc2D)+1, 0);
		parxy = (char*)memdup(lastParam2D, (int)strlen(lastParam2D)+1, 0);
		}
	if(!cmdxy || !parxy) {
		cmdxy = (char*)malloc(20*sizeof(char));		parxy = (char*)malloc(20*sizeof(char));
		if(cmdxy) rlp_strcpy(cmdxy, 20, "a+b*x^c");
		if(parxy) rlp_strcpy(parxy, 20, "a=1; b=1; c=0.1;");
		}
	Id = GO_FITFUNC;
}


FitFunc::FitFunc(int src):Plot(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
}

FitFunc::~FitFunc()
{
	int i;

	if(Symbols) {
		for(i = 0; i< nPoints; i++) if(Symbols[i]) DeleteGO(Symbols[i]);
		free(Symbols);
		}
	if(cmdxy) free(cmdxy);		cmdxy = 0L;
	if(parxy) free(parxy);		parxy = 0L;
	if(ssXref) free(ssXref);	ssXref = 0L;
	if(ssYref) free(ssYref);	ssYref = 0L;
	if(dl) DeleteGO(dl);		dl = 0L;
	if(name) free(name);		name=0L;
	Undo.InvalidGO(this);
}

bool
FitFunc::SetSize(int select, double value)
{
	int i;

	switch(select & 0xfff){
	case SIZE_SYMBOL:
	case SIZE_SYM_LINE:
		if(Symbols)	for(i = 0; i < nPoints; i++) 
			if(Symbols[i]) Symbols[i]->SetSize(select, value);
		return true;
		}
	return false;
}

bool
FitFunc::SetColor(int select, DWORD col)
{
	int i;

	switch(select) {
	case COL_SYM_LINE:
	case COL_SYM_FILL:
		if(Symbols) for(i = 0; i < nPoints; i++)
			if(Symbols[i]) Symbols[i]->SetColor(select, col);
		return true;
		}
	return false;
}

void
FitFunc::DoPlot(anyOutput *o)
{
	int i;

	if(!data || x1 >= x2) return;
	dirty = false;
	if(!dl && (dl = new Function(this, data, "Fitted function"))) {
		dl->SetSize(SIZE_MIN_X, x1);			dl->SetSize(SIZE_MAX_X, x2);
		dl->SetSize(SIZE_XSTEP, xstep);			dl->Command(CMD_SETFUNC, cmdxy, 0L);
		dl->Command(CMD_SETPARAM, parxy, 0L);	dl->Command(CMD_SET_LINE, &Line, 0L);
		dl->Update(o, UNDO_CONTINUE);
		}
	if(dl && o) {
		dl->SetSize(SIZE_MIN_X, x1);			dl->SetSize(SIZE_MAX_X, x2);
		dl->SetSize(SIZE_XSTEP, xstep);			dl->Command(CMD_SETFUNC, cmdxy, 0L);
		dl->Command(CMD_SETPARAM, parxy, 0L);	dl->Command(CMD_SET_LINE, &Line, 0L);
		dl->DoPlot(o);
		}
	if(Symbols) for(i = 0; i < nPoints; i++) if(Symbols[i]) Symbols[i]->DoPlot(o);
}

bool 
FitFunc::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i;
	MouseEvent *mev;
	LineDEF *ld;

	switch(cmd) {
	case CMD_LEGEND:
		if(tmpl && ((GraphObj*)tmpl)->Id == GO_LEGEND && dl && dl->Id == GO_FUNCTION) {
			ld = dl->GetLine();
			if(Symbols) {
				for (i = 0; i < nPoints && i < 100; i++)
					if(Symbols[i]) ((Legend*)tmpl)->HasSym(ld, Symbols[i], "Fitted function");
				}
			else ((Legend*)tmpl)->HasFill(ld, 0L, dl->name);
			return true;
			}
		return false;
	case CMD_ENDDIALOG:
		if(!cmdxy || !parxy) return false;
		if(i = (int)strlen(cmdxy)) {
			if(lastFunc2D = (char*)realloc(lastFunc2D, i+2))
				rlp_strcpy(lastFunc2D, i+1, cmdxy);
			}
		if(i = (int)strlen(parxy)) {
			if(lastParam2D = (char*)realloc(lastParam2D, i+2))
				rlp_strcpy(lastParam2D, i+1, parxy);
			}
		return true;
	case CMD_SCALE:
		if(dl) return dl->Command(cmd, tmpl, o);
		if(Symbols) for(i = 0; i < nPoints; i++) if(Symbols[i]) Symbols[i]->Command(cmd, tmpl, o);
		Line.patlength *= ((scaleINFO*)tmpl)->sy.fy;		Line.width *= ((scaleINFO*)tmpl)->sy.fy;
		return true;
	case CMD_MOUSE_EVENT:
		if(hidden) return false;
		mev = (MouseEvent *) tmpl;
		switch(mev->Action) {
		case MOUSE_LBUP:
			//select objects invers to plot order
			if(Symbols && !CurrGO) for(i = nPoints-1; i >=0; i--)
				if(Symbols[i] && Symbols[i]->Command(cmd, tmpl, o))return true;
			break;
			}
		if(dl) return dl->Command(cmd, tmpl, o);
		return false;
	case CMD_AUTOSCALE:
		if(dirty) {
			if(!dl && (dl = new Function(this, data, "Fitted function"))) {
				dl->SetSize(SIZE_MIN_X, x1);			dl->SetSize(SIZE_MAX_X, x2);
				dl->SetSize(SIZE_XSTEP, xstep);			dl->Command(CMD_SETFUNC, cmdxy, 0L);
				dl->Command(CMD_SETPARAM, parxy, 0L);	dl->Command(CMD_SET_LINE, &Line, 0L);
				dl->Update(o, UNDO_CONTINUE);
				}
			if(dl) {
				dl->Command(cmd, tmpl, o);
				memcpy(&Bounds, &dl->Bounds, sizeof(fRECT));
				}
			if(Symbols) for(i = 0; i < nPoints; i++) if(Symbols[i])Symbols[i]->Command(cmd, tmpl, o);
			dirty = false;
			}
		return true;
	case CMD_UPDATE:
		if(Symbols) {
			SavVarObs((GraphObj**)Symbols, nPoints, UNDO_CONTINUE);
			for(i = 0; i < nPoints; i++) if(Symbols[i]) Symbols[i]->Command(cmd, tmpl, o);
			}
		Undo.String(this, &parxy, UNDO_CONTINUE);
		do_fitfunc(data, ssXref, ssYref, 0L, &parxy, cmdxy, conv, maxiter, &chi2);
		if(!dl) dl = new Function(this, data, "Fitted function");
		if(dl){
			dl->SetSize(SIZE_MIN_X, x1);			dl->SetSize(SIZE_MAX_X, x2);
			dl->SetSize(SIZE_XSTEP, xstep);			dl->Command(CMD_SETFUNC, cmdxy, 0L);
			dl->Command(CMD_SETPARAM, parxy, 0L);	dl->Command(CMD_SET_LINE, &Line, 0L);
			dl->Update(o, UNDO_CONTINUE);
			}
		dirty = true;
		if(parent) parent->Command(CMD_MRK_DIRTY, 0L, o);
		return true;
	case CMD_DELOBJ:
		if(!parent) return false;
		if(tmpl && tmpl == dl) return parent->Command(CMD_DELOBJ, this, o);
		else if(DeleteGOL((GraphObj***)&Symbols,nPoints,(GraphObj*)tmpl,o)) return parent->Command(CMD_REDRAW,0L,o);
		else if(dl) return dl->Command(cmd, tmpl, o);
		return false;
	case CMD_MRK_DIRTY:
		dirty = true;
		if(dl){
			dl->SetSize(SIZE_MIN_X, x1);			dl->SetSize(SIZE_MAX_X, x2);
			dl->SetSize(SIZE_XSTEP, xstep);
			}
	case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_SET_DATAOBJ:
		if(dl) dl->Command(cmd, tmpl, o);
		if(Symbols) for(i = 0; i < nPoints; i++)
			if(Symbols[i]) Symbols[i]->Command(cmd, tmpl, o);
		Id = GO_FITFUNC;
		data = (DataObj *)tmpl;
		return true;
	case CMD_SYMTEXT:		case CMD_SYMTEXT_UNDO:	case CMD_SYM_RANGETEXT:
	case CMD_SYMTEXTDEF:	case CMD_SYM_TYPE:
		if(Symbols) for(i = 0; i < nPoints; i++)
			if(Symbols[i]) Symbols[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_SAVE_SYMBOLS:
		return SavVarObs((GraphObj **)Symbols, nPoints, 0L);
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// normal quantile plot and derivates
NormQuant::NormQuant(GraphObj *par, DataObj *d, char* range)
	:Plot(par, d)
{
	FileIO(INIT_VARS);
	if(range && range[0]) ssRef = (char*)memdup(range, (int)strlen(range)+1, 0);
	else ssRef = 0L;
	Id = GO_NORMQUANT;
}

NormQuant::NormQuant(GraphObj *par, DataObj *d, double *val, int nval)
	:Plot(par, d)
{
	FileIO(INIT_VARS);		ssRef = 0L;
	if(val && nval) {
		src_data = (double*)memdup(val, nval*sizeof(double), 0);
		SortArray(nData = nval, src_data);		ProcessData();
		}
	Id = GO_NORMQUANT;
}

NormQuant::NormQuant(int src):Plot(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		if(nData)SortArray(nData, src_data);	ProcessData();
		}
}

NormQuant::~NormQuant()
{
	if(ssRef) free(ssRef);		ssRef = 0L;
	if(x_info) free(x_info);	x_info = 0L;
	if(y_info) free(y_info);	y_info = 0L;
	if(x_vals) free(x_vals);	x_vals = 0L;
	if(y_vals) free(y_vals);	y_vals = 0L;
	if(src_data)free(src_data);	src_data = 0L;
	if(sy)delete(sy);
}

void
NormQuant::DoPlot(anyOutput *o)
{
	int i;

	//draw symbols
	if(sy && y_vals && src_data && y_vals && nValidData) {
		sy->SetSize(SIZE_SYMBOL, defs.GetSize(SIZE_SYMBOL)/10.0);
		for(i = 0; i < nValidData; i++) {
			sy->SetSize(SIZE_XPOS, x_vals[i]);
			sy->SetSize(SIZE_YPOS, y_vals[i]);
			sy->DoPlot(o);
			}
		}
}

bool
NormQuant::Command(int cmd, void *tmpl, anyOutput *o)
{
	switch(cmd) {
	case CMD_LEGEND:
		if(sy) ((Legend*)tmpl)->HasSym(0L, sy, x_info ? x_info : (char*)"Data");
		return true;
	case CMD_SCALE:
		return true;
	case CMD_UPDATE:
		return true;
	case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_SET_DATAOBJ:
		Id = GO_NORMQUANT;
		if(sy) sy->Command(cmd, tmpl, o);
		data = (DataObj *)tmpl;
		return true;
		}
	return false;
}

bool
NormQuant::ProcessData()
{
	int i, r, c, n;
	AccRange *rD;
	double y, dtmp, sum;

	if(data && ssRef && ssRef[0] && (rD = new AccRange(ssRef))) {
		if((n = rD->CountItems()) && (src_data = (double*)realloc(src_data, n * sizeof(double)))){
			for(nData = 0, rD->GetFirst(&c, &r); rD->GetNext(&c, &r); ) {
				if(data->GetValue(r, c, &dtmp)) src_data[nData++] = dtmp;
				}
			if(nData)SortArray(nData, src_data);
			}
		if(y_info = (char*)malloc(20)){
			rlp_strcpy(y_info, 20, "Normal quantiles");
			}
		x_info = rD->RangeDesc(data, 2);
		delete rD;
		}
	if(src_data && nData) {
		Bounds.Ymin = HUGE_VAL;			Bounds.Ymax = -HUGE_VAL;
		x_vals = (double*)realloc(x_vals, nData * sizeof(double));
		y_vals = (double*)realloc(y_vals, nData * sizeof(double));
		for(n = i = 0, sum = dtmp = 1.0/((double)nData); i < (nData-1); i++ ) {
			y = distinv(norm_dist, 0.0, 1.0, sum, 0.5);
			if(y > -HUGE_VAL && y < HUGE_VAL) {
				y_vals[n] = y;			x_vals[n] = src_data[i];
				if(y < Bounds.Ymin) Bounds.Ymin = y;
				if(y > Bounds.Ymax) Bounds.Ymax = y;
				n++;
				}
			sum += dtmp;
			}
		Bounds.Xmax = src_data[nData-1];	Bounds.Xmin = src_data[0];
		if(Bounds.Ymax <= Bounds.Ymin) {
			Bounds.Ymin = -5.0;		Bounds.Ymax = 5.0;
			}
		nValidData = n;
		return (n > 3);
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Contour Plot
ContourPlot::ContourPlot(GraphObj *par, DataObj *d)
	:Plot(par, d)
{
	FileIO(INIT_VARS);		Id = GO_CONTOUR;
}

ContourPlot::ContourPlot(int src):Plot(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
}

ContourPlot::~ContourPlot()
{
	int i;

	if(Symbols) {
		for(i = 0; i < nSym; i++) if(Symbols[i]) DeleteGO(Symbols[i]);
		free(Symbols);
		Symbols = 0L;		nSym = 0;
		}
	if(Labels) {
		for(i = 0; i < nLab; i++) if(Labels[i]) DeleteGO(Labels[i]);
		free(Labels);
		Labels = 0L;		nLab = 0;
		}
	if(zAxis)			DeleteGO(zAxis);
	if(ssRefX) free(ssRefX);	if(ssRefY) free(ssRefY);
	if(ssRefZ) free(ssRefZ);	ssRefX = ssRefY = ssRefZ = 0L;
	Undo.InvalidGO(this);
	if(name) free(name);		name=0L;
	if(val) free(val);		val = 0L;
}

bool
ContourPlot::SetSize(int select, double value)
{
	int i;

	switch(select & 0xfff){
	case SIZE_SYMBOL:		case SIZE_SYM_LINE:
		if(Symbols) for(i = 0; i < nSym; i++) 
			if(Symbols[i]) Symbols[i]->SetSize(select, value);
		return true;
	case SIZE_LB_XDIST:		case SIZE_LB_YDIST:
		if(Labels) for(i = 0; i < nLab; i++) 
			if(Labels[i]) Labels[i]->SetSize(select, value);
		return true;
	}
	return false;
}

bool
ContourPlot::SetColor(int select, DWORD col)
{
	int i;

	switch(select) {
	case COL_SYM_LINE:	case COL_SYM_FILL:
		if(Symbols) for(i = 0; i < nSym; i++)
			if(Symbols[i]) Symbols[i]->SetColor(select, col);
		return true;
		}
	return false;
}

void 
ContourPlot::DoPlot(anyOutput *o)
{
	FillDEF bg_fill={0, 0x0L, 1.0, 0L, 0x0L};
	LineDEF bg_line = {0.0, 1.0, 0x0L, 0x0L};
	POINT clp[4];
	int i;

	if(!zAxis){
		DoAxis(o);
		DoTriangulate();
		}
	if(zAxis) {
		clp[0].x = clp[3].x = iround(o->Box1.Xmin);	clp[0].y = clp[1].y = iround(o->Box1.Ymin);
		clp[1].x = clp[2].x = iround(o->Box1.Xmax);	clp[2].y = clp[3].y = iround(o->Box1.Ymax);
		ClipBezier(0L, 0L, clp[0], clp[0], clp[0], clp[0], &clp[0], &clp[2]);	//set clipping rectangle
		bg_fill.color = bg_fill.color2 = bg_line.color = zAxis->GradColor(z_axis.min);
		o->SetFill(&bg_fill);				o->SetLine(&bg_line);
		o->oPolygon(clp, 4, 0L);
		zAxis->Command(CMD_DRAWPG, 0L, o);
		if(Symbols) {
			for(i = 0; i < nSym; i++) if(Symbols[i]) Symbols[i]->DoPlot(o);
			}
		if(Labels) {
			for(i = 0; i < nLab; i++) if(Labels[i]) Labels[i]->DoPlot(o);
			}
		zAxis->DoPlot(o);
		}
}

bool
ContourPlot::Command(int cmd, void *tmpl, anyOutput *o)
{
	MouseEvent *mev;
	int i;

	switch (cmd) {
	case CMD_MOUSE_EVENT:
		if(hidden) return false;
		mev = (MouseEvent *) tmpl;
		switch(mev->Action) {
		case MOUSE_LBUP:
			if(Symbols) for (i = nSym-1; i >= 0; i--)
				if(Symbols[i] && Symbols[i]->Command(cmd, tmpl, o)) return true;
			if(Labels) for (i = nLab-1; i >= 0; i--)
				if(Labels[i] && Labels[i]->Command(cmd, tmpl, o)) return true;
			if(zAxis && zAxis->Command(cmd, tmpl, o)) return true;
			break;
			}
		break;
	case CMD_SETTEXTDEF:
		if(Labels) for (i = nLab-1; i >= 0; i--)
			if(Labels[i]) Labels[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_SCALE:
		if(Symbols) for (i = nSym-1; i >= 0; i--)
			if(Symbols[i]) Symbols[i]->Command(cmd, tmpl, o);
		if(Labels) for (i = nLab-1; i >= 0; i--)
			if(Labels[i]) Labels[i]->Command(cmd, tmpl, o);
		if(zAxis) zAxis->Command(cmd, tmpl, o);
		return true;
	case CMD_DELOBJ:
		if(parent && DeleteGOL((GraphObj***)&Symbols, nSym, (GraphObj*) tmpl, o))
			return parent->Command(CMD_REDRAW,0L,o);
		if(parent && DeleteGOL((GraphObj***)&Labels, nLab, (GraphObj*) tmpl, o))
			return parent->Command(CMD_REDRAW,0L,o);
		return false;
	case CMD_UPDATE:
		if(Symbols) Undo.DropListGO(this, (GraphObj***)&Symbols, &nSym, UNDO_CONTINUE);
		if(Labels) Undo.DropListGO(this, (GraphObj***)&Labels, &nLab, UNDO_CONTINUE);
		LoadData(ssRefX, ssRefY, ssRefZ);
//		if(zAxis) zAxis->Command(CMD_RECALC, tmpl, o);
//		else DoAxis(o);
		DoAxis(o);
		if(zAxis) DoTriangulate();
		return true;
	case CMD_RECALC:
		if(zAxis) zAxis->Command(cmd, tmpl, o);
		else DoAxis(o);
		if(zAxis) DoTriangulate();
		return true;
	case CMD_SET_DATAOBJ:
		Id = GO_CONTOUR;
		if(Symbols) for (i = nSym-1; i >= 0; i--)
			if(Symbols[i]) Symbols[i]->Command(cmd, tmpl, o);
		if(Labels) for (i = nLab-1; i >= 0; i--)
			if(Labels[i]) Labels[i]->Command(cmd, tmpl, o);
		if(zAxis) zAxis->Command(cmd, tmpl, o);
		break;
	case CMD_MRK_DIRTY:
		dirty = true;
	case CMD_SETSCROLL:		case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		return false;
	case CMD_USEAXIS:
		return UseAxis(*((int*)tmpl));
	case CMD_SAVE_SYMBOLS:
		return SavVarObs((GraphObj **)Symbols, nSym, 0L);
	case CMD_SAVE_LABELS:
		return SavVarObs((GraphObj **)Labels, nLab, 0L);
	case CMD_SYMTEXT:	case CMD_SYMTEXT_UNDO:		case CMD_SYM_RANGETEXT:
	case CMD_SYMTEXTDEF:	case CMD_SYM_TYPE:
		if(Symbols) for(i = 0; i < nSym; i++)
			if(Symbols[i]) Symbols[i]->Command(cmd, tmpl, o);
		return true;
		}
	return false;
}

bool
ContourPlot::LoadData(char *xref, char *yref, char *zref)
{
	AccRange *rX, *rY, *rZ;
	int i, n, cx, cy, cz, rx, ry, rz;
	int nVals, nTxt, nTime;
	bool bValid;
	anyResult arx, ary, arz;

	if(!data || !xref || !xref[0] || !yref || !yref[0] || !zref || !zref[0]) return false;
	if(!(rX = new AccRange(xref)) || !(rY = new AccRange(yref)) || !(rZ = new AccRange(zref))) return false;
	if(val) free(val);		val = 0L;		nval = 0;
	if(3 < (n = rX->CountItems())) val = (fPOINT3D*) malloc((n+1)*sizeof(fPOINT3D));
	Bounds.Xmin = Bounds.Ymin = HUGE_VAL;	Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
	xBounds.fx = yBounds.fx = zBounds.fx = HUGE_VAL;
	xBounds.fy = yBounds.fy = zBounds.fy = -HUGE_VAL;
	if(rX->DataTypes(data, &nVals, &nTxt, &nTime)){
		if(!nVals && nTime > 1 && nTime > nTxt) x_dtype = ET_DATETIME;
		else x_dtype = 0;
		}
	if(val && rX->GetFirst(&cx, &rx) && rY->GetFirst(&cy, &ry) && rZ->GetFirst(&cz, &rz)) {
		i = 0;
		while(rX->GetNext(&cx, &rx) && rY->GetNext(&cy, &ry) && rZ->GetNext(&cz, &rz)) {
			if(data->GetResult(&arx, rx, cx) && data->GetResult(&ary, ry, cy) && data->GetResult(&arz, rz, cz) 
				&& ary.type == ET_VALUE && arz.type == ET_VALUE) {
				bValid = false;
				if(x_dtype == ET_DATETIME && (arx.type == ET_DATE || arx.type == ET_TIME || arx.type == ET_DATETIME))
					bValid = true;
				else if(!x_dtype && arx.type == ET_VALUE) bValid = true;
				if(bValid) {
					val[i].fx = arx.value;			val[i].fy = ary.value;
					val[i].fz = arz.value;			i++;
					if(arx.value < Bounds.Xmin) Bounds.Xmin = arx.value;
					if(arx.value > Bounds.Xmax) Bounds.Xmax = arx.value;
					if(ary.value < Bounds.Ymin) Bounds.Ymin = ary.value;
					if(ary.value > Bounds.Ymax) Bounds.Ymax = ary.value;
					if(arz.value < zBounds.fx) zBounds.fx = arz.value;
					if(arz.value > zBounds.fy) zBounds.fy = arz.value;
					}
				}
			}
		xBounds.fx = Bounds.Xmin;		xBounds.fy = Bounds.Xmax;
		yBounds.fx = Bounds.Ymin;		yBounds.fy = Bounds.Ymax;
		nval = i;
		}
	delete	rX;		delete rY;		delete rZ;
	return (nval >3);
}

bool
ContourPlot::DoTriangulate()
{
	int i;
	double srz, zsum;
	Triangle *trl, *trn, *trc;
	Triangulate *tria;

	//check minima and maxima
	if(!val || nval < 4) return false;
	if((flags & 0x03) == 0x02 || Bounds.Xmax <= Bounds.Xmin || Bounds.Ymax <= Bounds.Ymin || zBounds.fy <= zBounds.fx) {
		Bounds.Xmin = Bounds.Ymin = HUGE_VAL;	Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
		xBounds.fx = yBounds.fx = zBounds.fx = HUGE_VAL;
		xBounds.fy = yBounds.fy = zBounds.fy = -HUGE_VAL;
		for(i = 0, zsum = 0.0; i < nval; i++) {
			if(val[i].fx < Bounds.Xmin) Bounds.Xmin = val[i].fx;
			if(val[i].fx > Bounds.Xmax) Bounds.Xmax = val[i].fx;
			if(val[i].fy < Bounds.Ymin) Bounds.Ymin = val[i].fy;
			if(val[i].fy > Bounds.Ymax) Bounds.Ymax = val[i].fy;
			if(val[i].fz < zBounds.fx) zBounds.fx = val[i].fz;
			if(val[i].fz > zBounds.fy) zBounds.fy = val[i].fz;
			zsum += val[i].fz;
			}
		xBounds.fx = Bounds.Xmin;		xBounds.fy = Bounds.Xmax;
		yBounds.fx = Bounds.Ymin;		yBounds.fy = Bounds.Ymax;
		}
	if(Bounds.Xmax <= Bounds.Xmin || Bounds.Ymax <= Bounds.Ymin || zBounds.fy <= zBounds.fx) return false;
	//setup two super triangles
	switch (flags & 0x03) {
		case 0:		default:
			srz = zBounds.fx;				break;
		case 1:
			srz = zBounds.fy;				break;
		case 2:
			srz = (zsum/((double)nval));	break;
		case 3:
			srz = sr_zval;					break;
			}
	if(!(trl = new Triangle()) || !(trn = new Triangle())) return false;
	trl->pt[0].fz = trl->pt[1].fz = trl->pt[2].fz = srz;
	trn->pt[0].fz = trn->pt[1].fz = trn->pt[2].fz = srz;
	trl->pt[0].fx = trn->pt[0].fx = trl->pt[2].fx = Bounds.Xmin-(Bounds.Xmax-Bounds.Xmin)*1.0E-8;
	trl->pt[0].fy = trn->pt[0].fy = trn->pt[1].fy = Bounds.Ymin-(Bounds.Ymax-Bounds.Ymin)*1.0E-8;
	trl->pt[1].fx = trn->pt[2].fx = trn->pt[1].fx = Bounds.Xmax+(Bounds.Xmax-Bounds.Xmin)*1.0E-8;
	trl->pt[1].fy = trn->pt[2].fy = trl->pt[2].fy = Bounds.Ymax+(Bounds.Ymax-Bounds.Ymin)*1.0E-8;
	trl->SetRect();			trn->SetRect();
	trl->next = trn;		trn->next = 0L;
	//do triangulation
	if(!(tria = new Triangulate(trl))) {
		delete tria;		delete trl;
		return false;
		}
	for(i = 0; i < nval; i++) {
		tria->AddVertex(&val[i]);
		}
	trl = tria->trl;		delete tria;		tria = 0L;
	//cut surface: create isopleths
	if(!zAxis) DoAxis(0L);
	if(zAxis && !zAxis->NumTicks) zAxis->CreateTicks();
	if(zAxis) for(i = 0; i < zAxis->NumTicks; i++) {
		trc = trl;
		while(trc) {
			trn = trc->next;
			if(zAxis->Ticks[i]) trc->IsoLine(zAxis->Ticks[i]->GetSize(SIZE_MINE), zAxis->Ticks[i]);
			trc = trn;	
			}
		if(zAxis->Ticks[i]) zAxis->Ticks[i]->ProcSeg();
		}
	//create symbols
	if(flags & 0x30) DoSymbols(trl);
	//free triangle list
	trc = trl;
	while(trc) {
		trn = trc->next; 		delete trc;			trc = trn;	
		}
	return false;
}

bool
ContourPlot::DoAxis(anyOutput *o)
{
	TextDEF tlbdef;

	if(!zAxis) {
		z_axis.min = zBounds.fx;		z_axis.max = zBounds.fy;
		NiceAxis(&z_axis, 5);
		z_axis.flags = AXIS_AUTOSCALE | AXIS_POSTICKS;
		z_axis.loc[0].fx = z_axis.loc[1].fx = defs.GetSize(SIZE_DRECT_LEFT) + defs.GetSize(SIZE_TEXT);
		z_axis.loc[0].fy = defs.GetSize(SIZE_DRECT_TOP) + defs.GetSize(SIZE_TEXT);
		z_axis.loc[1].fy = defs.GetSize(SIZE_DRECT_TOP) + defs.GetSize(SIZE_TEXT)*10.0;
		if(!(zAxis = new Axis(this, data, &z_axis, AXIS_AUTOTICK | AXIS_AUTOSCALE | AXIS_POSTICKS)))return false;
		zAxis->SetSize(SIZE_LB_XDIST, -NiceValue(DefSize(SIZE_AXIS_TICKS)*3.0)); 
		zAxis->SetSize(SIZE_TLB_XDIST, NiceValue(DefSize(SIZE_AXIS_TICKS)*2.0)); 
		tlbdef.ColTxt = defs.Color(COL_AXIS);				tlbdef.ColBg = 0x00ffffffL;
		tlbdef.RotBL = tlbdef.RotCHAR = 0.0;				tlbdef.iSize = 0;
		tlbdef.fSize = DefSize(SIZE_TICK_LABELS);			tlbdef.Align = TXA_VCENTER | TXA_HLEFT;
		tlbdef.Style = TXS_NORMAL;					tlbdef.Mode = TXM_TRANSPARENT;
		tlbdef.Font = FONT_HELVETICA;					tlbdef.text = 0L;
		zAxis->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);		zAxis->type = 4;
		zAxis->CreateTicks();						zAxis->moveable = 1;
		}
	else if(z_axis.flags & AXIS_AUTOSCALE) {
		z_axis.min = zBounds.fx;		z_axis.max = zBounds.fy;
		NiceAxis(&z_axis, 4);
		if(zAxis) {
			zAxis->Command(CMD_AUTOSCALE, &z_axis, o);
			zAxis->Command(CMD_RECALC, 0L, o);
			}
		}
	return true;
}

void
ContourPlot::DoSymbols(Triangle *trl)
{
	int i;
	Triangle *trc;
	fPOINT3D *vx;
	bool isValid;
	char lb_buff[20];
	TextDEF td = {0x0L, 0x00ffffffL, defs.GetSize(SIZE_SYMBOL), 0.0, 0.0, 0, TXA_HLEFT | TXA_VCENTER, TXM_TRANSPARENT,
		TXS_NORMAL, FONT_HELVETICA, lb_buff+1};

	if(!val || nval < 4 || !trl || Symbols || Labels || !(flags & 0x30)) return;
	if(!(vx = (fPOINT3D*)malloc(nval * sizeof(fPOINT3D)))) return;
	switch(flags & 0x30) {
	case 0x10:			// at minima
		for(i = 0; i < nval; i++) {
			trc = trl;	isValid = true;
			do {
				if((trc->pt[trc->order[1]].fx == val[i].fx && trc->pt[trc->order[1]].fy == val[i].fy
					&& trc->pt[trc->order[1]].fz == val[i].fz) || (trc->pt[trc->order[2]].fx == val[i].fx 
					&& trc->pt[trc->order[2]].fy == val[i].fy && trc->pt[trc->order[2]].fz == val[i].fz))
					isValid = false;
				trc = trc->next;
				} while(trc && isValid);
			if(isValid) {
				vx[nSym].fx = val[i].fx;		vx[nSym].fy = val[i].fy;
				vx[nSym].fz = val[i].fz;		nSym++;
				}
			}
		break;
	case 0x20:			// at maxima
		for(i = 0; i < nval; i++) {
			trc = trl;	isValid = true;
			do {
				if((trc->pt[trc->order[0]].fx == val[i].fx && trc->pt[trc->order[0]].fy == val[i].fy
					&& trc->pt[trc->order[0]].fz == val[i].fz) || (trc->pt[trc->order[1]].fx == val[i].fx 
					&& trc->pt[trc->order[1]].fy == val[i].fy && trc->pt[trc->order[1]].fz == val[i].fz))
					isValid = false;
				trc = trc->next;
				} while(trc && isValid);
			if(isValid) {
				vx[nSym].fx = val[i].fx;		vx[nSym].fy = val[i].fy;
				vx[nSym].fz = val[i].fz;		nSym++;
				}
			}
		break;
	case 0x30:			// all values
		for(nSym = 0; nSym < nval; nSym++) {
			vx[nSym].fx = val[nSym].fx;		vx[nSym].fy = val[nSym].fy;
			vx[nSym].fz = val[nSym].fz;
			}
		break;
	}
	// create symbols
	if(nSym && (flags & 0x40) && (Symbols = (Symbol**)malloc(nSym * sizeof(Symbol*)))) {
		for(i =0; i < nSym; i++) {
			Symbols[i] = new Symbol(this, data, vx[i].fx, vx[i].fy, 0);
			}
		}
	// add labels to symbols?
	if(nSym && (flags & 0x40) && (Labels = (Label**)malloc(nSym * sizeof(Label*)))) {
		for(nLab = 0; nLab < nSym; nLab++) {
			WriteNatFloatToBuff(lb_buff, vx[nLab].fz);
			if(Labels[nLab] = new Label(this, data, vx[nLab].fx, vx[nLab].fy, &td, LB_X_DATA | LB_Y_DATA)){
				Labels[nLab]->SetSize(SIZE_LB_XDIST, defs.GetSize(SIZE_SYMBOL));
				}
			}
		}
	free(vx);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// three dimensional graph
Plot3D::Plot3D(GraphObj *par, DataObj *d, DWORD flags):Plot(par, d)
{
	RotDef = (double*)malloc(6 *sizeof(double));
	FileIO(INIT_VARS);
	Id = GO_PLOT3D;
	crea_flags = flags;
	xBounds.fx = yBounds.fx = zBounds.fx = Bounds.Xmin = Bounds.Ymin = HUGE_VAL;
	xBounds.fy = yBounds.fy = zBounds.fy = Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
}

Plot3D::Plot3D(int src):Plot(0L, 0L)
{
	int i;

	RotDef = (double*)malloc(6 *sizeof(double));
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		//now set parent in all children
		if(Axes) for(i = 0; i < nAxes; i++)
			if(Axes[i]) Axes[i]->parent = this;
		if(plots) for(i = 0; i < nPlots; i++)
			if(plots[i]) plots[i]->parent = this;
		}
}

Plot3D::~Plot3D()
{
	int i;

	if(plots) {
		for(i = 0; i < nPlots; i++) if(plots[i]) DeleteGO(plots[i]);
		free(plots);
		}
	if(Axes) {
		for(i = 0; i < nAxes; i++) if(Axes[i]) DeleteGO(Axes[i]);
		free(Axes);
		}
	plots = 0L;		nPlots = nAxes = 0;
	if(nscp > 0 && nscp <= nPlots && Sc_Plots) free(Sc_Plots);
	nscp = 0;			Sc_Plots = 0L;
	if(drag) DeleteGO(drag);	drag = 0L;
	if(dispObs) free(dispObs);	dispObs = 0L;
	free(RotDef);
	if(name) free(name);		name=0L;
	Undo.InvalidGO(this);
}

double
Plot3D::GetSize(int select)
{
	AxisDEF *ax;

	switch(select){
	//The Bounds values must be returned by every plot:
	//   they are necessary for scaling !
	case SIZE_BOUNDS_XMIN:
		if(Axes && nAxes >2 && Axes[0] && (ax = Axes[0]->GetAxis()))
			return (ax->flags & AXIS_INVERT) ? ax->max : ax->min;
		return 0.0;
	case SIZE_BOUNDS_XMAX:
		if(Axes && nAxes >2 && Axes[0] && (ax = Axes[0]->GetAxis()))
			return (ax->flags & AXIS_INVERT) ? ax->min : ax->max;
		return 0.0;
	case SIZE_BOUNDS_YMIN:
		if(Axes && nAxes >2 && Axes[1] && (ax = Axes[1]->GetAxis())) 
			return (ax->flags & AXIS_INVERT) ? ax->max : ax->min;
		return 0.0;
	case SIZE_BOUNDS_YMAX:
		if(Axes && nAxes >2 && Axes[1] && (ax = Axes[1]->GetAxis())) 
			return (ax->flags & AXIS_INVERT) ? ax->min : ax->max;
		return 0.0;
	case SIZE_BOUNDS_ZMIN:
		if(Axes && nAxes >2 && Axes[2] && (ax = Axes[2]->GetAxis())) 
			return (ax->flags & AXIS_INVERT) ? ax->max : ax->min;
		return 0.0;
	case SIZE_BOUNDS_ZMAX:
		if(Axes && nAxes >2 && Axes[2] && (ax = Axes[2]->GetAxis())) 
			return (ax->flags & AXIS_INVERT) ? ax->min : ax->max;
		return 0.0;
	case SIZE_XPOS:		case SIZE_XPOS+4:		return cu1.fx;
	case SIZE_XPOS+1:	case SIZE_XPOS+5:		return cu2.fx;
	case SIZE_XPOS+2:	case SIZE_XPOS+6:		return cu2.fx;
	case SIZE_XPOS+3:	case SIZE_XPOS+7:		return cu1.fx;
	case SIZE_YPOS:		case SIZE_YPOS+1:		case SIZE_YPOS+2:
	case SIZE_YPOS+3:			return cu1.fy;
	case SIZE_YPOS+4:	case SIZE_YPOS+5:		case SIZE_YPOS+6:
	case SIZE_YPOS+7:			return cu2.fy;
	case SIZE_ZPOS:		case SIZE_ZPOS+1:		case SIZE_ZPOS+4:
	case SIZE_ZPOS+5:			return cu1.fz;
	case SIZE_ZPOS+2:	case SIZE_ZPOS+3:		case SIZE_ZPOS+6:
	case SIZE_ZPOS+7:			return cu2.fz;
	case SIZE_XCENTER:	return rotC.fx;
	case SIZE_YCENTER:	return rotC.fy;
	case SIZE_ZCENTER:	return rotC.fz;
	default:
		return DefSize(select);
		}
}

bool
Plot3D::SetColor(int select, DWORD col)
{
	int i;

	switch(select & 0xfff) {
	case COL_AXIS:
		if(Axes) for(i = 0; i< nAxes; i++) if(Axes[i]) Axes[i]->SetColor(select, col);
		return true;
		}
	return false;
}

void
Plot3D::DoPlot(anyOutput *o)
{
	long i, j;

	nObs = 0;
	if(!parent || !o) return;
	if(nscp > 0 && nscp <= nPlots && Sc_Plots) free(Sc_Plots);
	nscp = 0;			Sc_Plots = 0L;		o->MouseCursor(MC_WAIT, true);
	if(dirty) DoAutoscale();
	if(Axes && nAxes >2) {		//if no axes then parent is another Plot3D ...
		o->LightSource(32.0, 16.0);						CurrAxes = Axes;
		cu1.fx = cub1.fx;		cu1.fy = cub1.fy;		cu1.fz = cub1.fz;
		cu2.fx = cub2.fx;		cu2.fy = cub2.fy;		cu2.fz = cub2.fz;
		rc.fx = rotC.fx;		rc.fy = rotC.fy;		rc.fz = rotC.fz;
		o->SetSpace(&cu1, &cu2, defs.cUnits, RotDef, &rc, Axes[0]->GetAxis(),
			Axes[1]->GetAxis(), Axes[2]->GetAxis());
		if(nAxes >3) {									//DEBUG
			nAxes = nAxes;
			}
		for(i = 0; i< nAxes; i++) if(Axes[i]) Axes[i]->DoPlot(o);
		}
	else if(IsPlot3D(parent)) {
		if (use_xaxis || use_yaxis || use_zaxis)ApplyAxes(o);
		parent->Command(CMD_REG_AXISPLOT, (void*)this, o);
		}
	else CurrAxes = 0L;
	if(plots) for(i = 0; i < nPlots; i++) if(plots[i]){
		if(plots[i]->Id >= GO_PLOT && plots[i]->Id < GO_GRAPH) {
			if(((Plot*)plots[i])->hidden == 0) plots[i]->DoPlot(o);
			}
		else plots[i]->DoPlot(o);
		if(i) {
			UpdateMinMaxRect(&rDims, plots[i]->rDims.right, plots[i]->rDims.top);
			UpdateMinMaxRect(&rDims, plots[i]->rDims.left, plots[i]->rDims.bottom);
			}
		else memcpy(&rDims, &plots[i]->rDims, sizeof(RECT));
			}
	for(i = 0; i< nAxes; i++) if(Axes[i]){
		UpdateMinMaxRect(&rDims, Axes[i]->rDims.right, Axes[i]->rDims.top);
		UpdateMinMaxRect(&rDims, Axes[i]->rDims.left, Axes[i]->rDims.bottom);
		}
	for(i = j = 1; i < nObs; i++) if(dispObs[i] && dispObs[i]->go) 
		dispObs[j++] = dispObs[i];
	nObs = j;
	if(nObs >1  && dispObs){
		SortObj();
		for (i = 1; i <= nObs; i++){
			for(j = 1; j <= nObs; j++) {
				if(dispObs[j]->go->Id != GO_LINESEG && i != j) switch(dispObs[i]->go->Id) {
				case GO_LINESEG:
				case GO_SPHERE:
				case GO_PLANE:
					dispObs[i]->go->Command(CMD_CLIP, dispObs[j]->go, o);
					break;
					}
				//the following line, if included, reduces time but clipping 
				//   is not complete because of CMD_DRAW_LATER
//				if(dispObs[j]->Zmin > dispObs[i]->Zmax) break;
				}
			}
		for (i = 1; i <= nObs; i++)	dispObs[i]->go->Command(CMD_REDRAW, 0L, o);
		}
	if(IsPlot3D(parent) && (use_xaxis || use_yaxis || use_zaxis))parent->Command(CMD_AXIS, 0L, o);
	dirty = false;
	o->MouseCursor(MC_ARROW, true);
}

void
Plot3D::DoMark(anyOutput *o, bool mark)
{
	RECT upd;

	if(!drag) drag = new Drag3D(this);
	if(mark && drag) drag->DoPlot(o);
	else {
		memcpy(&upd, &rDims, sizeof(RECT));
		IncrementMinMaxRect(&upd, 6);
		o->UpdateRect(&upd, false);
		}
}

bool
Plot3D::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i;
	GraphObj **tmpPlots;

	switch (cmd) {
	case CMD_SCALE:
		cub1.fx *= ((scaleINFO*)tmpl)->sx.fy;		cub1.fy *= ((scaleINFO*)tmpl)->sy.fy;
		cub1.fz *= ((scaleINFO*)tmpl)->sz.fy;		cub2.fx *= ((scaleINFO*)tmpl)->sx.fy;
		cub2.fy *= ((scaleINFO*)tmpl)->sy.fy;		cub2.fz *= ((scaleINFO*)tmpl)->sz.fy;
		rotC.fx *= ((scaleINFO*)tmpl)->sx.fy;		rotC.fy *= ((scaleINFO*)tmpl)->sy.fy;
		rotC.fz *= ((scaleINFO*)tmpl)->sz.fy;
		if(plots) for(i = 0; i < nPlots; i++)
			if(plots[i]) plots[i]->Command(cmd, tmpl, o);
		if(Axes) for(i = 0; i < nAxes; i++)
			if(Axes[i]) Axes[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_MOUSE_EVENT:
		if(hidden || ((MouseEvent*)tmpl)->Action != MOUSE_LBUP || CurrGO) return false;
		if(dispObs) for (i = nObs; i > 0; i--)	{
			if(dispObs[i]) dispObs[i]->go->Command(cmd, tmpl, o);
			if(CurrGO) return true;
			}
		if(IsInRect(&rDims, ((MouseEvent*)tmpl)->x, ((MouseEvent*)tmpl)->y)) {
			o->ShowMark(CurrGO = this, MRK_GODRAW);
			return true;
			}
		break;
	case CMD_USEAXIS:
		if(IsPlot3D(parent)) return UseAxis(*((int*)tmpl));
		break;
	case CMD_REG_AXISPLOT:	//notification: plot can handle its own axes
		if(nscp > 0 && nscp <= nPlots && Sc_Plots)  {
			for(i = 0; i < nscp; i++)
				if(Sc_Plots[i] == (GraphObj*)tmpl) return true;
			if(tmpPlots = (GraphObj**)realloc(Sc_Plots, (nscp+1)*sizeof(GraphObj*))){
				tmpPlots[nscp++] = (GraphObj *)tmpl;
				Sc_Plots = tmpPlots;
				}
			else {		//memory allocation error
				nscp = 0;
				Sc_Plots = 0L;
				}
			}
		else {
			if(Sc_Plots = (GraphObj **)calloc(1, sizeof(GraphObj*))){
				Sc_Plots[0] = (GraphObj *)tmpl;
				nscp = 1;
				}
			else nscp = 0;
			}
		return true;
	case CMD_AXIS:			//one of the plots has changed scaling: reset
		CurrAxes = Axes;
		if(o) o->SetSpace(&cu1, &cu2, defs.cUnits, RotDef, &rc, Axes[0]->GetAxis(),
			Axes[1]->GetAxis(), Axes[2]->GetAxis());
		return true;
	case CMD_OBJTREE:
		if(!tmpl || !plots) return false;
		for(i = 0; i < nPlots; i++) if(plots[i]) {
			((ObjTree*)tmpl)->Command(CMD_UPDATE, plots[i], 0L);
			if(plots[i]->Id > GO_PLOT && plots[i]->Id < GO_GRAPH) plots[i]->Command(cmd, tmpl, o);
			}
		return true;
	case CMD_REPL_GO:
		if(!(tmpPlots = (GraphObj **)tmpl) || !tmpPlots[0] || !tmpPlots[1]) return false;
		if(plots) for(i = 0; i < nPlots; i++) if(plots[i] && plots[i] == tmpPlots[0]){
			return dirty = ReplaceGO((GraphObj**)&plots[i], tmpPlots);
			}
		return false;
	case CMD_MRK_DIRTY:
		if(IsPlot3D(parent)) return parent->Command(cmd, tmpl, o);
		return dirty = true;
	case CMD_ADDAXIS:
		if(AddAxis()){
			if(parent) return parent->Command(CMD_REDRAW, tmpl, o);
			}
		return false;
	case CMD_SET_GO3D:
		if(IsPlot3D(parent)) return parent->Command(CMD_REDRAW, 0L, o);
		return AcceptObj((GraphObj *)tmpl);
	case CMD_SETSCROLL:				case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_SHIFTLEFT:
		return Rotate(-0.017452406, 0.0, 0.0, o, true);
	case CMD_SHIFTRIGHT:
		return Rotate(0.017452406, 0.0, 0.0, o, true);
	case CMD_SHIFTUP:
		return Rotate(0.0, 0.017452406, 0.0, o, true);
	case CMD_SHIFTDOWN:
		return Rotate(0.0, -0.017452406, 0.0, o, true);
	case CMD_CURRIGHT:
		return Rotate(0.087155742, 0.0, 0.0, o, true);
	case CMD_CURRLEFT:
		return Rotate(-0.087155742, 0.0, 0.0, o, true);
	case CMD_CURRUP:
		return Rotate(0.0, 0.087155742, 0.0, o, true);
	case CMD_CURRDOWN:
		return Rotate(0.0, -0.087155742, 0.0, o, true);
	case CMD_ADDCHAR:
		if(tmpl && *((int*)tmpl) == 'r') return Rotate(0.0, 0.0, 0.087155742, o, true);
		if(tmpl && *((int*)tmpl) == 'l') return Rotate(0.0, 0.0, -0.087155742, o, true);
		if(tmpl && *((int*)tmpl) == 'R') return Rotate(0.0, 0.0, 0.017452406, o, true);
		if(tmpl && *((int*)tmpl) == 'L') return Rotate(0.0, 0.0, -0.017452406, o, true);
		return false;
	case CMD_LEGEND:
		if(plots) for(i = 0; i < nPlots; i++)
			if(plots[i]) plots[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_SET_DATAOBJ:
		Id = GO_PLOT3D;
		data = (DataObj *)tmpl;
	case CMD_UPDATE:
		if(plots) for(i = 0; i < nPlots; i++)
			if(plots[i]) plots[i]->Command(cmd, tmpl, o);
		if(Axes) for(i = 0; i < nAxes; i++)
			if(Axes[i]) Axes[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_DELOBJ:
		if(DeleteGOL((GraphObj***)&plots,nPlots,(GraphObj*)tmpl,o)) return parent->Command(CMD_REDRAW,0L,o);
		return false;
	case CMD_MOVE:
		if(CurrGO && CurrGO->Id == GO_DRAGHANDLE) {
			CalcRotation(((lfPOINT*)tmpl)[0].fx, ((lfPOINT*)tmpl)[0].fy, o, true);
			if(parent) return parent->Command(CMD_REDRAW, 0L, 0L);
			}
		return true;
	case CMD_AUTOSCALE:
		if(dirty) {
			DoAutoscale();
			dirty = false;
			}
		return true;
	case CMD_DROP_PLOT:
		if(!parent || !tmpl || ((GraphObj*)tmpl)->Id < GO_PLOT) return false;
		if(IsPlot3D(parent)) return parent->Command(cmd, tmpl, o);
		if(!nPlots) {
			plots = (GraphObj**)calloc(2, sizeof(GraphObj*));
			if(plots) {
				nPlots = 1;					plots[0] = (Plot *)tmpl;
				plots[0]->parent = this;	CreateAxes();
				return dirty = parent->Command(CMD_REDRAW, 0L, 0L);
				}
			}
		else {
			((Plot *)tmpl)->parent = this;
			tmpPlots = (GraphObj**)memdup(plots, sizeof(GraphObj*) * (nPlots+2), 0);
			Undo.ListGOmoved(plots, tmpPlots, nPlots);
			Undo.SetGO(this, &tmpPlots[nPlots++], (Plot *)tmpl, 0L);
			free(plots);			plots = tmpPlots;
			return dirty = parent->Command(CMD_REDRAW, 0L, 0L);
			}
		return false;
	case CMD_ADDPLOT:
		return AddPlot(0x0);
		}
	return false;
}

void *
Plot3D::ObjThere(int x, int y)
{
	if(drag) return drag->ObjThere(x, y);
	return 0L;
}

void
Plot3D::Track(POINT *p, anyOutput *o)
{
	fPOINT3D v, iv;
	POINT pts[5];
	RECT upd_rc;

	CalcRotation(((lfPOINT*)p)->fx, ((lfPOINT*)p)->fy, o, false);
	memcpy(&upd_rc, &rDims, sizeof(RECT));
	IncrementMinMaxRect(&rDims, 3);
	o->UpdateRect(&upd_rc, false);
	memcpy(&v, &cu2, sizeof(fPOINT3D));
	o->cvec2ivec(&v, &iv);
	pts[0].x = iround(iv.fx);		pts[0].y = iround(iv.fy);
	UpdateMinMaxRect(&rDims, pts[0].x, pts[0].y);
	v.fx = cu1.fx;					o->cvec2ivec(&v, &iv); 
	pts[1].x = iround(iv.fx);		pts[1].y = iround(iv.fy);
	UpdateMinMaxRect(&rDims, pts[1].x, pts[1].y);
	v.fy = cu1.fy;					o->cvec2ivec(&v, &iv); 
	pts[2].x = iround(iv.fx);		pts[2].y = iround(iv.fy);
	UpdateMinMaxRect(&rDims, pts[2].x, pts[2].y);
	v.fz = cu1.fz;					o->cvec2ivec(&v, &iv); 
	pts[3].x = iround(iv.fx);		pts[3].y = iround(iv.fy);
	UpdateMinMaxRect(&rDims, pts[3].x, pts[3].y);
	v.fy = cu2.fy;					o->cvec2ivec(&v, &iv); 
	pts[4].x = iround(iv.fx);		pts[4].y = iround(iv.fy);
	UpdateMinMaxRect(&rDims, pts[4].x, pts[4].y);
	o->ShowLine(pts, 5, 0x000000ff);
	v.fz = cu2.fz;					o->cvec2ivec(&v, &iv);
	pts[0].x = iround(iv.fx);		pts[0].y = iround(iv.fy);
	v.fz = cu1.fz;					o->cvec2ivec(&v, &iv); 
	pts[1].x = iround(iv.fx);		pts[1].y = iround(iv.fy);
	v.fx = cu2.fx;					o->cvec2ivec(&v, &iv);
	pts[2].x = iround(iv.fx);		pts[2].y = iround(iv.fy);
	v.fz = cu2.fz;					o->cvec2ivec(&v, &iv);
	pts[3].x = iround(iv.fx);		pts[3].y = iround(iv.fy);
	v.fy = cu1.fy;					o->cvec2ivec(&v, &iv);
	pts[4].x = iround(iv.fx);		pts[4].y = iround(iv.fy);
	o->ShowLine(pts, 5, 0x000000ff);
	v.fy = cu2.fy;	v.fz = cu1.fz;		o->cvec2ivec(&v, &iv);
	pts[0].x = iround(iv.fx);		pts[0].y = iround(iv.fy);
	v.fy = cu1.fy;					o->cvec2ivec(&v, &iv);
	pts[1].x = iround(iv.fx);		pts[1].y = iround(iv.fy);
	v.fx = cu1.fx;					o->cvec2ivec(&v, &iv);
	pts[2].x = iround(iv.fx);		pts[2].y = iround(iv.fy);
	o->ShowLine(pts, 3, 0x000000ff);
	v.fz = cu2.fz;					o->cvec2ivec(&v, &iv);
	pts[0].x = iround(iv.fx);		pts[0].y = iround(iv.fy);
	v.fx = cu2.fx;					o->cvec2ivec(&v, &iv);
	pts[1].x = iround(iv.fx);		pts[1].y = iround(iv.fy);
	v.fz = cu1.fz;					o->cvec2ivec(&v, &iv);
	pts[2].x = iround(iv.fx);		pts[2].y = iround(iv.fy);
	o->ShowLine(pts, 3, 0x000000ff);
}

void
Plot3D::CreateAxes()
{
	typedef struct {
		double x1, y1, z1, x2, y2, z2;
		DWORD flags;
		int a_type, t_type;
		double lb_x, lb_y, tlb_x, tlb_y;
		int txa;
		}Axis3Ddef;
	AxisDEF tmp_axis;
	double ts = DefSize(SIZE_AXIS_TICKS);
	int i;
	if(Axes || !parent)return;
	TextDEF tlbdef = {parent->GetColor(COL_AXIS), 0x00ffffffL, DefSize(SIZE_TICK_LABELS),
		0.0, 0.0, 0, TXA_HLEFT | TXA_VCENTER, TXM_TRANSPARENT, TXS_NORMAL, FONT_HELVETICA, 0L};
	Axis3Ddef *at = 0L;
	Axis3Ddef at1[] = {
		{cub1.fx, cub1.fy, 0.0, cub2.fx, cub1.fy, 0.0,
			AXIS_3D | AXIS_DEFRECT | AXIS_AUTOTICK | AXIS_AUTOSCALE | AXIS_NEGTICKS, 1, 3,
			0.0, NiceValue((ts+DefSize(SIZE_AXIS_TICKS))*2.0), 0.0,
			NiceValue(ts * 2.0), TXA_HCENTER | TXA_VTOP},
		{cub1.fx, cub1.fy, 0.0, cub1.fx, cub2.fy, 0.0,
			AXIS_3D | AXIS_DEFRECT | AXIS_AUTOTICK | AXIS_AUTOSCALE | AXIS_NEGTICKS, 2, 2,
			-NiceValue((ts+DefSize(SIZE_AXIS_TICKS))*3.0), 0.0,
			-NiceValue(ts * 2.0), 0.0, TXA_HRIGHT | TXA_VCENTER},
		{cub1.fx, cub1.fy, 0.0, cub1.fx, cub1.fy, cub2.fz,
			AXIS_3D | AXIS_DEFRECT | AXIS_AUTOTICK | AXIS_AUTOSCALE | AXIS_NEGTICKS, 3, 2,
			-NiceValue((ts+DefSize(SIZE_AXIS_TICKS))*3.0), 0.0,
			-NiceValue(ts * 2.0), 0.0, TXA_HRIGHT | TXA_VCENTER}};
	Axis3Ddef at2[] = {
		{at1[0].x1, at1[0].y1, at1[2].z2, at1[0].x2, at1[0].y2, at1[2].z2,
			AXIS_3D | AXIS_DEFRECT | AXIS_AUTOTICK | AXIS_AUTOSCALE | AXIS_NEGTICKS, 1, 3,
			0.0, at1[0].lb_y, 0.0,	at1[0].tlb_y, TXA_HCENTER | TXA_VTOP},
		{at1[0].x2, at1[1].y1, 0.0, at1[0].x2, at1[1].y2, 0.0,
			AXIS_3D | AXIS_DEFRECT | AXIS_AUTOTICK | AXIS_AUTOSCALE | AXIS_POSTICKS, 2, 2,
			-at1[1].lb_x, 0.0, -at1[1].tlb_x, 0.0, TXA_HLEFT | TXA_VCENTER},
		{at1[0].x2, at1[0].y1, 0.0, at1[0].x2, at1[0].y1, at1[2].z2,
			AXIS_3D | AXIS_DEFRECT | AXIS_AUTOTICK | AXIS_AUTOSCALE | AXIS_POSTICKS, 3, 2,
			-at1[2].lb_x, 0.0, -at1[2].tlb_x, 0.0, TXA_HLEFT | TXA_VCENTER},
		{at1[0].x1, at1[0].y1, 0.0, at1[0].x2, at1[0].y2, 0.0,
			AXIS_3D, 1, 3, 0.0, at1[0].lb_y, 0.0, at1[0].tlb_y, TXA_HCENTER | TXA_VCENTER},
		{at1[0].x1, at1[1].y2, 0.0, at1[0].x2, at1[1].y2, 0.0,
			AXIS_3D, 1, 3, 0.0, at1[0].lb_y, 0.0, at1[0].tlb_y, TXA_HCENTER | TXA_VCENTER},
		{at1[0].x1, at1[1].y1, 0.0, at1[0].x1, at1[1].y2, 0.0,
			AXIS_3D, 2, 2, at1[1].lb_x, 0.0, at1[1].tlb_x, 0.0, TXA_HCENTER | TXA_VCENTER},
		{at1[0].x1, at1[1].y1, at1[2].z2, at1[0].x1, at1[1].y2, at1[2].z2,
			AXIS_3D, 2, 2, at1[1].lb_x, 0.0, at1[1].tlb_x, 0.0, TXA_HCENTER | TXA_VCENTER},
		{at1[0].x1, at1[0].y1, 0.0, at1[0].x1, at1[0].y1, at1[2].z2,
			AXIS_3D, 3, 2, at1[2].lb_x, 0.0, at1[2].tlb_x, 0.0, TXA_HCENTER | TXA_VCENTER},
		{at1[0].x1, at1[1].y2, 0.0, at1[0].x1, at1[1].y2, at1[2].z2,
			AXIS_3D, 3, 2, at1[2].lb_x, 0.0, at1[2].tlb_x, 0.0}, TXA_HCENTER | TXA_VCENTER};
	Axis3Ddef at3[] = {
		{at1[0].x1, (at1[1].y1+at1[1].y2)/2.0, at1[2].z2/2.0, at1[0].x2, 
			(at1[1].y1+at1[1].y2)/2.0, at1[2].z2/2.0,
			AXIS_3D | AXIS_AUTOTICK | AXIS_AUTOSCALE | AXIS_NEGTICKS, 1, 3,
			0.0, at1[0].lb_y, 0.0,	at1[0].tlb_y, TXA_HCENTER | TXA_VTOP},
		{(at1[0].x1 + at1[0].x2)/2.0, at1[1].y1, at1[2].z2/2.0, 
			(at1[0].x1 + at1[0].x2)/2.0, at1[1].y2, at1[2].z2/2.0,
			AXIS_3D | AXIS_AUTOTICK | AXIS_AUTOSCALE | AXIS_POSTICKS, 2, 2,
			-at1[1].lb_x, 0.0, -at1[1].tlb_x, 0.0, TXA_HLEFT | TXA_VCENTER},
		{(at1[0].x1 + at1[0].x2)/2.0, (at1[1].y1+at1[1].y2)/2.0, 0.0,
			(at1[0].x1 + at1[0].x2)/2.0, (at1[1].y1+at1[1].y2)/2.0, at1[2].z2,
			AXIS_3D | AXIS_AUTOTICK | AXIS_AUTOSCALE | AXIS_POSTICKS, 3, 2,
			-at1[2].lb_x, 0.0, -at1[2].tlb_x, 0.0, TXA_HLEFT | TXA_VCENTER}};

	tmp_axis.min = 0.0;			tmp_axis.max = 100.0;
	tmp_axis.Start = 0.0;		tmp_axis.Step = 20.0;
	tmp_axis.Center.fx = tmp_axis.Center.fy = 0.0;
	tmp_axis.Radius = 0.0;		tmp_axis.nBreaks = 0;
	tmp_axis.breaks = 0L;		tmp_axis.owner = 0L;
	switch(AxisTempl3D){
	case 0:		at = at1;		nAxes = 3;		break;
	case 1:		at = at2;		nAxes = 9;		break;
	case 2:		at = at3;		nAxes = 3;		break;
		}
	if(!(Axes = (Axis**)calloc(nAxes, sizeof(Axis *))))return;
	if(at && nAxes) for(i = 0; i < nAxes; i++) {
		tmp_axis.loc[0].fx = at[i].x1;		tmp_axis.loc[0].fy = at[i].y1;
		tmp_axis.loc[0].fz = at[i].z1;		tmp_axis.loc[1].fx = at[i].x2;
		tmp_axis.loc[1].fy = at[i].y2;		tmp_axis.loc[1].fz = at[i].z2;
		tlbdef.Align = at[i].txa;
		if((Axes[i] = new Axis(this, data, &tmp_axis, at[i].flags))){
			Axes[i]->type = at[i].a_type;
			Axes[i]->SetSize(SIZE_LB_YDIST, at[i].lb_y);
			Axes[i]->SetSize(SIZE_LB_XDIST, at[i].lb_x);
			Axes[i]->SetSize(SIZE_TLB_YDIST, at[i].tlb_y);
			Axes[i]->SetSize(SIZE_TLB_XDIST, at[i].tlb_x);
			Axes[i]->Command(CMD_TICK_TYPE, &at[i].t_type, 0L);
			Axes[i]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		}
}

void
Plot3D::DoAutoscale()
{
	int i;
	AxisDEF *ad;

	if(!plots) return;
	Bounds.Xmin = Bounds.Ymin = HUGE_VAL;	Bounds.Xmax = Bounds.Ymax = -HUGE_VAL;
	xBounds.fx = yBounds.fx = zBounds.fx = HUGE_VAL;
	xBounds.fy = yBounds.fy = zBounds.fy = -HUGE_VAL;
	for(i = 0; i < nPlots; i++) {
		if(plots[i]) {
			if(plots[i]->Id >= GO_PLOT && plots[i]->Id < GO_GRAPH) {
				if(!((Plot*)plots[i])->hidden) plots[i]->Command(CMD_AUTOSCALE, 0L, 0L);
				}
			else plots[i]->Command(CMD_AUTOSCALE, 0L, 0L);
			}
		}
	if(xBounds.fx <= xBounds.fy && yBounds.fx <= yBounds.fy && zBounds.fx <= zBounds.fy){
		if(Axes)for(i = 0; i < 3; i++) if(Axes[i]){
			ad = Axes[i]->axis;
			if(ad->flags & AXIS_AUTOSCALE) {
				switch(i) {
				case 0:
					if(xBounds.fx == xBounds.fy) {
						xBounds.fx -= 1.0;	xBounds.fy += 1.0;
						}
					ad->min = xBounds.fx;	ad->max = xBounds.fy;	break;
				case 1:
					if(yBounds.fx == yBounds.fy) {
						yBounds.fx -= 1.0;	yBounds.fy += 1.0;
						}
					ad->min = yBounds.fx;	ad->max = yBounds.fy;	break;
				case 2:
					if(zBounds.fx == zBounds.fy) {
						zBounds.fx -= 1.0;	zBounds.fy += 1.0;
						}
					ad->min = zBounds.fx;	ad->max = zBounds.fy;	break;
					}
				NiceAxis(ad, 4);
				if(ad->min <= 0.0 && ((ad->flags & 0xf000) == AXIS_LOG ||
					(ad->flags & 0xf000) == AXIS_RECI)) {
					ad->min = base4log(ad, i);
					}
				Axes[i]->Command(CMD_AUTOSCALE, ad, 0L);
				}
			}
		else if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH) {
			((Plot*)parent)->CheckBounds3D(xBounds.fx, yBounds.fx, zBounds.fx);
			((Plot*)parent)->CheckBounds3D(xBounds.fy, yBounds.fy, zBounds.fy);
			}
		}
}

//Implement some kind of virtual trackball
//see: J. Hultquist: A Virtual Trackball
//Graphic Gems, A.S. Glassner ed.; Academic Press Inc.
//ISBN 0-12-286165-5, pp. 462-463
void
Plot3D::CalcRotation(double dx, double dy, anyOutput *o, bool accept)
{
	fPOINT3D V0, V1, A;
	double R, R2, si, dp, NewRot[6];
	double rotM[3][3], newM[3][3];			//rotation matrices

	if(!CurrGO || CurrGO->Id != GO_DRAGHANDLE || CurrGO->type < DH_18 ||
		CurrGO->type > DH_88) return;
	//get coordinates for last accepted rotation
	V0.fx = GetSize(SIZE_XPOS + CurrGO->type - DH_18);
	V0.fy = GetSize(SIZE_YPOS + CurrGO->type - DH_18);
	V0.fz = GetSize(SIZE_ZPOS + CurrGO->type - DH_18);
	//revert to last matrix	
	o->SetSpace(&cu1, &cu2, defs.cUnits, RotDef, &rc, Axes[0]->GetAxis(),
		Axes[1]->GetAxis(), Axes[2]->GetAxis());
	o->cvec2ivec(&V0, &V1);
	memcpy(&V0, &V1, sizeof(fPOINT3D));
	V1.fx += o->un2fix(dx);		V1.fy += o->un2fiy(dy);
	V0.fx -= o->rotC.fx;		V0.fy -= o->rotC.fy;		V0.fz -= o->rotC.fz;
	V1.fx -= o->rotC.fx;		V1.fy -= o->rotC.fy;		V1.fz -= o->rotC.fz;
	R = sqrt(R2 = V0.fx * V0.fx + V0.fy * V0.fy + V0.fz * V0.fz);
	R2 -= (V1.fx * V1.fx + V1.fy * V1.fy);
	if (R2 <= 1.0) return;
	V1.fz = V1.fz > 0.0 ? sqrt(R2) : -sqrt(R2);
	V0.fx /= R;			V0.fy /= R;			V0.fz /= R;
	V1.fx /= R;			V1.fy /= R;			V1.fz /= R;
	A.fx = (V1.fy * V0.fz) - (V1.fz * V0.fy);
	A.fy = (V1.fz * V0.fx) - (V1.fx * V0.fz);
	A.fz = (V1.fx * V0.fy) - (V1.fy * V0.fx);

	si = sqrt(A.fx * A.fx + A.fy * A.fy + A.fz * A.fz);
	if(si > 0.001) {
		NewRot[0] = A.fx;	NewRot[1] = A.fy;	NewRot[2] = A.fz;
		NewRot[3] = si;		NewRot[4] = sqrt(1.0-si*si);	NewRot[5] = 1.0-NewRot[4];
		//normalize vector part of NewRot
		dp = sqrt(NewRot[0]*NewRot[0] + NewRot[1]*NewRot[1] + NewRot[2]*NewRot[2]);
		NewRot[0] /= dp;	NewRot[1] /= dp;	NewRot[2] /= dp;
		//set up rotation matrix from quaternion
		//see: Graphic Gems, A.S. Glassner ed.; Academic Press Inc.
		//M.E. Pique: Rotation Tools
		// ISBN 0-12-286165-5, p.466
		rotM[0][0] = NewRot[5]*NewRot[0]*NewRot[0] + NewRot[4];
		rotM[0][1] = NewRot[5]*NewRot[0]*NewRot[1] + NewRot[3]*NewRot[2];
		rotM[0][2] = NewRot[5]*NewRot[0]*NewRot[2] - NewRot[3]*NewRot[1];
		rotM[1][0] = NewRot[5]*NewRot[0]*NewRot[1] - NewRot[3]*NewRot[2];
		rotM[1][1] = NewRot[5]*NewRot[1]*NewRot[1] + NewRot[4];
		rotM[1][2] = NewRot[5]*NewRot[1]*NewRot[2] + NewRot[3]*NewRot[0];
		rotM[2][0] = NewRot[5]*NewRot[0]*NewRot[2] + NewRot[3]*NewRot[1];
		rotM[2][1] = NewRot[5]*NewRot[1]*NewRot[2] - NewRot[3]*NewRot[0];
		rotM[2][2] = NewRot[5]*NewRot[2]*NewRot[2] + NewRot[4];
		//rotate rotation matrix
		if(MatMul(o->rotM, rotM, newM)) memcpy(&o->rotM, &newM, sizeof(newM));
		}
	if(accept) {
		//create new quaternion in RotDef from rotation matrix of output class
		Undo.RotDef(this, &RotDef, 0L);
		RotDef[4] = (o->rotM[0][0] + o->rotM[1][1] + o->rotM[2][2] -1)/2.0;
		RotDef[3] = sqrt(1.0-RotDef[4]*RotDef[4]);
		RotDef[0] = (o->rotM[1][2] - o->rotM[2][1])/(2.0 * RotDef[3]);
		RotDef[1] = (o->rotM[2][0] - o->rotM[0][2])/(2.0 * RotDef[3]);
		RotDef[2] = (o->rotM[0][1] - o->rotM[1][0])/(2.0 * RotDef[3]);
		RotDef[5] = 1.0-RotDef[4];
		}
}

bool
Plot3D::AcceptObj(GraphObj *go)
{
	if(!dispObs && !(dispObs = (obj_desc**)
		calloc(nmaxObs = 1024, sizeof(obj_desc*))))return false;
	else if((nObs+1) >= nmaxObs) dispObs = (obj_desc**)
		realloc(dispObs, (nmaxObs = nmaxObs +1024) * sizeof(obj_desc*));
	if(dispObs[++nObs] = (obj_desc*)calloc(1, sizeof(obj_desc))){
		dispObs[nObs]->Zmin = go->GetSize(SIZE_MIN_Z);
		dispObs[nObs]->Zmax = go->GetSize(SIZE_MAX_Z);
		dispObs[nObs]->go = go;
		}
	return true;
}

//Execute a heap sort before drawing the objects
//W.H. pres, B.P. Flannery, S.A. Teukolsky, W.T. Vetterling (1988/1989)
//Numerical Recipes in C, Cambridge University Press, ISBN 0-521-35465-X
// p. 245
int
Plot3D::cmp_obj_desc(obj_desc *obj1, obj_desc *obj2)
{
	double *v1, *v2, tmp1, tmp2;

	if(obj1->go->Id == GO_PLANE && obj2->go->Id == GO_PLANE) {
		if(obj1->Zmax < obj2->Zmin) return -1;
		if(obj2->Zmax < obj1->Zmin) return 1;
		if(obj1->Zmax == obj2->Zmax) {
			if(obj1->Zmin < obj2->Zmin) return -1;
			else if(obj1->Zmin > obj2->Zmin) return 1;
			else return 0;
			}
		if((v1=((plane*)(obj1->go))->GetVec()) && (v2=((plane*)(obj2->go))->GetVec()) ) {
			if(v1[0] == v2[0] && v1[1] == v2[1] && v1[2] == v2[2]){
				if(obj1->Zmax < obj2->Zmax) return -1;
				else if(obj2->Zmax < obj1->Zmax) return 1;
				else return 0;
				}
			}
		tmp1 = obj1->Zmax + obj1->Zmin;		tmp2 = obj2->Zmax + obj2->Zmin;
		if(tmp1 < tmp2) return -1;
		else if(tmp1 > tmp2) return 1;
		else return 0;
		}
	else {
		if(obj1->Zmin < obj2->Zmin) return -1;
		if(obj1->Zmin > obj2->Zmin) return 1;
		}
	return 0;
}

void
Plot3D::SortObj()
{
	int l, j, ir, i;
	obj_desc *rra;

	if(nObs < 2) return;
	l=(nObs >> 1)+1;			ir = nObs;
	for( ; ; ){
		if(l > 1) rra = dispObs[--l];
		else {
			rra = dispObs[ir];
			dispObs[ir] = dispObs[1];
			if(--ir == 1) {
				dispObs[1] = rra;
				return;
				}
			}
		i = l;					j = l << 1;
		while(j <= ir) {
			if(j < ir && cmp_obj_desc(dispObs[j], dispObs[j+1]) < 0) ++j;
			if(cmp_obj_desc(rra, dispObs[j])) {
				dispObs[i] = dispObs[j];
				j += (i = j);
				}
			else j = ir + 1;
			}
		dispObs[i] = rra;
		}
}

bool
Plot3D::Rotate(double dx, double dy, double dz, anyOutput *o, bool accept)
{
	int i;
	double si, NewRot[6];
	double rotM[3][3], newM[3][3];			//rotation matrices
	bool bRet = true;

	o->SetSpace(&cu1, &cu2, defs.cUnits, RotDef, &rc, Axes[0]->GetAxis(),
		Axes[1]->GetAxis(), Axes[2]->GetAxis());
	for(i = 0; i < 3; i++) {
		switch (i){
		case 0:
			if(dx > 0.0) {
				NewRot[0] = -o->rotM[1][0];			NewRot[1] = -o->rotM[1][1];
				NewRot[2] = -o->rotM[1][2];
				NewRot[3] = si = dx;
				}
			else {
				NewRot[0] = o->rotM[1][0];			NewRot[1] = o->rotM[1][1];
				NewRot[2] = o->rotM[1][2];
				NewRot[3] = si = -dx;
				}
			break;
		case 1:
			if(dy > 0.0) {
				NewRot[0] = -o->rotM[0][0];			NewRot[1] = -o->rotM[0][1];
				NewRot[2] = -o->rotM[0][2];
				NewRot[3] = si = dy;
				}
			else {
				NewRot[0] = o->rotM[0][0];			NewRot[1] = o->rotM[0][1];
				NewRot[2] = o->rotM[0][2];
				NewRot[3] = si = -dy;
				}
			break;
		case 2:
			if(dz > 0.0) {
				NewRot[0] = -o->rotM[2][0];			NewRot[1] = -o->rotM[2][1];
				NewRot[2] = -o->rotM[2][2];
				NewRot[3] = si = dz;
				}
			else {
				NewRot[0] = o->rotM[2][0];			NewRot[1] = o->rotM[2][1];
				NewRot[2] = o->rotM[2][2];
				NewRot[3] = si = -dz;
				}
			break;
			}
		if(si > 0.0) {
			NewRot[4] = sqrt(1.0-si*si);	NewRot[5] = 1.0-NewRot[4];
			//set up rotation matrix from quaternion
			//see: Graphic Gems, A.S. Glassner ed.; Academic Press Inc.
			//M.E. Pique: Rotation Tools
			// ISBN 0-12-286165-5, p.466
			rotM[0][0] = NewRot[5]*NewRot[0]*NewRot[0] + NewRot[4];
			rotM[0][1] = NewRot[5]*NewRot[0]*NewRot[1] + NewRot[3]*NewRot[2];
			rotM[0][2] = NewRot[5]*NewRot[0]*NewRot[2] - NewRot[3]*NewRot[1];
			rotM[1][0] = NewRot[5]*NewRot[0]*NewRot[1] - NewRot[3]*NewRot[2];
			rotM[1][1] = NewRot[5]*NewRot[1]*NewRot[1] + NewRot[4];
			rotM[1][2] = NewRot[5]*NewRot[1]*NewRot[2] + NewRot[3]*NewRot[0];
			rotM[2][0] = NewRot[5]*NewRot[0]*NewRot[2] + NewRot[3]*NewRot[1];
			rotM[2][1] = NewRot[5]*NewRot[1]*NewRot[2] - NewRot[3]*NewRot[0];
			rotM[2][2] = NewRot[5]*NewRot[2]*NewRot[2] + NewRot[4];
			if(MatMul(o->rotM, rotM, newM))	memcpy(&o->rotM, &newM, sizeof(newM));
			else accept = bRet = false;
			}
		}
	if(accept && bRet) {
		//create new quaternion in RotDef from rotation matrix of output class
		Undo.RotDef(this, &RotDef, 0L);
		RotDef[4] = (o->rotM[0][0] + o->rotM[1][1] + o->rotM[2][2] -1)/2.0;
		RotDef[3] = sqrt(1.0-RotDef[4]*RotDef[4]);
		RotDef[0] = (o->rotM[1][2] - o->rotM[2][1])/(2.0 * RotDef[3]);
		RotDef[1] = (o->rotM[2][0] - o->rotM[0][2])/(2.0 * RotDef[3]);
		RotDef[2] = (o->rotM[0][1] - o->rotM[1][0])/(2.0 * RotDef[3]);
		RotDef[5] = 1.0-RotDef[4];
		if(parent)return parent->Command(CMD_REDRAW, 0L, o);
		}
	return bRet;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// use Plot3D to create a 2.5 dimensional chart
Chart25D::Chart25D(GraphObj *par, DataObj *d, DWORD flags)
	:Plot3D(par, d, flags)
{
	dspm.fx = dspm.fy = dspm.fz = 1.0;
}

Chart25D::~Chart25D()
{
	if(name) free(name);		name=0L;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// use Plot3D to create a 2.5 dimensional ribbon chart
Ribbon25D::Ribbon25D(GraphObj *par, DataObj *d, DWORD flags)
	:Plot3D(par, d, flags)
{
	dspm.fx = dspm.fy = dspm.fz = 1.0;
}

Ribbon25D::~Ribbon25D()
{
	if(name) free(name);		name=0L;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// use Plot3D to create a 3 dimensional bubble plot
BubblePlot3D::BubblePlot3D(GraphObj *par, DataObj *d)
	:Plot3D(par, d, 0x0L)
{
}

BubblePlot3D::~BubblePlot3D()
{
	if(name) free(name);		name=0L;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 3D function plotter
Func3D::Func3D(GraphObj *par, DataObj *d)
	:Plot3D(par, d, 0x0L)
{
	FileIO(INIT_VARS);
	if(cmdxy = (char*)malloc(40*sizeof(char)))
		rlp_strcpy(cmdxy, 40, (char*)"r=sqrt(x*x+z*z)\ny=1-exp(-8/(r+1))");
	Id = GO_FUNC3D;
}

Func3D::Func3D(int src):Plot3D(0)
{
	int i;

	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		//now set parent in all children
		if(Axes) for(i = 0; i < nAxes; i++)
			if(Axes[i]) Axes[i]->parent = this;
		if(plots) for(i = 0; i < nPlots; i++)
			if(plots[i]) plots[i]->parent = this;
		}
}

Func3D::~Func3D()
{
	if(param) free(param);		param = 0L;
	if(cmdxy) free(cmdxy);		cmdxy = 0L;
	if(gda) delete(gda);		gda = 0L;
	if(name) free(name);		name=0L;
}

bool
Func3D::Command(int cmd, void *tmpl, anyOutput *o)
{
	switch(cmd) {
	case CMD_SET_DATAOBJ:
		Plot3D::Command(cmd, tmpl, o);
		if(gob && gda) gob->Command(cmd, gda, o);
		Id = GO_FUNC3D;
		return true;
	case CMD_UPDATE:
		return Update();
		break;
		}
	return Plot3D::Command(cmd, tmpl, o);
}

bool
Func3D::Update()
{
	if(cmdxy) {
		dirty = true;
		if(xstep == 0.0) xstep = 1.0;	if(zstep == 0.0) zstep = 1.0;
		if(!gda) gda = new DataObj();
		if(gda && do_func3D(gda, x1, x2, xstep, z1, z2, zstep, cmdxy, param)) {
			if(gob = new Grid3D(this, gda, type, x1, xstep, z1, zstep)) {
				gob->Command(CMD_SET_LINE, &Line, 0L);
				gob->Command(CMD_SYM_FILL, &Fill, 0L);
				if(!plots && (plots = (GraphObj**)calloc(2, sizeof(GraphObj*)))) {
					nPlots = 1;				plots[0] = (Plot *)gob;
					if(parent->Id == GO_GRAPH) CreateAxes();
					return dirty = parent->Command(CMD_REDRAW, 0L, 0L);
					}
				else if(plots && nPlots && plots[0]->Id == GO_GRID3D) {
					Undo.DeleteGO(&plots[0], UNDO_CONTINUE, 0L);
					Undo.SetGO(this, &plots[0], gob, UNDO_CONTINUE);
					return true;
					}
				else {
					DeleteGO(gob);		gob=0L;
					}
				}
			}
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// fit 3D function to data
static char *lastFunc3D = 0L, *lastParam3D=0L;
FitFunc3D::FitFunc3D(GraphObj *par, DataObj *d)
	:Plot3D(par, d, 0x0L)
{
	FileIO(INIT_VARS);
	if(lastFunc3D && lastFunc3D[0] && lastParam3D && lastParam3D[0]) {
		cmdxy = (char*)memdup(lastFunc3D, (int)strlen(lastFunc3D)+1, 0);
		param = (char*)memdup(lastParam3D, (int)strlen(lastParam3D)+1, 0);
		}
	if(!cmdxy || !param) {
		cmdxy = (char*)malloc(20*sizeof(char));		param = (char*)malloc(20*sizeof(char));
		if(cmdxy) rlp_strcpy(cmdxy, 20, (char*)"a+b*x^c");
		if(param) rlp_strcpy(param, 20, (char*)"a=1; b=1; c=0.1;");
		}
	Id = GO_FITFUNC3D;
}

FitFunc3D::FitFunc3D(int src):Plot3D(0)
{
	int i;

	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		//now set parent in all children
		if(Axes) for(i = 0; i < nAxes; i++)
			if(Axes[i]) Axes[i]->parent = this;
		if(plots) for(i = 0; i < nPlots; i++)
			if(plots[i]) plots[i]->parent = this;
		}
}

FitFunc3D::~FitFunc3D()
{
	if(param) free(param);		param = 0L;
	if(cmdxy) free(cmdxy);		cmdxy = 0L;
	if(gda) delete(gda);		gda = 0L;
	if(name) free(name);		name=0L;
}

bool
FitFunc3D::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i;

	switch(cmd) {
	case CMD_ENDDIALOG:
		if(!cmdxy || !param) return false;
		if(i = (int)strlen(cmdxy)) {
			if(lastFunc3D = (char*)realloc(lastFunc3D, i+2))
				rlp_strcpy(lastFunc3D, i+1, cmdxy);
			}
		if(i = (int)strlen(param)) {
			if(lastParam3D = (char*)realloc(lastParam3D, i+2))
				rlp_strcpy(lastParam3D, i+1, param);
			}
		return true;
	case CMD_SET_DATAOBJ:
		Plot3D::Command(cmd, tmpl, o);
		if(gob && gda) gob->Command(cmd, gda, o);
		Id = GO_FITFUNC3D;
		return true;
	case CMD_UPDATE:
		return Update();
		break;
		}
	return Plot3D::Command(cmd, tmpl, o);
}

bool
FitFunc3D::Update()
{
	if(cmdxy) {
		dirty = true;
		if(xstep == 0.0) xstep = 1.0;	if(zstep == 0.0) zstep = 1.0;
		if(!gda) gda = new DataObj();
		if(gda && do_func3D(gda, x1, x2, xstep, z1, z2, zstep, cmdxy, param)) {
			if(gob = new Grid3D(this, gda, type, x1, xstep, z1, zstep)) {
				gob->Command(CMD_SET_LINE, &Line, 0L);
				gob->Command(CMD_SYM_FILL, &Fill, 0L);
				if(!plots && (plots = (GraphObj**)calloc(3, sizeof(GraphObj*)))) {
					nPlots = 1;				plots[0] = (Plot *)gob;
					if(parent->Id == GO_GRAPH) CreateAxes();
					return dirty = parent->Command(CMD_REDRAW, 0L, 0L);
					}
				else if(plots && nPlots && plots[0]->Id == GO_GRID3D) {
					Undo.DeleteGO(&plots[0], UNDO_CONTINUE, 0L);
					Undo.SetGO(this, &plots[0], gob, UNDO_CONTINUE);
					return true;
					}
				else {
					DeleteGO(gob);		gob=0L;
					}
				}
			}
		}
	return false;
}
