//rlplot.cpp, Copyright 2000-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
//
#include "rlplot.h"
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>

extern tag_Units Units[];
extern char TmpTxt[];
extern Default defs;

GraphObj *CurrGO, *TrackGO;			//Selected Graphic Objects
Label *CurrLabel = 0L;
Graph *CurrGraph = 0L;
Axis **CurrAxes = 0L;
dragHandle *CurrHandle = 0L;
UndoObj Undo;
fmtText DrawFmtText;

int cGraphs = 0;
int cPlots = 0;
int cPages = 0;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// grapic objects
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
GraphObj::GraphObj(GraphObj *par, DataObj *d)
{
	parent = par;	data = d;	Id = GO_UNKNOWN;
	type = moveable = 0;		name = 0L;
	rDims.left = rDims.right = rDims.top = rDims.bottom = 0;
}

GraphObj::~GraphObj()
{
	if(name)free(name);		name = 0L;
	if(CurrGO == this)		CurrGO = 0L;
	if(TrackGO == this)	 TrackGO = 0L;
}

double
GraphObj::GetSize(int select)
{
	if(parent) return parent->GetSize(select);
	else return defs.GetSize(select);
}

DWORD 
GraphObj::GetColor(int select){
	return defs.Color(select);
}

void
GraphObj::RegGO(void *n)
{
	((notary*)n)->AddRegGO(this);
}

void *
GraphObj::ObjThere(int x, int y)
{
	if(IsInRect(&rDims, x, y)) return this;
	else return 0L;
}

void
GraphObj::Track(POINT *p, anyOutput *o)
{
}

double
GraphObj::DefSize(int select)
{
	if(parent) return parent->DefSize(select);
	else return defs.GetSize(select);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// This is a special object to read certain svg-settings from a *.rlp file
svgOptions::svgOptions(int src):GraphObj(0L, 0L)
{
	FileIO(INIT_VARS);
	if(defs.svgScript) free(defs.svgScript);
	if(defs.svgAttr) free(defs.svgAttr);
	defs.svgScript = defs.svgAttr = 0L;
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		if(script) defs.svgScript = script;
		if(svgattr) defs.svgAttr = svgattr;
		script = svgattr = 0L;
		}
	Id=GO_SVGOPTIONS;
}

svgOptions::~svgOptions()
{
	if(script)free(script);
	script = 0L;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Symbols are graphic objects
Symbol::Symbol(GraphObj *par, DataObj *d, double x, double y, int which,
		int xc, int xr, int yc, int yr):GraphObj(par, d)
{
	//Symbols with no parent are part of a dialog
	FileIO(INIT_VARS);
	fPos.fx = x;
	fPos.fy = y;
	type = which;
	Id = GO_SYMBOL;
	if(xc >= 0 && xr >= 0 && yc >= 0 && yr >= 0) {
		if(ssRef = (POINT*)malloc(sizeof(POINT)*2)) {
			ssRef[0].x = xc;	ssRef[0].y = xr;
			ssRef[1].x = yc;	ssRef[1].y = yr;
			cssRef = 2;
			}
		}
}

Symbol::Symbol(int src):GraphObj(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		SymFill.hatch = (LineDEF *) NULL;
		}
}

Symbol::~Symbol()
{
	Command(CMD_FLUSH, 0L, 0L);
}

double
Symbol::GetSize(int select)
{
	switch(select) {
	case SIZE_MINE:		case SIZE_SYMBOL:
		return size;
	case SIZE_SYM_LINE:
		return SymLine.width;
	case SIZE_XPOS:
		return fPos.fx;
	case SIZE_YPOS:
		return fPos.fy;
	default:
		return DefSize(select);
		}
}

bool
Symbol::SetSize(int select, double value)
{
	switch(select & 0xfff){
	case SIZE_MINE:		case SIZE_SYMBOL:
		size = value;
		return true;
	case SIZE_SYM_LINE:
		SymLine.width = value;
		return true;
	case SIZE_XPOS:
		fPos.fx = value;
		return true;
	case SIZE_YPOS:
		fPos.fy = value;
		return true;
	}
	return false;
}

DWORD
Symbol::GetColor(int select)
{
	switch(select) {
	case COL_SYM_LINE:
		return SymLine.color;
	case COL_SYM_FILL:
		return SymFill.color;
	default:
		return parent ? parent->GetColor(select) : defs.Color(select);
		}
}

bool
Symbol::SetColor(int select, DWORD col)
{
	switch(select & 0xfff) {
	case COL_SYM_LINE:
		SymLine.color = col;
		if(SymTxt) SymTxt->ColTxt = col;
		return true;
	case COL_SYM_FILL:
		SymFill.color = col;
		return true;
	default:
		return false;
		}
}

void
Symbol::DoPlot(anyOutput *target)
{
	int ix, iy, rx, ry, crx, cry, atype;
	double sc;
	long ncpts;
	lfPOINT fip;
	POINT pts[14], *cpts;
	FillDEF cf;

	atype = (type  & 0xfff);
	memcpy(&cf, &SymFill, sizeof(FillDEF));
	if(atype == SYM_CIRCLEF || atype == SYM_RECTF || atype == SYM_TRIAUF ||
		atype == SYM_TRIADF || atype == SYM_DIAMONDF || atype == SYM_4STARF ||
		atype == SYM_5GONF || atype == SYM_5STARF || atype == SYM_6STARF) cf.color = SymLine.color;
	if(type & SYM_POS_PARENT) {
		if(!parent) return;
		fip.fx = parent->GetSize(SIZE_XCENTER);
		fip.fy = parent->GetSize(SIZE_YCENTER);
		}
	else if(!target->fp2fip(&fPos, &fip)) return;
	ix = iround(fip.fx);		iy = iround(fip.fy);
	target->SetLine(&SymLine);
	switch(atype){
	default:
	case SYM_CIRCLE:		//circle
	case SYM_CIRCLEF:		//filled circle
	case SYM_CIRCLEC:		//circle with center point
	case SYM_1QUAD:		case SYM_2QUAD:		case SYM_3QUAD:
		rx = target->un2ix(size/2.0);		ry = target->un2iy(size/2.0);
		if(rx < 5) rx = 1;					if(ry < 5) ry = 1;
		target->SetFill(&cf);
		target->oCircle(ix-rx, iy-ry, ix+rx+1, iy+ry+1, name);
		if(atype == SYM_CIRCLEC) {
			crx = target->un2ix(size/5.0);	cry = target->un2iy(size/5.0);
			cf.color = SymLine.color;		target->SetFill(&cf);
			target->oCircle(ix-crx, iy-cry, ix+crx+1, iy+cry+1, name);
			}
		else if(atype == SYM_1QUAD || atype == SYM_2QUAD || atype == SYM_3QUAD) {
			ncpts = 0L;			cf.color = SymLine.color;		target->SetFill(&cf);
			if(atype == SYM_1QUAD) {
				if(!(cpts = MakeArc(ix, iy, rx, 0x04, &ncpts)) || !ncpts) return;
				cpts[0].x = ix + rx;		cpts[0].y = iy;
				}
			else if(atype == SYM_2QUAD) {
				if(!(cpts = MakeArc(ix, iy, rx, 0x06, &ncpts)) || !ncpts) return;
				cpts[0].x = ix;				cpts[0].y = iy+rx;
				}
			else if(atype == SYM_3QUAD) {
				if(!(cpts = MakeArc(ix, iy, rx, 0x07, &ncpts)) || !ncpts) return;
				cpts[0].x = ix-rx;			cpts[0].y = iy;
				}
			cpts[ncpts-1].x = ix;			cpts[ncpts-1].y = iy-rx;
			cpts[ncpts].x = ix;				cpts[ncpts].y = iy;				ncpts++;
			cpts[ncpts].x = cpts[0].x;		cpts[ncpts].y = cpts[0].y;		ncpts++;
			target->oPolygon(cpts, ncpts);	free(cpts);
			}
		rx--;ry--;			//smaller marking rectangle
		break;
	case SYM_RECT:			//rectange (square)
	case SYM_RECTF:			//filled rectangle
	case SYM_RECTC:			//square with center point
		rx = target->un2ix(size/2.25676);	ry = target->un2iy(size/2.25676);
		if(rx < 5) rx = 1;					if(ry < 5) ry = 1;
		target->SetFill(&cf);
		target->oRectangle(ix-rx, iy-ry, ix+rx+1, iy+ry+1, name);
		if(atype == SYM_RECTC) {
			crx = target->un2ix(size/6.0);	cry = target->un2iy(size/6.0);
			cf.color = SymLine.color;		target->SetFill(&cf);
			target->oCircle(ix-crx, iy-cry, ix+crx+1, iy+cry+1, name);
			}
		break;
	case SYM_TRIAU:			//triangles up and down, open or closed
	case SYM_TRIAUF:	case SYM_TRIAD:		case SYM_TRIADF:		case SYM_TRIADC:
	case SYM_TRIAUC:	case SYM_TRIAUL:	case SYM_TRIAUR:		case SYM_TRIADL:
	case SYM_TRIADR:
		rx = target->un2ix(size/1.48503);	ry = target->un2iy(size/1.48503);
		if(rx < 5) rx = 1;					if(ry < 5) ry = 1;
		target->SetFill(&cf);
		pts[0].x = pts[3].x = ix - rx;		pts[1].x = ix;		pts[2].x = ix+rx;
		//patch by anonymous
		if(atype == SYM_TRIAU || atype == SYM_TRIAUF || atype == SYM_TRIAUL 
			|| atype == SYM_TRIAUR || atype == SYM_TRIAUC) {
			pts[0].y = pts[2].y = pts[3].y = iy+target->un2iy(size*0.38878f);
			pts[1].y = iy-target->un2iy(size*0.77756f);
			}
		else {
			pts[0].y = pts[2].y = pts[3].y = iy-target->un2iy(size*0.38878f);
			pts[1].y = iy+target->un2iy(size*0.77756f);
			}
		target->oPolygon(pts, 4);
		if(atype == SYM_TRIAUC || atype == SYM_TRIADC) {
			crx = target->un2ix(size/6.0);	cry = target->un2iy(size/6.0);
			cf.color = SymLine.color;		target->SetFill(&cf);
			target->oCircle(ix-crx, iy-cry, ix+crx+1, iy+cry+1, name);
			}
		else if(atype == SYM_TRIAUL || atype == SYM_TRIADL) {
			cf.color = SymLine.color;		target->SetFill(&cf);
			pts[2].x = pts[1].x;			target->oPolygon(pts, 4);
			}
		else if(atype == SYM_TRIAUR || atype == SYM_TRIADR) {
			cf.color = SymLine.color;		target->SetFill(&cf);
			pts[0].x = pts[3].x = pts[1].x;	target->oPolygon(pts, 4);
			}
		rx--; ry--;
		break;
	case SYM_DIAMOND:	case SYM_DIAMONDF:		case SYM_DIAMONDC:
		rx = target->un2ix(size/1.59588);	ry = target->un2iy(size/1.59588);
		if(rx < 5) rx = 1;					if(ry < 5) ry = 1;
		target->SetFill(&cf);
		pts[0].x = pts[2].x = pts[4].x = ix;		
		pts[0].y = pts[4].y = iy -ry;
		pts[1].x = ix +rx;					pts[1].y = pts[3].y = iy;
		pts[2].y = iy +ry;					pts[3].x = ix-rx;
		target->oPolygon(pts, 5);
		if(atype == SYM_DIAMONDC) {
			crx = target->un2ix(size/6.0);	cry = target->un2iy(size/6.0);
			cf.color = SymLine.color;		target->SetFill(&cf);
			target->oCircle(ix-crx, iy-cry, ix+crx+1, iy+cry+1, name);
			}
		rx--;									ry--;
		break;
	case SYM_4STAR:		case SYM_4STARF:
		rx = target->un2ix(size/1.4);	ry = target->un2iy(size/1.4);
		crx = target->un2ix(size/6.0);	cry = target->un2iy(size/6.0);
		pts[0].x = pts[8].x = ix-rx;	pts[0].y = pts[4].y = pts[8].y = iy;
		pts[1].x = pts[7].x = ix-crx;	pts[1].y = pts[3].y = iy - cry;
		pts[2].x = pts[6].x = ix;		pts[2].y = iy - ry;
		pts[3].x = pts[5].x = ix+crx;	pts[4].x = ix + rx;
		pts[5].y = pts[7].y = iy+cry;	pts[6].y = iy + ry;
		target->SetFill(&cf);			target->oPolygon(pts, 9);
		break;
	case SYM_5GON:		case SYM_5GONF:		case SYM_5GONC:
		sc = 1.4;
		rx = target->un2ix(size/sc);	ry = target->un2iy(size/sc);
		crx = target->un2ix(size/sc * 0.951057);	
		cry = target->un2iy(size/sc * 0.309017);
		pts[0].x = ix-crx;		pts[0].y = pts[2].y = iy-cry;	pts[1].x = ix;
		pts[1].y = iy-ry;		pts[2].x = ix+crx;
		crx = target->un2ix(size/sc * 0.587785);	
		cry = target->un2iy(size/sc * 0.809017);
		pts[3].x = ix + crx;	pts[4].x = ix - crx;
		pts[3].y = pts[4].y = iy+cry;
		pts[5].x = pts[0].x;	pts[5].y = pts[0].y;
		target->SetFill(&cf);			target->oPolygon(pts, 6);
		if(atype == SYM_5GONC) {
			crx = target->un2ix(size/6.0);	cry = target->un2iy(size/6.0);
			cf.color = SymLine.color;		target->SetFill(&cf);
			target->oCircle(ix-crx, iy-cry, ix+crx+1, iy+cry+1, name);
			}
		break;
	case SYM_5STAR:		case SYM_5STARF:
		sc = 1.4;
		rx = target->un2ix(size/sc);	ry = target->un2iy(size/sc);
		crx = target->un2ix(size/sc * 0.951057);	
		cry = target->un2iy(size/sc * 0.309017);
		pts[0].x = ix-crx;		pts[0].y = pts[1].y = pts[3].y = pts[4].y = iy-cry;
		pts[2].x = pts[7].x = ix;		pts[2].y = iy-ry;		pts[4].x = ix+crx;
		crx = target->un2ix(size/sc * 0.23);
		pts[1].x = ix - crx;	pts[3].x = ix + crx;
		crx =  target->un2ix(size/sc * 0.36);
		cry = target->un2iy(size/sc * 0.11);
		pts[5].x = ix + crx;	pts[5].y = pts[9].y = iy +	cry;	pts[9].x = ix - crx;
		pts[7].y = iy + target->un2iy(size/sc * 0.38);
		crx = target->un2ix(size/sc * 0.587785);	
		cry = target->un2iy(size/sc * 0.809017);
		pts[6].x = ix + crx;	pts[8].x = ix - crx;
		pts[6].y = pts[8].y = iy+cry;
		pts[10].x = pts[0].x;	pts[10].y = pts[0].y;
		target->SetFill(&cf);			target->oPolygon(pts, 11);
		break;
	case SYM_6STAR:		case SYM_6STARF:
		sc = 1.4 / 0.86;				rx = target->un2ix(size/sc);
		sc = 1.4 / 0.5;					ry = target->un2iy(size/sc);
		pts[0].x = pts[10].x = pts[12].x = ix - rx;
		pts[4].x = pts[6].x = ix + rx;	pts[2].x = pts[8].x = ix;
		pts[0].y = pts[1].y = pts[3].y = pts[4].y = pts[12].y = iy - ry;
		pts[6].y = pts[7].y = pts[9].y = pts[10].y = iy + ry;	
		sc = 1.4 / 0.29;				rx = target->un2ix(size/sc);
		pts[1].x = pts[9].x = ix - rx;	pts[3].x = pts[7].x = ix + rx;
		sc = 1.4 / 0.52;				rx = target->un2ix(size/sc);
		pts[5].x = ix +rx;	pts[11].x = ix - rx;	pts[5].y = pts[11].y = iy;
		sc = 1.4;
		rx = target->un2ix(size/sc);	ry = target->un2iy(size/sc);
		pts[2].y = iy - ry;				pts[8].y = iy + ry;
		target->SetFill(&cf);			target->oPolygon(pts, 13);
		break;
	case SYM_STAR:			//star is a combination of + and x symbols
	case SYM_PLUS:			//draw a + sign
	case SYM_HLINE:		case SYM_VLINE:
		rx = target->un2ix(size/2.0f);		ry = target->un2iy(size/2.0f);
		if(rx < 5) rx = 1;					if(ry < 5) ry = 1;
		pts[0].x = pts[1].x = ix;
		pts[0].y = iy - ry;					pts[1].y = iy + ry +1;
		if(type != SYM_HLINE) target->oPolyline(pts, 2);
		pts[0].x = ix -rx;					pts[1].x = ix + rx +1;
		pts[0].y = pts[1].y = iy;
		if(atype != SYM_VLINE) target->oPolyline(pts, 2);
		if(atype == SYM_VLINE){ rx = 2; break;}
		if(atype == SYM_HLINE){ ry = 2; break;}
		if(atype == SYM_PLUS) break;		//continue with x symbol for star
	case SYM_CROSS:			//draw a x symbol
		rx = target->un2ix(size/2.5);		ry = target->un2iy(size/2.5);
		if(rx < 5) rx = 1;					if(ry < 5) ry = 1;
		pts[0].x = ix - rx - 2;				pts[1].x = ix + rx + 3;
		pts[0].y = iy - ry - 2;				pts[1].y = iy + ry + 3;
		target->oPolyline(pts, 2);			Swap(pts[0].y, pts[1].y);
		pts[1].y -= 1;						pts[0].y -= 1;
		target->oPolyline(pts, 2);
		break;
	case SYM_TEXT:
		if(!SymTxt) Command(CMD_SETTEXT, (void *)"text", target);
		if(!SymTxt || !SymTxt->text || !SymTxt->text[0])return;
		SymTxt->iSize = target->un2iy(SymTxt->fSize = size *1.5);
		target->SetTextSpec(SymTxt);
		DrawFmtText.SetText(target, SymTxt->text, &ix, &iy);
		if (target->oGetTextExtent(SymTxt->text, 0, &rx, &ry)){
			rx >>= 1;		ry >>= 1;
			}
		else rx = ry = 10;
		}
	rDims.left = ix-rx-1;				rDims.right = ix+rx+2;
	rDims.top = iy-ry-1;				rDims.bottom = iy+ry+2;
}

bool 
Symbol::Command(int cmd, void *tmpl, anyOutput *o)
{
	MouseEvent *mev;
	char *tmptxt;
	AccRange *ac;
	int i, r, c;

	switch (cmd) {
	case CMD_SCALE:
		if(!tmpl) return false;
		size *= ((scaleINFO*)tmpl)->sy.fy;
		SymLine.width *= ((scaleINFO*)tmpl)->sy.fy;
		if(SymTxt) {
			SymTxt->fSize *= ((scaleINFO*)tmpl)->sy.fy;
			SymTxt->iSize = 0;
			}
		return true;
	case CMD_FLUSH:
		if(SymTxt) {
			if(SymTxt->text) free(SymTxt->text);
			free(SymTxt);
			}
		if(ssRef) free(ssRef);	ssRef = 0L;
		if(name)free(name);		name = 0L;
		return true;
	case CMD_REDRAW:
		//if we come here its most likely the result of Undo
		if(parent && parent->Id==GO_REGRESSION)
			return parent->Command(CMD_MRK_DIRTY, 0L, o);
		return false;
	case CMD_GETTEXT:
		if(SymTxt && SymTxt->text && tmpl) {
			rlp_strcpy((char*)tmpl, 50, SymTxt->text);
			return true;
			}
		return false;
	case CMD_SYMTEXT_UNDO:
		if(SymTxt && SymTxt->text){
			c = Undo.String(this, &SymTxt->text, UNDO_CONTINUE);
			i = (int)strlen((char*)tmpl);		i = i > c ? i+2 : c+2;
			if(tmpl) {
				if(SymTxt->text = (char*)realloc(SymTxt->text, i)) rlp_strcpy(SymTxt->text, i, (char*)tmpl);
				}
			else if(SymTxt->text) SymTxt->text[0] = 0;
			return true;
			}
		//fall through if its new
	case CMD_SYMTEXT:		case CMD_SETTEXT:
		if(!SymTxt && (SymTxt = (TextDEF *) calloc(1, sizeof(TextDEF)))) {
			SymTxt->ColTxt = SymLine.color;			SymTxt->fSize = size*1.5;
			SymTxt->ColBg = parent ? parent->GetColor(COL_BG) : 0x00ffffffL;
			SymTxt->Align = TXA_VCENTER | TXA_HCENTER;
			SymTxt->Style = TXS_NORMAL;				SymTxt->Mode = TXM_TRANSPARENT;
			SymTxt->Font = FONT_HELVETICA;			SymTxt->text = 0L;
			}
		if(!SymTxt) return false;
		if(tmpl) {
			i = (int) strlen((char*)tmpl) + 2;
			if(SymTxt->text = (char*)realloc(SymTxt->text, i)) rlp_strcpy(SymTxt->text, i, (char*)tmpl);
			}
		else if(SymTxt->text) SymTxt->text[0] = 0;
		return true;
	case CMD_SYM_TYPE:
		if(tmpl)type = *((int*)tmpl);
		return true;
	case CMD_GETTEXTDEF:
		if(!SymTxt || !tmpl) return false;
		memcpy(tmpl, SymTxt, sizeof(TextDEF));
		return true;
	case CMD_SYMTEXTDEF:		case CMD_SETTEXTDEF:
		if(!tmpl)return false;
		if(SymTxt) tmptxt = SymTxt->text;
		else tmptxt = 0L;
		if(!SymTxt && !(SymTxt = (TextDEF *) calloc(1, sizeof(TextDEF)))) return false;
		memcpy(SymTxt, tmpl, sizeof(TextDEF));
		SymTxt->text = tmptxt;
		return true;
	case CMD_SYM_RANGETEXT:		case CMD_RANGETEXT:
		if(!data || !tmpl) return false;
		if(!(tmptxt = (char*)malloc(500)))return false;
		if((ac = new AccRange((char*)tmpl)) && ac->GetFirst(&c, &r)) {
			for(i = 0, tmptxt[0] = 0; i <= idx; i++) ac->GetNext(&c, &r);
			data->GetText(r, c, tmptxt, 500);
			delete(ac);
			}
		Command(CMD_SETTEXT, tmptxt, 0L);
		free(tmptxt);
		return true;
	case CMD_SET_DATAOBJ:
		Id = GO_SYMBOL;
		data = (DataObj *)tmpl;
		return true;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(IsInRect(&rDims, mev->x, mev->y) && !CurrGO) {
				o->ShowMark(&rDims, MRK_INVERT);
				CurrGO = this;
				return true;
				}
			break;
			}
		break;
	case CMD_UPDATE:
		if(ssRef && cssRef >1 && data) {
			data->GetValue(ssRef[0].y, ssRef[0].x, &fPos.fx);
			data->GetValue(ssRef[1].y, ssRef[1].x, &fPos.fy);
			return true;
			}
		return false;
	case CMD_AUTOSCALE:
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH) {
			((Plot*)parent)->CheckBounds(fPos.fx, fPos.fy);
			return true;
			}
		break;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Bubbles are graphic objects
Bubble::Bubble(GraphObj *par, DataObj *d, double x, double y, double s, int which, 
	FillDEF *fill, LineDEF *outline, int xc, int xr, int yc, int yr, int sc,
	int sr):GraphObj(par, d)
{
	FileIO(INIT_VARS);
	fPos.fx = x;	fPos.fy = y;	fs = s;
	type = which;
	if(fill) {
		memcpy(&BubbleFill,fill, sizeof(FillDEF));
		if(BubbleFill.hatch) memcpy(&BubbleFillLine, BubbleFill.hatch, sizeof(LineDEF));
		}
	BubbleFill.hatch = &BubbleFillLine;
	if(outline)memcpy(&BubbleLine, outline, sizeof(LineDEF));
	Id = GO_BUBBLE;
	if(xc >= 0 || xr >= 0 || yc >= 0 || yr >= 0 || sc >= 0 || sr >= 0) {
		if(ssRef = (POINT*)malloc(sizeof(POINT)*3)) {
			ssRef[0].x = xc;	ssRef[0].y = xr;
			ssRef[1].x = yc;	ssRef[1].y = yr;
			ssRef[2].x = sc;	ssRef[2].y = sr;
			cssRef = 3;
			}
		}
}

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

Bubble::~Bubble()
{
	Command(CMD_FLUSH, 0L, 0L);
}

void
Bubble::DoPlot(anyOutput *o)
{
	int x1, y1, x2, y2, ix, iy, tmp;
	double fix, fiy;

	o->SetLine(&BubbleLine);		o->SetFill(&BubbleFill);
//	if(mo) DelBitmapClass(mo);		mo = 0L;
	switch(type & 0x0f0) {
	case BUBBLE_UNITS:
		fix = o->un2fix(fs);		fiy = o->un2fiy(fs);
		break;
	case BUBBLE_XAXIS:
		fix = (o->fx2fix(fPos.fx+fs) - o->fx2fix(fPos.fx-fs))/2.0;
		fiy = fix * (o->un2fiy(10.0f)/o->un2fix(10.0f));	//x and y resolution different ?
		break;
	case BUBBLE_YAXIS:
		fix = (o->fy2fiy(fPos.fy-fs) - o->fy2fiy(fPos.fy+fs))/2.0;
		fiy = fix * (o->un2fiy(10.0f)/o->un2fix(10.0f));	//x and y resolution different ?
		break;
		}
	fix = fix < 0.0 ? -fix : fix;							//sign must be positive
	fiy = fiy < 0.0 ? -fiy : fiy;
	rDims.left = rDims.right = iround(o->fx2fix(fPos.fx));
	rDims.top = rDims.bottom = iround(o->fy2fiy(fPos.fy));
	switch(type & 0x00f) {
	case BUBBLE_CIRCLE:
		ix = (int)(fix/2.0);			iy = (int)(fiy/2.0);
		tmp = iround(o->fx2fix(fPos.fx));		x1 = tmp - ix;		x2 = tmp + ix;
		tmp = iround(o->fy2fiy(fPos.fy));		y1 = tmp - iy;		y2 = tmp + iy;
		o->oCircle(x1, y1, x2, y2, name);
		UpdateMinMaxRect(&rDims, x1, y1);	UpdateMinMaxRect(&rDims, x2, y2);
		break;
	case BUBBLE_SQUARE:
		if((type & 0xf00) == BUBBLE_CIRCUM) {
			ix = iround(fix*.392699081);		iy = iround(fiy*.392699081);
			}
		else if((type & 0xf00) == BUBBLE_AREA) {
			ix = iround(fix*.443113462);		iy = iround(fiy*.443113462);
			}
		else {
			ix = iround(fix*.353553391);		iy = iround(fiy*.353553391);
			}
		tmp = iround(o->fx2fix(fPos.fx));		x1 = tmp - ix;		x2 = tmp + ix;
		tmp = iround(o->fy2fiy(fPos.fy));		y1 = tmp - iy;		y2 = tmp + iy;
		o->oRectangle(x1, y1, x2, y2, name);
		UpdateMinMaxRect(&rDims, x1, y1);	UpdateMinMaxRect(&rDims, x2, y2);
		break;
	case BUBBLE_UPTRIA:
	case BUBBLE_DOWNTRIA:
		if((type & 0xf00) == BUBBLE_CIRCUM) {
			fix *= .523598775;		fiy *= .523598775;
			}
		else if((type & 0xf00) == BUBBLE_AREA) {
			fix *= .673386843;		fiy *= .673386843;
			}
		else {
			fix *=.433012702;		fiy *= .433012702;
			}
		ix =  iround(fix);		iy = iround(fiy*.57735);
		tmp = iround(o->fx2fix(fPos.fx));
		pts[0].x = pts[3].x = tmp - ix;		pts[1].x = tmp + ix;		pts[2].x = tmp;
		tmp = iround(o->fy2fiy(fPos.fy));
		if((type & 0x00f) == BUBBLE_UPTRIA) {
			pts[0].y = pts[1].y = pts[3].y = tmp + iy;
			pts[2].y = tmp - iround(fiy*1.1547);
			}
		else {
			pts[0].y = pts[1].y = pts[3].y = tmp - iy;
			pts[2].y = tmp + iround(fiy*1.1547);
			}
		o->oPolygon(pts, 4);
		UpdateMinMaxRect(&rDims, pts[0].x, pts[0].y);
		UpdateMinMaxRect(&rDims, pts[1].x, pts[2].y);
		break;
		}
}

void
Bubble::DoMark(anyOutput *o, bool mark)
{
	if(mark) {
		BubbleFillLine.color ^= 0x00ffffffL;
		BubbleFill.color ^= 0x00ffffffL;
		DoPlot(o);
		BubbleFill.color ^= 0x00ffffffL;
		BubbleFillLine.color ^= 0x00ffffffL;
		}
	else {
		if(parent) parent->DoPlot(o);
		else DoPlot(o);
		}
	o->UpdateRect(&rDims, false);
}

bool 
Bubble::Command(int cmd, void *tmpl, anyOutput *o)
{
	MouseEvent *mev;
	bool bSelected = false;
	unsigned long n, s;
	POINT p;

	switch (cmd) {
	case CMD_FLUSH:
		if(ssRef) free(ssRef);	ssRef = 0L;
		if(name)free(name);		name = 0L;
		return true;
	case CMD_SCALE:
		if(!tmpl) return false;
		if((type & 0x0f0)== BUBBLE_UNITS) fs *= ((scaleINFO*)tmpl)->sy.fy;
		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;
		return true;
	case CMD_LEGEND:
		if(!tmpl || ((GraphObj*)tmpl)->Id != GO_LEGEND) return false;
		((Legend*)tmpl)->HasFill(&BubbleLine, &BubbleFill, 0L);
		break;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(IsInRect(&rDims, p.x = mev->x, p.y = mev->y) && !CurrGO) {
				switch(type & 0x00f) {
				case BUBBLE_CIRCLE:
					n = s = p.x - ((rDims.right+rDims.left)>>1);
					s *= n;
					n = p.y - ((rDims.bottom+rDims.top)>>1);
					n = isqr(s += n*n) -2;
					bSelected = ((unsigned)((rDims.right-rDims.left)>>1) > n);
					break;
				case BUBBLE_SQUARE:
					bSelected = true;
					break;
				case BUBBLE_UPTRIA:
				case BUBBLE_DOWNTRIA:
					if(!(bSelected = IsInPolygon(&p, pts, 4)))
						bSelected = IsCloseToPL(p, pts, 4);
					break;
					}
				if(bSelected) o->ShowMark(this, MRK_GODRAW);
				return bSelected;
				}
			break;
			}
		break;
	case CMD_SET_DATAOBJ:
		Id = GO_BUBBLE;
		data = (DataObj*)tmpl;
		return true;
	case CMD_UPDATE:
		if(ssRef && cssRef >2 && data) {
			data->GetValue(ssRef[0].y, ssRef[0].x, &fPos.fx);
			data->GetValue(ssRef[1].y, ssRef[1].x, &fPos.fy);
			data->GetValue(ssRef[2].y, ssRef[2].x, &fs);
			return true;
			}
		return false;
	case CMD_BUBBLE_ATTRIB:
		if(tmpl) {
			type &= ~0xff0;
			type |= (*((int*)tmpl) & 0xff0);
			return true;
			}
		return false;
	case CMD_BUBBLE_TYPE:
		if(tmpl) {
			type &= ~0x00f;
			type |= (*((int*)tmpl) & 0x00f);
			return true;
			}
		return false;
	case CMD_BUBBLE_FILL:
		if(tmpl) {
			BubbleFill.type = ((FillDEF*)tmpl)->type;
			BubbleFill.color = ((FillDEF*)tmpl)->color;
			BubbleFill.scale = ((FillDEF*)tmpl)->scale;
			if(((FillDEF*)tmpl)->hatch)
				memcpy(&BubbleFillLine, ((FillDEF*)tmpl)->hatch, sizeof(LineDEF));
			}
		return true;
	case CMD_BUBBLE_LINE:
		if(tmpl) memcpy(&BubbleLine, tmpl, sizeof(LineDEF));
		return true;
	case CMD_AUTOSCALE:
		return DoAutoscale(o);
		break;
		}
	return false;
}

bool
Bubble::DoAutoscale(anyOutput *o) 
{
	double dx, dy;

	switch(type & 0x0f0) {
	case BUBBLE_XAXIS:			case BUBBLE_YAXIS:
		dx = dy = fs/2.0;		break;
	case BUBBLE_UNITS:
		dx = fPos.fx/20;		dy = fPos.fy/20;		break;
		}
	((Plot*)parent)->CheckBounds(fPos.fx+dx, fPos.fy-dy);
	((Plot*)parent)->CheckBounds(fPos.fx-dx, fPos.fy+dy);
	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Bars are graphic objects
Bar::Bar(GraphObj *par, DataObj *d, double x, double y, int which,int xc, int xr,
		int yc, int yr, char *desc):GraphObj(par, d)
{
	FileIO(INIT_VARS);
	fPos.fx = x;			fPos.fy = y;			type = which;
	if(type & BAR_RELWIDTH) size = 60.0;			Id = GO_BAR;
	if(xc >= 0 || xr >= 0 || yc >= 0 || yr >= 0) {
		if(ssRef = (POINT*)malloc(sizeof(POINT)*2)) {
			ssRef[0].x = xc;	ssRef[0].y = xr;
			ssRef[1].x = yc;	ssRef[1].y = yr;
			cssRef = 2;
			}
		}
	if(desc && desc[0]) name = (char*)memdup(desc, (int)strlen(desc)+1, 0);
}

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

Bar::~Bar()
{
	if(mo) DelBitmapClass(mo);	mo = 0L;
	Command(CMD_FLUSH, 0L, 0L);
}

double
Bar::GetSize(int select)
{
	switch(select){
	case SIZE_XPOS:				return fPos.fx;
	case SIZE_YPOS:				return fPos.fy;
		}
	return 0.0;
}

bool
Bar::SetSize(int select, double value)
{
	switch(select & 0xfff) {
	case SIZE_BAR: 
		size = value;
		return true;
	case SIZE_BAR_LINE:
		BarLine.width = value;
		return true;
	case SIZE_XBASE:
		BarBase.fx = value;
		return true;
	case SIZE_YBASE:
		BarBase.fy = value;
		return true;
		}
	return false;
}

bool
Bar::SetColor(int select, DWORD col)
{
	switch(select & 0xfff) {
	case COL_BAR_LINE:
		BarLine.color = col;
		return true;
	case COL_BAR_FILL:
		BarFill.color = col;
		return true;
		}
	return false;
}

void
Bar::DoPlot(anyOutput *target)
{
	int w;
	double fBase, rsize;
	POINT pts[2];

	if(!parent || size <= 0.001) return;
	target->SetLine(&BarLine);			target->SetFill(&BarFill);
	if(mo) DelBitmapClass(mo);			mo = 0L;
	switch(type & 0xff) {
	case BAR_VERTU:		case BAR_VERTT:		case BAR_VERTB:
		switch(type & 0xff) {
		case BAR_VERTB:
			fBase = parent->GetSize(SIZE_BOUNDS_BOTTOM);
			break;
		case BAR_VERTT:
			fBase = parent->GetSize(SIZE_BOUNDS_TOP);
			break;
		case BAR_VERTU:
			fBase = BarBase.fy;
			break;
			}
		if(type & BAR_RELWIDTH) {
			rsize = size * parent->GetSize(SIZE_BARMINX)/100.0;
			pts[0].x = iround(target->fx2fix(fPos.fx - rsize/2.0));
			pts[1].x = iround(target->fx2fix(fPos.fx + rsize/2.0));
			}
		else {
			w = target->un2ix(size);
			pts[0].x = iround(target->fx2fix(fPos.fx)) - (w>>1);
			pts[1].x = pts[0].x + w;
			}
		if(type & BAR_CENTERED) {
			pts[0].y = iround(target->fy2fiy(fBase - (fPos.fy - fBase)));
			pts[1].y = iround(target->fy2fiy(fBase + (fPos.fy - fBase)));
			}
		else {
			pts[0].y = iround(target->fy2fiy(fBase));
			pts[1].y = iround(target->fy2fiy(fPos.fy));
			}
		break;
	case BAR_HORU:		case BAR_HORR:		case BAR_HORL:
		switch(type & 0xff) {
		case BAR_HORL:
			fBase = parent->GetSize(SIZE_BOUNDS_LEFT);
			break;
		case BAR_HORR:
			fBase = parent->GetSize(SIZE_BOUNDS_RIGHT);
			break;
		case BAR_HORU:
			fBase = BarBase.fx;
			break;
			}
		if(type & BAR_RELWIDTH) {
			rsize = size * parent->GetSize(SIZE_BARMINY)/100.0;
			pts[0].y = iround(target->fy2fiy(fPos.fy - rsize/2.0));
			pts[1].y = iround(target->fy2fiy(fPos.fy + rsize/2.0));
			}
		else {
			w = target->un2iy(size);
			pts[0].y = target->fy2iy(fPos.fy) - w/2;
			pts[1].y = pts[0].y+w;
			}
		if(type & BAR_CENTERED) {
			pts[0].x = target->fx2ix(fBase - (fPos.fx - fBase));
			pts[1].x = target->fx2ix(fBase + (fPos.fx - fBase));
			}
		else {
			pts[0].x = target->fx2ix(fBase);
			pts[1].x = target->fx2ix(fPos.fx);
			}
		break;
	default:
		return;
		}
	if(pts[0].x == pts[1].x || pts[0].y == pts[1].y) {
		target->oSolidLine(pts);
		}
	else target->oRectangle(pts[0].x, pts[0].y, pts[1].x, pts[1].y, name);
	SetMinMaxRect(&rDims, pts[0].x, pts[0].y, pts[1].x, pts[1].y);
}

void
Bar::DoMark(anyOutput *o, bool mark)
{
	POINT mpts[5];

	if(mark){
		if(mo) DelBitmapClass(mo);			mo = 0L;
		mpts[0].x = mpts[4].x = mpts[3].x = rDims.left;
		mpts[0].y = mpts[4].y = mpts[1].y = rDims.bottom;
		mpts[1].x = mpts[2].x = rDims.right;	mpts[2].y = mpts[3].y = rDims.top;
		memcpy(&mrc, &rDims, sizeof(RECT));
		IncrementMinMaxRect(&mrc, 3*o->un2ix(BarLine.width)+2);
		mo = GetRectBitmap(&mrc, o);
		InvertLine(mpts, 5, &BarLine, &mrc, o, mark);
		}
	else RestoreRectBitmap(&mo, &mrc, o);
}

bool 
Bar::Command(int cmd, void *tmpl, anyOutput *o)
{
	MouseEvent *mev;
	FillDEF *TmpFill;
	lfPOINT bl;

	switch (cmd) {
	case CMD_FLUSH:
		if(ssRef) free(ssRef);		ssRef = 0L;
		if(name)free(name);			name = 0L;
		return true;
	case CMD_SCALE:
		if(!tmpl) return false;
		if(!(type & BAR_RELWIDTH)) size *= ((scaleINFO*)tmpl)->sy.fy;
		BarLine.width *= ((scaleINFO*)tmpl)->sy.fy;
		BarLine.patlength *= ((scaleINFO*)tmpl)->sy.fy;
		HatchLine.width *= ((scaleINFO*)tmpl)->sy.fy;
		HatchLine.patlength *= ((scaleINFO*)tmpl)->sy.fy;
		BarFill.scale *= ((scaleINFO*)tmpl)->sy.fy;
		return true;
	case CMD_LEGEND:
		if(!tmpl || ((GraphObj*)tmpl)->Id != GO_LEGEND) return false;
		((Legend*)tmpl)->HasFill(&BarLine, &BarFill, name);
		break;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(IsInRect(&rDims, mev->x, mev->y) && !CurrGO) {
				o->ShowMark(CurrGO = this, MRK_GODRAW);
				return true;
				}
			break;
			}
		return false;
	case CMD_BAR_FILL:
		TmpFill = (FillDEF *)tmpl;
		if(TmpFill) {
			BarFill.type = TmpFill->type;
			BarFill.color = TmpFill->color;
			BarFill.scale = TmpFill->scale;
			if(TmpFill->hatch) memcpy(&HatchLine, TmpFill->hatch, sizeof(LineDEF));
			}
		return true;
	case CMD_BAR_TYPE:
		if(tmpl) type = *((int*)tmpl);
		return true;
	case CMD_SET_DATAOBJ:
		Id = GO_BAR;
		data = (DataObj *)tmpl;
		return true;
	case CMD_UPDATE:
		if(ssRef && cssRef >1 && data) {
			data->GetValue(ssRef[0].y, ssRef[0].x, &fPos.fx);
			data->GetValue(ssRef[1].y, ssRef[1].x, &fPos.fy);
			return true;
			}
		return false;
	case CMD_AUTOSCALE:
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH) {
			((Plot*)parent)->CheckBounds(fPos.fx, fPos.fy);
			switch(type & 0xff) {
			case BAR_VERTU:
			case BAR_VERTT:
			case BAR_VERTB:
				bl.fx = fPos.fx;
				switch (type & 0xff) {
				case BAR_VERTU:
					bl.fy = BarBase.fy;
					break;
				case BAR_VERTT:
				case BAR_VERTB:
					bl.fy = 0.0f;		//cannot resolve
					break;
					}
				if(type & BAR_CENTERED) bl.fy -= fPos.fy;
				break;
			case BAR_HORU:
			case BAR_HORR:
			case BAR_HORL:
				bl.fy = fPos.fy;
				switch(type & 0xff) {
				case BAR_HORU:
					bl.fx = BarBase.fx;
				case BAR_HORR:
				case BAR_HORL:
					bl.fx = 0.0f;		//cannot resolve
					}
				if(type & BAR_CENTERED) bl.fx -= fPos.fx;
				break;
				}
			((Plot*)parent)->CheckBounds(bl.fx, bl.fy);
			return true;
			}
		break;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Data line is a graphic object
DataLine::DataLine(GraphObj *par, DataObj *d, char *xrange, char *yrange, char *nam):GraphObj(par, d)
{
	size_t cb;

	FileIO(INIT_VARS);
	Id = GO_DATALINE;
	if(xrange && xrange[0]) {
		cb = strlen(xrange) +2;		ssXref = (char*)malloc(cb);		rlp_strcpy(ssXref, (int)cb, xrange);
		}
	if(yrange && yrange[0]) {
		cb = strlen(yrange) +2;		ssYref = (char*)malloc(cb);		rlp_strcpy(ssYref, (int)cb, yrange);
		}
	if(nam && nam[0]) name = (char*)memdup(nam, (int)strlen(nam)+1, 0);
	SetValues();
}
	
DataLine::DataLine(GraphObj *par, DataObj *d, lfPOINT *val, long nval, char *na):GraphObj(par, d)
{
	int cb;

	FileIO(INIT_VARS);
	Values = val;			nPnt = nval;	nPntSet = nPnt-1;
	if(na && na[0] && (name = (char*)malloc((cb = (int)strlen(na))+2))) {
		rlp_strcpy(name, cb+1, na);
		}
	Id = GO_DATALINE;
}

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

DataLine::~DataLine()
{
	if(Values)free(Values);		Values = 0L;
	if(pts) free(pts);			pts = 0L;
	if(ssXref) free(ssXref);	ssXref = 0L;
	if(ssYref) free(ssYref);	ssYref = 0L;
	if(mo) DelBitmapClass(mo);	mo = 0L;
	if(name) free(name);		name = 0L;
	if(parent)parent->Command(CMD_MRK_DIRTY, 0L, 0L);
}

bool
DataLine::SetColor(int select, DWORD col)
{
	switch(select & 0xfff) {
	case COL_DATA_LINE:
		LineDef.color = col;		return true;
	case COL_POLYGON:
		pgFill.color = col;
		LineDef.color = ((col & 0x00fefefeL)>>1);
		return true;
		}
	return false;
}

void
DataLine::DoPlot(anyOutput *target)
{
	int i;
	lfPOINT fip;
	POINT pn, *tmppts;

	if(!Values || nPntSet < 1) return;
	if (nPntSet >= nPnt) nPntSet = nPnt-1;
	if(mo) DelBitmapClass(mo);		mo = 0L;
	if(pts) free(pts);				pts = 0L;
	if((type & 0xff) == 9 || (type & 0xff) == 10)	//splines
		pts = (POINT *)malloc(sizeof(POINT)*1000);
	else if((type & 0xff) == 11 || (type & 0xff) == 12)			// curve
		pts = (POINT *) malloc(sizeof(POINT)* (nPntSet+2)*192);
	else if(type & 0xff) pts = (POINT *)malloc(sizeof(POINT)*(nPntSet+2)*2);
	else pts = (POINT *)malloc(sizeof(POINT)*(nPntSet+2));
	if(!pts) return;
	if(max.fx > min.fx && max.fy > min.fy) dirty = false;
	else if(dirty) Command(CMD_AUTOSCALE, 0L, target);
	cp = 0;
	switch(type & 0x0f) {
	case 0:		default:
		for (i = 0; i <= nPntSet; i++){
			target->fp2fip(Values+i, &fip);
			pn.x = iround(fip.fx);		pn.y = iround(fip.fy);
			AddToPolygon(&cp, pts, &pn);
			}
		break;
	case 5:
		target->fp2fip(Values, &fip);
		pn.x = iround(fip.fx);		pn.y = iround(fip.fy);
		target->fp2fip(Values+1, &fip);
		pn.y += (pn.y -iround(fip.fy))>>1;
		AddToPolygon(&cp, pts, &pn);
	case 1:
		target->fp2fip(Values, &fip);
		pn.x = iround(fip.fx);		pn.y = iround(+fip.fy);
		for (i = 0; i <= nPntSet; i++){
			target->fp2fip(Values+i, &fip);
			pn.x = iround(fip.fx);			AddToPolygon(&cp, pts, &pn);
			pn.y = iround(fip.fy);			AddToPolygon(&cp, pts, &pn);
			}
		if((type &0xf) == 5) {
			target->fp2fip(Values+i-2, &fip);
			pn.x += (pn.x - iround(fip.fx))>>1;
			AddToPolygon(&cp, pts, &pn);
			}
		break;
	case 6:
		target->fp2fip(Values, &fip);
		pn.x = iround(fip.fx);		pn.y = iround(fip.fy);
		target->fp2fip(Values+1, &fip);
		pn.x += (pn.x - iround(fip.fx))>>1;
		AddToPolygon(&cp, pts, &pn);
	case 2:
		target->fp2fip(Values, &fip);
		pn.x = iround(fip.fx);		pn.y = iround(fip.fy);
		for (i = 0; i <= nPntSet; i++){
			target->fp2fip(Values+i, &fip);
			pn.y = iround(fip.fy);			AddToPolygon(&cp, pts, &pn);
			pn.x = iround(fip.fx);			AddToPolygon(&cp, pts, &pn);
			}
		if((type &0xf) == 6) {
			target->fp2fip(Values+i-2, &fip);
			pn.y += (pn.y - iround(fip.fy))>>1;
			AddToPolygon(&cp, pts, &pn);
			}
		break;
	case 7:
		target->fp2fip(Values, &fip);
		pn.x = iround(fip.fx);		pn.y = iround(fip.fy);
		target->fp2fip(Values+1, &fip);
		pn.x += (pn.x - iround(fip.fx))>>1;
		AddToPolygon(&cp, pts, &pn);
	case 3:
		target->fp2fip(Values, &fip);
		pn.x = iround(fip.fx);		pn.y = iround(fip.fy);
		for (i = 0; i <= nPntSet; i++){
			target->fp2fip(Values+i, &fip);
			pn.x = (pn.x + iround(fip.fx))>>1;		AddToPolygon(&cp, pts, &pn);
			pn.y = iround(fip.fy);				AddToPolygon(&cp, pts, &pn);
			pn.x = iround(fip.fx);
			}
		AddToPolygon(&cp, pts, &pn);
		if((type &0xf) == 7) {
			target->fp2fip(Values+i-2, &fip);
			pn.x += (pn.x - iround(fip.fx))>>1;
			AddToPolygon(&cp, pts, &pn);
			}
		break;
	case 8:
		target->fp2fip(Values, &fip);
		pn.x = iround(fip.fx);		pn.y = iround(fip.fy);
		target->fp2fip(Values+1, &fip);
		pn.y += (pn.y - iround(fip.fy))>>1;
		AddToPolygon(&cp, pts, &pn);
	case 4:
		target->fp2fip(Values, &fip);
		pn.x = iround(fip.fx);		pn.y = iround(fip.fy);
		for (i = 0; i <= nPntSet; i++){
			target->fp2fip(Values+i, &fip);
			pn.y = (pn.y + iround(fip.fy))>>1;		AddToPolygon(&cp, pts, &pn);
			pn.x = iround(fip.fx);				AddToPolygon(&cp, pts, &pn);
			pn.y = iround(fip.fy);
			}
		AddToPolygon(&cp, pts, &pn);
		if((type &0xf) == 8) {
			target->fp2fip(Values+i-2, &fip);
			pn.y += (pn.y - iround(fip.fy))>>1;
			AddToPolygon(&cp, pts, &pn);
			}
		break;
	case 9:		case 10:
		DrawSpline(target);
		break;
	case 11:	case 12:
		DrawCurve(target);
		break;
		}
	if(cp < 2) return;
	if(isPolygon) {			//for mark polygon only !!
		AddToPolygon(&cp, pts, pts);
		}
	else{
		target->SetLine(&LineDef);		target->oPolyline(pts, cp);
		}
	if(tmppts = (POINT*)realloc(pts, cp *sizeof(POINT))) pts = tmppts;
	SetMinMaxRect(&rDims, pts[0].x, pts[0].y, pts[1].x, pts[1].y);
	for(i = 2; i < cp; i++) UpdateMinMaxRect(&rDims, pts[i].x, pts[i].y);
	i = 2*target->un2ix(LineDef.width);		//increase size of rectangle for marks
	IncrementMinMaxRect(&rDims, i);
}

void
DataLine::DoMark(anyOutput *o, bool mark)
{
	if(pts && cp && o){
		if(mark){
			if(mo) DelBitmapClass(mo);				mo = 0L;
			memcpy(&mrc, &rDims, sizeof(RECT));
			IncrementMinMaxRect(&mrc, 6 + o->un2ix(LineDef.width));
			mo = GetRectBitmap(&mrc, o);
			InvertLine(pts, cp, &LineDef, &mrc, o, mark);
			}
		else if(mo) RestoreRectBitmap(&mo, &mrc, o);
		}
}

bool
DataLine::Command(int cmd, void *tmpl, anyOutput *o)
{
	MouseEvent *mev;
	bool bFound = false;
	POINT p1;
	int i;

	switch (cmd) {
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(!IsInRect(&rDims, p1.x= mev->x, p1.y= mev->y) || CurrGO || !o || nPntSet <1)
				return false; 
			if(isPolygon && IsInPolygon(&p1, pts, cp)) bFound = true;
			if(bFound || IsCloseToPL(p1,pts,cp)) 
				return o->ShowMark(this, MRK_GODRAW);
			}
		break;
	case CMD_SCALE:
		LineDef.width *= ((scaleINFO*)tmpl)->sy.fy;		LineDef.patlength *= ((scaleINFO*)tmpl)->sy.fy;
		break;
	case CMD_SET_DATAOBJ:
		Id = isPolygon ? GO_DATAPOLYGON : GO_DATALINE;
		data = (DataObj*)tmpl;
		return true;
	case CMD_MRK_DIRTY:
		dirty= true;
	case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, 0L);
		return false;
	case CMD_LEGEND:
		if(tmpl && ((GraphObj*)tmpl)->Id == GO_LEGEND) {
			if(Id == GO_DATALINE) ((Legend*)tmpl)->HasFill(&LineDef, 0L, name);
			}
		break;
	case CMD_SET_LINE:
		if(tmpl) memcpy(&LineDef, tmpl, sizeof(LineDEF));
		return true;
	case CMD_UPDATE:
		Undo.DataMem(this, (void**)&Values, nPnt * sizeof(lfPOINT), &nPnt, UNDO_CONTINUE);
		Undo.ValLong(this, &nPntSet, UNDO_CONTINUE);
		SetValues();
		return true;
	case CMD_AUTOSCALE:
		if(nPntSet < 1 || !Values) return false;
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH) {
			if(dirty) {
				min.fx = max.fx = Values[0].fx;	min.fy = max.fy = Values[0].fy;
				for (i = 1; i <= nPntSet; i++){
					min.fx = Values[i].fx < min.fx ? Values[i].fx : min.fx;
					max.fx = Values[i].fx > max.fx ? Values[i].fx : max.fx;
					min.fy = Values[i].fy < min.fy ? Values[i].fy : min.fy;
					max.fy = Values[i].fy > max.fy ? Values[i].fy : max.fy;
					}
				}
			((Plot*)parent)->CheckBounds(min.fx, min.fy);
			((Plot*)parent)->CheckBounds(max.fx, max.fy);
			dirty = false;
			return true;
			}
		return false;
		}
	return false;
}

void
DataLine::SetValues()
{
	AccRange *rX, *rY1=0L, *rY2=0L;
	int i, j, k, l, m, n;
	double x, y;
	char yref1[500], yref2[500];
	lfPOINT *tmpValues = Values;

	if(!ssXref || !ssYref) return;
	if(!rlp_strcpy(yref1, 500, ssYref)) return;
	for(i = 0, yref2[0] = 0; yref1[i]; i++) {
		if(yref1[i] == ';') {
			yref1[i++] = 0;
			while(yref1[i] && yref1[i] < 33) i++;
			rlp_strcpy(yref2, 500, yref1+i);
			}
		}
	nPnt = nPntSet = 0;
	min.fx = min.fy = HUGE_VAL;		max.fx = max.fy = -HUGE_VAL;
	rX = new AccRange(ssXref);		rY1 = new AccRange(yref1);
	if(!rX || !rY1){
		if(rX) delete(rX);	if(rY1) delete(rY1);
		return;
		}
	if(!name) name = rY1->RangeDesc(data, 1);
	if(yref2[0] &&((nPnt = rX->CountItems()) == (rY1->CountItems()))) {
		if(!(Values = (lfPOINT *)realloc(Values, ((nPnt*2+2) * sizeof(lfPOINT))))) return; 
		if(!(rY2 = new AccRange(yref2))) {
			if(yref1) free(yref1);		if(yref2) free(yref2);
			if(rX) delete(rX);	if(rY1) delete(rY1);
			return;
			}
		if(rX->GetFirst(&i, &j) && rY1->GetFirst(&k, &l) && 
			rX->GetNext(&i, &j) && rY1->GetNext(&k, &l) &&
			rY2->GetFirst(&m, &n) && rY2->GetNext(&m, &n)) do {
			if(data->GetValue(j, i, &x)){
				if(data->GetValue(l, k, &y)){
					Values[nPntSet].fx = x;			Values[nPntSet++].fy = y;
					}
				if(data->GetValue(n, m, &y)){
					Values[nPntSet].fx = x;			Values[nPntSet++].fy = y;
					}
				}
			}while(rX->GetNext(&i, &j) && rY1->GetNext(&k, &l) && rY2->GetNext(&m, &n));
		}
	else {
		if((nPnt = rX->CountItems()) != (rY1->CountItems())) return;
		if(!(Values = (lfPOINT *)realloc(Values, (nPnt+2) * sizeof(lfPOINT)))) return; 
		if(rX->GetFirst(&i, &j) && rY1->GetFirst(&k, &l) && 
			rX->GetNext(&i, &j) && rY1->GetNext(&k, &l)) do {
			if(data->GetValue(j, i, &x) && data->GetValue(l, k, &y)){
				Values[nPntSet].fx = x;				Values[nPntSet++].fy = y;
				}
			}while(rX->GetNext(&i, &j) && rY1->GetNext(&k, &l));
		}
	nPnt = nPntSet;		nPntSet--;	dirty = true;
	Command(CMD_AUTOSCALE, 0L, 0L);
	if(tmpValues && Values != tmpValues) Undo.InvalidGO(this);
	if(rX) delete(rX);	if(rY1) delete(rY1);	if(rY2) delete(rY2);
}

void
DataLine::LineData(lfPOINT *val, long nval)
{
	lfPOINT *ov = Values;

	if(!val || nval <2) return;
	if(nval > nPnt && nPnt > 1 && Values){
		if(!(Values = (lfPOINT *)realloc(Values, ((nval*2+2) * sizeof(lfPOINT))))) return; 
		if(ov != Values) Undo.InvalidGO(this);
		}
	else if(!Undo.busy) Undo.DataMem(this, (void**)&Values, nPnt * sizeof(lfPOINT), &nPnt, UNDO_CONTINUE);
	memcpy(Values, val, nval * sizeof(lfPOINT));
	if(pts) free(pts);			pts = 0L;					dirty = true;			
	free(val);					nPnt = nval;				nPntSet = nPnt-1;
}

void
DataLine::DrawCurve(anyOutput *target)
{
	lfPOINT *sdata, *bdata;
	POINT *tmppts;
	int i, j, n;

	if(!(sdata = (lfPOINT *)malloc(nPnt * sizeof(lfPOINT))))return;
	sdata[0].fx = Values[0].fx;				sdata[0].fy = Values[0].fy;
	for(i = j = 1; i < nPnt; i++) {
		if(Values[i].fx != sdata[j-1].fx || Values[i].fy != sdata[j-1].fy) {
			sdata[j].fx = Values[i].fx;		sdata[j++].fy = Values[i].fy;
			}
		}
	n = mkCurve(sdata, j, &bdata, (type&0x0f) != 11);
//	if(!(tmppts = (POINT*)malloc((n+2)*sizeof(POINT))))return;
	if(!(tmppts = (POINT*)malloc((n*64+2)*sizeof(POINT))))return;
	for(i = 0; i < n; i++){
		tmppts[i].x = target->fx2ix(bdata[i].fx);	tmppts[i].y = target->fy2iy(bdata[i].fy);
		}
	for(i = cp = 0; i< (n-2); i += 3) {
		if(parent->Id == GO_TICK) ClipBezier(&cp, pts, tmppts[i], tmppts[i+1], tmppts[i+2], tmppts[i+3], 0L, 0L);
		else DrawBezier(&cp, pts, tmppts[i], tmppts[i+1], tmppts[i+2], tmppts[i+3], 0);
		}
	if(bdata) free(bdata);	free(sdata);
}

void
DataLine::DrawSpline(anyOutput *target)
{
	int i, j, k, klo, khi, ptsize = 1000;
	double *y2, min, max, x, y, h, b, a;
	POINT pn;
	lfPOINT *scvals;
	
	if(!(y2 = (double*)malloc(sizeof(double)*(nPnt)))) return;
	if(!(scvals = (lfPOINT*)malloc(sizeof(lfPOINT)*(nPnt)))){
		free(y2);
		return;
		}
	if((type & 0x0f) == 9 || (type & 0x0f) == 10) {
		if((type & 0x0f) == 9) for(i = 0; i < nPnt; i++) {
			scvals[i].fx = target->fx2fix(Values[i].fx);
			scvals[i].fy = target->fy2fiy(Values[i].fy);
			}
		else for(i = 0; i < nPnt; i++) {
			scvals[i].fy = target->fx2fix(Values[i].fx);
			scvals[i].fx = target->fy2fiy(Values[i].fy);
			}
		SortFpArray(nPnt, scvals);
		min = scvals[0].fx;			max = scvals[nPnt-1].fx;
		for(i = j = 0; i < (nPnt-1); i++, j++) {
			y = scvals[i].fy;			scvals[j].fx = scvals[i].fx;
			for(k = 1; scvals[i+1].fx == scvals[i].fx; k++) {
				y += scvals[i+1].fy;		i++;
				}
			scvals[j].fy = y/((double)k);
			}
		if(scvals[i].fx > scvals[i-1].fx) {
			scvals[j].fx = scvals[i].fx;	scvals[j].fy = scvals[i].fy;
			j++;
			}
		spline(scvals, j, y2);
		h = scvals[1].fx - scvals[0].fx;	// klo and khi bracket the input value of x
		for(x = min, klo = 0, i = khi = 1; x < max && i < j; x += 1.0) {
			while(x > scvals[i].fx) {
				klo++;		khi++;	i++;
				h = scvals[khi].fx - scvals[klo].fx;
				}
			a = (scvals[khi].fx - x) / h;		b = (x - scvals[klo].fx) / h;
			y = a * scvals[klo].fy + b * scvals[khi].fy + ((a*a*a - a) * y2[klo] + (b*b*b - b) * y2[khi]) * (h*h)/6.0;
			if((type & 0x0f) == 9) {
				pn.x = iround(x);		pn.y = iround(y);
				}
			else {
				pn.x = iround(y);		pn.y = iround(x);
				}
			if(cp >= ptsize) {
				ptsize += 1000;
				pts = (POINT*)realloc(pts, sizeof(POINT)*ptsize); 
				}
			AddToPolygon(&cp, pts, &pn);
			}
		}
	free(y2);	free(scvals);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// DataPolygon is a graphic object based on DataLine
DataPolygon::DataPolygon(GraphObj *par, DataObj *d, char *xrange, char *yrange, char *nam):
	DataLine(par, d, xrange, yrange, nam)
{
	lfPOINT *fp = Values;
	char *rx = ssXref;
	char *ry = ssYref;
	long np = nPnt;

	FileIO(INIT_VARS);
	Values = fp;			//FileIO will just set Values to 0L !
	ssXref = rx;			ssYref = ry;
	nPnt = np;
	Id = GO_DATAPOLYGON;
}

DataPolygon::DataPolygon(GraphObj *par, DataObj *d, lfPOINT *val, long nval, char *na):
	DataLine(par, d, val, nval, 0L)
{
	int cb;

	FileIO(INIT_VARS);
	Values = val;			nPnt = nval;	nPntSet = nPnt-1;
	if(na && na[0] && (name = (char*)malloc((cb = (int)strlen(na))+2))) {
		rlp_strcpy(name, cb+1, na);
		}
	Id = GO_DATAPOLYGON;
}

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

DataPolygon::~DataPolygon()
{
	if(Values)free(Values);		Values =0L;
	if(pts) free (pts);		pts = 0L;
	if(ssXref) free(ssXref);	ssXref = 0L;
	if(ssYref) free(ssYref);	ssYref = 0L;
	if(mo) DelBitmapClass(mo);	mo = 0L;
	if(name) free(name);		name = 0;
	if(parent)parent->Command(CMD_MRK_DIRTY, 0L, 0L);
}

void
DataPolygon::DoPlot(anyOutput *o)
{
	LineDEF currLine;

	if(!Values || !o || nPntSet < 2) return;
	if(mo) DelBitmapClass(mo);	mo = 0L;
	if((type & 0x0f) != 12 && (type & 0x0f)) {		//close polygon if necessary
		if(Values[nPntSet].fx != Values[0].fx || Values[nPntSet].fy != Values[0].fy) {
			Values = (lfPOINT*)realloc(Values, (nPntSet+2) * sizeof(lfPOINT));
			Values[nPntSet+1].fx = Values[0].fx;	Values[nPntSet+1].fy = Values[0].fy;
			nPntSet++;	nPnt++;
			}
		}
	DataLine::DoPlot(o);			//no drawing but fill pts only
	memcpy(&currLine, &LineDef, sizeof(LineDEF));
	if(currLine.width < 1.0e-10) currLine.color = pgFill.color;
	o->SetLine(&currLine);		o->SetFill(&pgFill);
	o->oPolygon(pts, cp);
}

void
DataPolygon::DoMark(anyOutput *o, bool mark)
{
	if(pts && cp && o){
		if(mark){
			if(mo) DelBitmapClass(mo);			mo = 0L;
			memcpy(&mrc, &rDims, sizeof(RECT));
			IncrementMinMaxRect(&mrc, 6 + o->un2ix(LineDef.width));
			mo = GetRectBitmap(&mrc, o);
			InvertLine(pts, cp, &LineDef, &mrc, o, mark);
			}
		else RestoreRectBitmap(&mo, &mrc, o);
		}
}

bool
DataPolygon::Command(int cmd, void *tmpl, anyOutput *o)
{
	switch (cmd) {
	case CMD_PG_FILL:
		if(tmpl) {
			memcpy((void*)&pgFill, tmpl, sizeof(FillDEF));
			if(pgFill.hatch) memcpy((void*)&pgFillLine, (void*)pgFill.hatch, sizeof(LineDEF));
			pgFill.hatch = (LineDEF*)&pgFillLine;
			}
		return true;
	case CMD_SCALE:
		LineDef.width *= ((scaleINFO*)tmpl)->sy.fy;			LineDef.patlength *= ((scaleINFO*)tmpl)->sy.fy;
		pgFillLine.width *= ((scaleINFO*)tmpl)->sy.fy;		pgFillLine.patlength *= ((scaleINFO*)tmpl)->sy.fy;
		pgFill.scale *= ((scaleINFO*)tmpl)->sy.fy;
		break;
	case CMD_LEGEND:
		if(tmpl && ((GraphObj*)tmpl)->Id == GO_LEGEND) {
			if(Id == GO_DATAPOLYGON) ((Legend*)tmpl)->HasFill(&LineDef, &pgFill, name);
			}
		break;
	default:
		return DataLine::Command(cmd, tmpl, o);
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Calculate and display a regression line
// Ref.: "Biometry" third edition 1995 (ed. R.R. Sokal and F.J. Rohlf),
// W.H. Freeman and Company, New York; ISBN 0-7167-2411-1; pp. 451ff
RegLine::RegLine(GraphObj *par, DataObj *d, lfPOINT *values, long n, int sel):
	GraphObj(par, d)
{
	FileIO(INIT_VARS);
	type = sel;
	Id = GO_REGLINE;
	uclip.Xmin = uclip.Ymin = lim.Xmin = lim.Ymin = -1.0;
	uclip.Xmax = uclip.Ymax = lim.Xmax = lim.Ymax = 1.0;
	Recalc(values, n);
}

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

RegLine::~RegLine()
{
	if(pts) free(pts);		pts = 0L;
	if(parent)parent->Command(CMD_MRK_DIRTY, 0L, 0L);
}

double
RegLine::GetSize(int select)
{
	double a, b;

	switch(select) {
	case SIZE_MX:		return mx;
	case SIZE_MY:		return my;
	case SIZE_A:
	case SIZE_B:
		switch(type & 0x07) {
		case 1:		a = l2.fx;	b = l2.fy;	break;
		case 2:		a = l3.fx;	b = l3.fy;	break;
		case 3:		a = l4.fx;	b = l4.fy;	break;
		case 4:		a = l5.fx;	b = l5.fy;	break;
		default:	a = l1.fx;	b = l1.fy;	break;
			}
		if(select == SIZE_A) return a;
		else return b;
		}
	return 0.0;
}

void
RegLine::DoPlot(anyOutput *o)
{
	int i;
	POINT pn;
	double x, x1, y, d, a, b;
	fRECT cliprc;
	bool dValid;

	switch (type & 0x70) {
	case 0x20:	memcpy(&cliprc, &uclip, sizeof(fRECT));		break;
	case 0x10:
		if(parent) {
			cliprc.Xmin = parent->GetSize(SIZE_BOUNDS_LEFT);
			cliprc.Xmax = parent->GetSize(SIZE_BOUNDS_RIGHT);
			cliprc.Ymin = parent->GetSize(SIZE_BOUNDS_BOTTOM);
			cliprc.Ymax = parent->GetSize(SIZE_BOUNDS_TOP);
			break;
			}
		//no parent: use default
	default:	memcpy(&cliprc, &lim, sizeof(fRECT));		break;
		}
	if(cliprc.Xmax < cliprc.Xmin) {
		x = cliprc.Xmax;	cliprc.Xmax = cliprc.Xmin;	cliprc.Xmin = x;
		}
	if(cliprc.Ymax < cliprc.Ymin) {
		y = cliprc.Ymax;	cliprc.Ymax = cliprc.Ymin;	cliprc.Ymin = y;
		}
	if(cliprc.Xmin == cliprc.Xmax || cliprc.Ymin == cliprc.Ymax) return;
	if((!pts) && (!(pts = (POINT *)malloc(sizeof(POINT)*202))))return;
	switch(type & 0x07) {
	case 1:		a = l2.fx;	b = l2.fy;	break;
	case 2:		a = l3.fx;	b = l3.fy;	break;
	case 3:		a = l4.fx;	b = l4.fy;	break;
	case 4:		a = l5.fx;	b = l5.fy;	break;
	default:	a = l1.fx;	b = l1.fy;	break;
		}
	x = cliprc.Xmin;	d = (cliprc.Xmax - cliprc.Xmin)/200.0;
	for (cp = i = 0; i <= 200; i++){
		dValid = true;
		switch(type & 0x700) {
		case 0x100:						//logarithmic x
			if(dValid = x > defs.min4log) x1 = log10(x);
			break;
		case 0x200:						//reciprocal x
			if(dValid = fabs(x) > defs.min4log) x1 = 1.0/x;
			break;
		case 0x300:						//square root x
			if(dValid = fabs(x) > defs.min4log) x1 = sqrt(x);
			break;
		default:	x1 = x;	break;		//linear x
			}
		if(dValid) {
			y = a + b*x1;
			switch(type & 0x7000) {
			case 0x1000:				//logarithmic y
				y = pow(10.0, y);
				break;
			case 0x2000:				//reciprocal y
				if(dValid = fabs(y) >0.0001) y = 1.0/y;
				break;
			case 0x3000:				//square root y
				if(dValid = fabs(y) >0.0001) y = y*y;
				break;
				}
			if(y >= cliprc.Ymin && y <= cliprc.Ymax) {
				pn.x = o->fx2ix(x);	pn.y = o->fy2iy(y);
				AddToPolygon(&cp, pts, &pn);
				}
			}
		x += d;
		}
	if(cp < 2) return;
	o->SetLine(&LineDef);			o->oPolyline(pts, cp);
	SetMinMaxRect(&rDims, pts[0].x, pts[0].y, pts[1].x, pts[1].y);
	for(i = 2; i < cp; i++) UpdateMinMaxRect(&rDims, pts[i].x, pts[i].y);
	i = 2*o->un2ix(LineDef.width);		//increase size of rectangle for marks
	IncrementMinMaxRect(&rDims, i);
}

void
RegLine::DoMark(anyOutput *o, bool mark)
{
	if(pts && cp && o){
		if(mark)InvertLine(pts, cp, &LineDef, &rDims, o, mark);
		else if(parent) parent->Command(CMD_REDRAW, 0L, o);
		}
}

bool
RegLine::Command(int cmd, void *tmpl, anyOutput *o)
{
	MouseEvent *mev;
	POINT p1;

	switch (cmd) {
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(!IsInRect(&rDims, p1.x= mev->x, p1.y= mev->y) || CurrGO || !o || nPoints <2)
				return false; 
			if(IsCloseToPL(p1,pts,cp)) return o->ShowMark(CurrGO= this, MRK_GODRAW);
			}
		break;
	case CMD_SCALE:
		LineDef.width *= ((scaleINFO*)tmpl)->sy.fy;		LineDef.patlength *= ((scaleINFO*)tmpl)->sy.fy;
		break;
	case CMD_SET_DATAOBJ:
		Id = GO_REGLINE;
		return true;
	case CMD_BOUNDS:
		if(tmpl) {
			memcpy(&lim, tmpl, sizeof(fRECT));
			memcpy(&uclip, tmpl, sizeof(fRECT));
			}
		return true;
	case CMD_AUTOSCALE:
		if(nPoints < 2) return false;
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH) {
			((Plot*)parent)->CheckBounds(lim.Xmin, lim.Ymin);
			((Plot*)parent)->CheckBounds(lim.Xmax, lim.Ymax);
			return true;
			}
		return false;
		}
	return false;
}

void
RegLine::Recalc(lfPOINT *values, long n)
{
	double sx, sy, dx, dy, sxy, sxx, syy;
	double a, b, k;
	long ic;

	sx = sy = 0.0;
	if((nPoints = n)<2) return;
	for(ic = 0; ic < n; ic++) {
		sx += values[ic].fx;		sy += values[ic].fy;
		}
	mx = sx /((double)nPoints);	my = sy/((double)nPoints);
	sxy = sxx = syy = 0.0;
	for(ic = 0; ic < n; ic++) {
		dx = mx - values[ic].fx;	dy = my - values[ic].fy;
		sxx += (dx*dx);	syy += (dy*dy);	sxy += (dx*dy);
		}
	l1.fy = sxy / sxx;			l1.fx = my - (sxy / sxx) * mx;
	b = sxy / syy;				a = mx - (sxy / syy) * my;
	l2.fy = 1.0/b;				l2.fx = -a / b;
	l3.fy = (l1.fy+l2.fy)/2.0;	l3.fx = (l1.fx+l2.fx)/2.0;
	l4.fy = sy/sx;				l4.fx = 0.0;
	if(l5.fx == 0.0 && l5.fx == 0.0){
		l5.fy = l1.fy;				l5.fx = l1.fx;
		}
	//calculate distance point from line algorithm
	//Ref: K. Thompson, 1990: Vertical Distance from a Point to a Line. In:
	//   Graphic Gems (Andrew S. Glassner, ed.), Academic Press,
	//   pp. 47-48; ISBN 0-12-286165-5
	k = (sqrt(1.0/(1.0+l1.fy*l1.fy))+sqrt(1.0/(1.0+l2.fy*l2.fy)))/2.0;
	b = sqrt(1.0/(k*k) -1.0);
	l3.fy = l3.fy > 0.0 ? b : -b;
	l3.fx = my - mx * l3.fy;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Calculate and display a statnard deviation (SD-) ellipse
SDellipse::SDellipse(GraphObj *par, DataObj *d, lfPOINT *values, long n, int sel):
	GraphObj(par, d)
{
	FileIO(INIT_VARS);
	type = sel;
	Id = GO_SDELLIPSE;
	if(val = (lfPOINT*)malloc(n * sizeof(lfPOINT))){
		memcpy(val, values, (nPoints = n)*sizeof(lfPOINT));
		rl = new RegLine(this, data, values, n, type);
		}
}

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

SDellipse::~SDellipse()
{
	if(val) free(val);
	if(pts) free(pts);
	if(!(type & 0x10000) && parent && rl && 
		parent->Command(CMD_DROP_OBJECT, rl, 0L)) return;
	if(rl) DeleteGO(rl);
}

void
SDellipse::DoPlot(anyOutput *o)
{
	int i;
	double a1, b1, a2, b2, fv, k1, k2, ss1, ss2, np, x, dx, si, csi, fac, fac2;
	lfPOINT fp, fip;
	POINT p1, *tmppts;

	if(!rl) return;
	if(pts) free(pts);
	if(!(pts = (POINT *)malloc(sizeof(POINT)*420)))return;
	//get line data from regression line object
	mx = rl->GetSize(SIZE_MX);		my = rl->GetSize(SIZE_MY);
	a1 = rl->GetSize(SIZE_A);		b1 = rl->GetSize(SIZE_B);
	b2 = -1.0/b1;	a2 = my - b2 * mx;
	//calculate sine and cosine for back rotation
	fv = sqrt(1.0+b1*b1);			si = b1/fv;			csi = 1.0/fv;
	//calculate distance from line for each point and squared sum of distances
	//Ref: K. Thompson, 1990: Vertical Distance from a Point to a Line. In:
	//   Graphic Gems (Andrew S. Glassner, ed.), Academic Press,
	//   pp. 47-48; ISBN 0-12-286165-5
	k1 = sqrt(1.0/(1.0+b1*b1));			k2 = sqrt(1.0/(1.0+b2*b2));
	// y = a + b*x;
	ss1 = ss2 = 0.0;
	for(i = 0; i < nPoints; i++) {
		fv = (a1 + b1 * val[i].fx - val[i].fy) * k1;	ss1 += (fv*fv);
		fv = (a2 + b2 * val[i].fx - val[i].fy) * k2;	ss2 += (fv*fv);
		}
	np = ((double)(nPoints-1));
	//SD perpendicular and in direction of regression line
	sd1 = sqrt(ss1 /= np);		sd2 = sqrt(ss2 /= np);
	switch(type & 0x60000) {
		case 0x20000:		fac = 2.0;		break;
		case 0x40000:		fac = 3.0;		break;
		default:			fac = 1.0;		break;
		}
	fac2 = fac*fac;			dx = sd2/100.0*fac;
	for(i = 0, cp = 0, x = -(sd2*fac); i < 2; i++) {
		do {
			fv = (x*x)/(ss2*fac2);
			fv = fv < 0.99999 ? sqrt((1.0-fv)*ss1*fac2) : 0.0;
			fv = i ? fv : -fv;
			fp.fx = mx + x * csi - fv * si;
			fp.fy = my + x * si + fv * csi;
			switch(type & 0x700) {
			case 0x100:					//logarithmic x
				fp.fx = pow(10.0, fp.fx);
				break;
			case 0x200:					//reciprocal x
				if(fabs(fp.fx) > defs.min4log) fp.fx = 1.0/fp.fx;
				else fp.fx = 0.0;
				break;
			case 0x300:					//square root x
				if(fabs(fp.fx) > defs.min4log) fp.fx = fp.fx*fp.fx;
				else fp.fx = 0.0;
				break;
				}
			switch(type & 0x7000) {
			case 0x1000:				//logarithmic y
				fp.fy = pow(10.0, fp.fy);
				break;
			case 0x2000:				//reciprocal y
				if(fabs(fp.fy) > defs.min4log) fp.fy = 1.0/fp.fy;
				else fp.fy = 0.0;
				break;
			case 0x3000:				//square root y
				if(fabs(fp.fy) > defs.min4log) fp.fy = fp.fy*fp.fy;
				else fp.fy = 0.0;
				break;
				}
			o->fp2fip(&fp, &fip);	p1.x = iround(fip.fx);		p1.y = iround(fip.fy);
			AddToPolygon(&cp, pts, &p1);
			}while((x += dx) < (sd2*fac) && x > (-sd2*fac));
		x = sd2*fac;
		dx *= -1.0;
		}
	o->SetLine(&LineDef);
	if(cp > 2) {
		AddToPolygon(&cp, pts, pts);		//close polygon
		if(tmppts = (POINT*)realloc(pts, cp *sizeof(POINT))) pts = tmppts;
		SetMinMaxRect(&rDims, pts[0].x, pts[0].y, pts[1].x, pts[1].y);
		for(i = 2; i < cp; i++) 
			UpdateMinMaxRect(&rDims, pts[i].x, pts[i].y);
		i = 3*o->un2ix(LineDef.width);		//increase size of rectangle for marks
		IncrementMinMaxRect(&rDims, i);
		o->oPolyline(pts, cp);
		}
	else {
		free(pts);
		cp = 0;
		}
	if(!(type & 0x10000))rl->DoPlot(o);
}

void
SDellipse::DoMark(anyOutput *o, bool mark)
{
	if(pts && cp && o){
		if(mark)InvertLine(pts, cp, &LineDef, &rDims, o, mark);
		else if(parent) parent->Command(CMD_REDRAW, 0L, o);
		}
}

bool
SDellipse::Command(int cmd, void *tmpl, anyOutput *o)
{
	MouseEvent *mev;
	POINT p1;

	switch (cmd) {
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(!(type & 0x10000) && rl && rl->Command(cmd, tmpl, o)) return true;
			if(!IsInRect(&rDims, p1.x= mev->x, p1.y= mev->y) || CurrGO || !o || nPoints <2)
				return false; 
			if(IsCloseToPL(p1,pts,cp)) return o->ShowMark(CurrGO= this, MRK_GODRAW);
			}
		break;
	case CMD_SCALE:
		LineDef.width *= ((scaleINFO*)tmpl)->sy.fy;		LineDef.patlength *= ((scaleINFO*)tmpl)->sy.fy;
		if(rl) rl->Command(cmd, tmpl, o);
		break;
	case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_RMU:
		if(!(type & 0x10000) && parent && rl && parent->Command(CMD_DROP_OBJECT, rl, o)){
			rl = 0L;
			return true;
			}
		return false;
	case CMD_INIT:
		if(rl) return rl->PropertyDlg();
		break;
	case CMD_DROP_OBJECT:
		if(tmpl && ((GraphObj*)tmpl)->Id == GO_REGLINE && !rl) {
			rl = (RegLine *)tmpl;
			rl->parent = this;
			return true;
			}
		return false;
	case CMD_SET_DATAOBJ:
		Id = GO_SDELLIPSE;
		if(rl) rl->Command(cmd, tmpl, o);
		return true;
	case CMD_BOUNDS:
		if(tmpl) {
			if(rl) rl->Command(cmd, tmpl, o);
			memcpy(&lim, tmpl, sizeof(fRECT));
			}
		return true;
	case CMD_DELOBJ:
		if(tmpl && tmpl == (void*)rl) {
			Undo.ValInt(parent, &type, 0L);
			type |= 0x10000;
			if(parent) parent->Command(CMD_REDRAW, 0L, o);
			return true;
			}
		break;
	case CMD_AUTOSCALE:
		if(nPoints < 2) return false;
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH) {
			((Plot*)parent)->CheckBounds(lim.Xmin, lim.Ymin);
			((Plot*)parent)->CheckBounds(lim.Xmax, lim.Ymax);
			return true;
			}
		break;
		}
	return false;
}

void
SDellipse::Recalc(lfPOINT *values, long n)
{
	if(val) free(val);
	if(val = (lfPOINT*)malloc(n * sizeof(lfPOINT)))
		memcpy(val, values, (nPoints = n)*sizeof(lfPOINT));
	if(rl) rl->Recalc(values, n);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Error bars are simple graphic objects
ErrorBar::ErrorBar(GraphObj *par, DataObj *d, double x, double y, double err, int which,
	int xc, int xr, int yc, int yr, int ec, int er):GraphObj(par, d)
{
	FileIO(INIT_VARS);
	fPos.fx = x;		fPos.fy = y;
	ferr = err;			type = which;
	Id = GO_ERRBAR;		data = d;
	if(xc >= 0 || xr >= 0 || yc >= 0 || yr >= 0 || ec >= 0 || er >= 0) {
		if(ssRef = (POINT*)malloc(sizeof(POINT)*3)) {
			ssRef[0].x = xc;	ssRef[0].y = xr;
			ssRef[1].x = yc;	ssRef[1].y = yr;
			ssRef[2].x = ec;	ssRef[2].y = er;
			cssRef = 3;
			}
		}
	Command(CMD_AUTOSCALE, 0L, 0L);
}

ErrorBar::ErrorBar(int src):GraphObj(0L, 0L)
{
	FileIO(INIT_VARS);
	type = ERRBAR_VSYM;
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
}

ErrorBar::~ErrorBar()
{
	if(mo) DelBitmapClass(mo);	mo = 0L;
	if(ssRef) free(ssRef);		ssRef = 0L;
	if(name) free(name);		name = 0L;
}

bool
ErrorBar::SetSize(int select, double value)
{
	switch(select & 0xfff) {
	case SIZE_ERRBAR: 
		SizeBar = value;
		return true;
	case SIZE_ERRBAR_LINE:
		ErrLine.width = value;
		return true;
		}
	return false;
}

bool
ErrorBar::SetColor(int select, DWORD col)
{
	switch(select & 0xfff) {
	case COL_ERROR_LINE:
		ErrLine.color = col;
		return true;
		}
	return false;
}

void
ErrorBar::DoPlot(anyOutput *target)
{
	int ie;

	switch (type & 0x0ff) {
	case ERRBAR_VSYM:
	case ERRBAR_VUP:
	case ERRBAR_VDOWN:
		ie = target->un2ix(SizeBar/2.0);
		break;
	default:
		ie = target->un2iy(SizeBar/2.0);
		break;
		}
	target->SetLine(&ErrLine);
	switch(type) {
	case ERRBAR_VSYM:
		ebpts[0].x = ebpts[1].x = target->fx2ix(fPos.fx);
		ebpts[0].y = target->fy2iy(fPos.fy-ferr);
		ebpts[4].y = ebpts[5].y = ebpts[1].y = target->fy2iy(fPos.fy+ferr);
		if(ebpts[1].y != ebpts[0].y) target->oSolidLine(ebpts);
		ebpts[4].x = ebpts[2].x = ebpts[0].x - ie;
		ebpts[5].x = ebpts[3].x = ebpts[1].x + ie+1;
		ebpts[2].y = ebpts[3].y = ebpts[0].y;
		if(ebpts[3].x > ebpts[2].x) {
			target->oSolidLine(ebpts+2);
			target->oSolidLine(ebpts+4);
			}
		rDims.left =  ebpts[2].x;		rDims.right = ebpts[3].x;
		rDims.top = ebpts[0].y;			rDims.bottom = ebpts[1].y;
		break;
	case ERRBAR_VUP:
	case ERRBAR_VDOWN:
		ebpts[0].x = ebpts[1].x = target->fx2ix(fPos.fx);
		ebpts[0].y = target->fy2iy(fPos.fy);
		ebpts[2].y = ebpts[3].y = ebpts[1].y = 
			target->fy2iy(type == ERRBAR_VUP ? fPos.fy+ferr : fPos.fy-ferr);
		if(ebpts[1].y != ebpts[0].y) target->oSolidLine(ebpts);
		ebpts[2].x = ebpts[0].x - ie;
		ebpts[3].x = ebpts[1].x + ie+1;
		if(ebpts[3].x > ebpts[2].x) target->oSolidLine(ebpts+2);
		rDims.left =  ebpts[2].x;		rDims.right = ebpts[3].x;
		rDims.top = ebpts[0].y;			rDims.bottom = ebpts[1].y;
		break;
	case ERRBAR_HSYM:
		ebpts[2].x = ebpts[3].x = ebpts[0].x = target->fx2ix(fPos.fx-ferr);
		ebpts[4].x = ebpts[5].x = ebpts[1].x = target->fx2ix(fPos.fx+ferr);
		ebpts[0].y = ebpts[1].y = target->fy2iy(fPos.fy);
		if(ebpts[1].x != ebpts[0].x) target->oSolidLine(ebpts);
		ebpts[2].y = ebpts[4].y = ebpts[0].y - ie;
		ebpts[3].y = ebpts[5].y = ebpts[1].y + ie+1;
		if(ebpts[3].y >ebpts[2].y) {
			target->oSolidLine(ebpts+2);
			target->oSolidLine(ebpts+4);
			}
		rDims.left =  ebpts[0].x;		rDims.right = ebpts[1].x;
		rDims.top = ebpts[2].y;			rDims.bottom = ebpts[3].y;
		break;
	case ERRBAR_HLEFT:
	case ERRBAR_HRIGHT:
		ebpts[0].x = target->fx2ix(fPos.fx);
		ebpts[0].y = ebpts[1].y = target->fy2iy(fPos.fy);
		ebpts[2].x = ebpts[3].x = ebpts[1].x = 
			target->fx2ix(type == ERRBAR_HRIGHT ? fPos.fx+ferr : fPos.fx-ferr);
		if(ebpts[1].x != ebpts[0].x) target->oSolidLine(ebpts);
		ebpts[2].y = ebpts[0].y - ie;
		ebpts[3].y = ebpts[1].y + ie+1;
		if(ebpts[3].y > ebpts[2].y) target->oSolidLine(ebpts+2);
		rDims.left =  ebpts[0].x;		rDims.right = ebpts[1].x;
		rDims.top = ebpts[2].y;			rDims.bottom = ebpts[3].y;
		break;
		}
	if(rDims.left > rDims.right) Swap(rDims.left, rDims.right);
	if(rDims.top > rDims.bottom) Swap(rDims.top, rDims.bottom);
	IncrementMinMaxRect(&rDims, 2);
}

void
ErrorBar::DoMark(anyOutput *o, bool mark)
{
	int i;
	LineDEF OldLine;

	if(mark){
		if(mo) DelBitmapClass(mo);			mo = 0L;
		memcpy(&mrc, &rDims, sizeof(RECT));
		memcpy(&OldLine, &ErrLine, sizeof(LineDEF));
		i = 3*o->un2ix(ErrLine.width);		//increase size of rectangle for marks
		IncrementMinMaxRect(&mrc, i);		mo = GetRectBitmap(&mrc, o);
		ErrLine.width *= 5.0;				DoPlot(o);
		ErrLine.width = OldLine.width;		ErrLine.color = OldLine.color ^ 0x00ffffffL;
		DoPlot(o);							o->UpdateRect(&mrc, false);
		memcpy(&ErrLine, &OldLine, sizeof(LineDEF));
		}
	else if(mo) RestoreRectBitmap(&mo, &mrc, o);
}

bool
ErrorBar::Command(int cmd, void *tmpl, anyOutput *o)
{
	MouseEvent *mev;
	bool bFound;
	int cb;

	switch (cmd) {
	case CMD_SCALE:
		ErrLine.width *= ((scaleINFO*)tmpl)->sy.fy;		ErrLine.patlength *= ((scaleINFO*)tmpl)->sy.fy;
		SizeBar *= ((scaleINFO*)tmpl)->sy.fy;
		return true;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		bFound = false;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(!IsInRect(&rDims, mev->x, mev->y) || CurrGO) return false; 
			switch (type) {
			case ERRBAR_HSYM:		case ERRBAR_HLEFT:		case ERRBAR_HRIGHT:
				if(mev->y >= (ebpts[0].y-2) && mev->y <= (ebpts[1].y+2)) bFound = true;
				else if(mev->x >= (ebpts[2].x-2) && mev->x <= (ebpts[2].x+2)) bFound = true;
				else if(type == ERRBAR_HSYM && mev->x >= (ebpts[4].x-2) &&
					mev->x <= (ebpts[4].x + 2)) bFound = true;
				break;
			case ERRBAR_VSYM:		case ERRBAR_VUP:		case ERRBAR_VDOWN:
				if(mev->x >= (ebpts[0].x-2) && mev->x <= (ebpts[1].x+2)) bFound = true;
				else if(mev->y >= (ebpts[2].y-2) && mev->y <= (ebpts[2].y+2)) bFound = true;
				else if(type == ERRBAR_VSYM && mev->y >= (ebpts[4].y-2) &&
					mev->y <= (ebpts[4].y + 2)) bFound = true;
				break;
				}
			if(bFound) o->ShowMark(this, MRK_GODRAW);
			}
		return false;
	case CMD_SET_DATAOBJ:
		Id = GO_ERRBAR;
		data = (DataObj *) tmpl;
		return true;
	case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_UPDATE:
		if(ssRef && cssRef >2 && data) {
			data->GetValue(ssRef[0].y, ssRef[0].x, &fPos.fx);
			data->GetValue(ssRef[1].y, ssRef[1].x, &fPos.fy);
			data->GetValue(ssRef[2].y, ssRef[2].x, &ferr);
			return true;
			}
		return false;
	case CMD_LEGEND:
		if(!tmpl || ((GraphObj*)tmpl)->Id != GO_LEGEND) return false;
		switch(type) {
			case ERRBAR_VSYM:		case ERRBAR_VUP:		case ERRBAR_VDOWN:
				((Legend*)tmpl)->HasErr(&ErrLine, 1, name);
				break;
			case ERRBAR_HSYM:		case ERRBAR_HLEFT:		case ERRBAR_HRIGHT:
				((Legend*)tmpl)->HasErr(&ErrLine, 2, name);
				break;
			}
		break;
	case CMD_ERRDESC:
		if(tmpl && *((char *)tmpl)){
			cb = (int)strlen((char*)tmpl)+2;
			if(name = (char*)realloc(name, cb)) rlp_strcpy(name, cb, (char*)tmpl);
			return true;
			}
		return false;
	case CMD_ERR_TYPE:
		if(tmpl) type = *((int*)tmpl);
		return true;
	case CMD_AUTOSCALE:
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH) {
			switch(type) {
			case ERRBAR_VSYM:	
				((Plot*)parent)->CheckBounds(fPos.fx, fPos.fy+ferr);
				((Plot*)parent)->CheckBounds(fPos.fx, fPos.fy-ferr);		break;
			case ERRBAR_VUP:	
				((Plot*)parent)->CheckBounds(fPos.fx, fPos.fy);
				((Plot*)parent)->CheckBounds(fPos.fx, fPos.fy+ferr);		break;
			case ERRBAR_VDOWN:
				((Plot*)parent)->CheckBounds(fPos.fx, fPos.fy);
				((Plot*)parent)->CheckBounds(fPos.fx, fPos.fy-ferr);		break;
			case ERRBAR_HSYM:
				((Plot*)parent)->CheckBounds(fPos.fx+ferr, fPos.fy);
				((Plot*)parent)->CheckBounds(fPos.fx-ferr, fPos.fy);		break;
			case ERRBAR_HLEFT:
				((Plot*)parent)->CheckBounds(fPos.fx-ferr, fPos.fy);
				((Plot*)parent)->CheckBounds(fPos.fx, fPos.fy);				break;
			case ERRBAR_HRIGHT:
				((Plot*)parent)->CheckBounds(fPos.fx+ferr, fPos.fy);
				((Plot*)parent)->CheckBounds(fPos.fx, fPos.fy);				break;
				}
			return true;
			}
		break;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// arrows to data points or with absolute coordinates
Arrow::Arrow(GraphObj * par, DataObj *d, lfPOINT fp1, lfPOINT fp2, int which,
	int xc1, int xr1, int yc1, int yr1, int xc2, int xr2, int yc2, int yr2):
	GraphObj(par, d)
{
	double dx, dy;

	FileIO(INIT_VARS);
	memcpy(&pos1, &fp1, sizeof(lfPOINT));
	memcpy(&pos2, &fp2, sizeof(lfPOINT));
	type = which;
	if(type & ARROW_UNITS) {
		dx = parent->GetSize(SIZE_GRECT_LEFT);	dy = parent->GetSize(SIZE_GRECT_TOP);
		pos1.fx -= dx;	pos1.fy -= dy;			pos2.fx -= dx;	pos2.fy -= dy;
		}
	if(xc1 >= 0 || xr1 >= 0 || yc1 >= 0 || yr1 >= 0 || xc2 >= 0 || xr2 >= 0 || 
		yc2 >= 0 || yr2 >= 0) {
		if(ssRef = (POINT*)malloc(sizeof(POINT)*4)) {
			ssRef[0].x = xc1;	ssRef[0].y = xr1;
			ssRef[1].x = yc1;	ssRef[1].y = yr1;
			ssRef[2].x = xc2;	ssRef[2].y = xr2;
			ssRef[3].x = yc2;	ssRef[3].y = yr2;
			cssRef = 4;
			}
		}
	bModified = false;
}

Arrow::Arrow(int src):GraphObj(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
	bModified = false;
}

Arrow::~Arrow()
{
	Command(CMD_FLUSH, 0L, 0L);
	if(bModified) Undo.InvalidGO(this);
}

double
Arrow::GetSize(int select)
{
	switch(select) {
	case SIZE_XPOS:		return pos1.fx;
	case SIZE_XPOS+1:	return pos2.fx;
	case SIZE_YPOS:		return pos1.fy;
	case SIZE_YPOS+1:	return pos2.fy;
	case SIZE_GRECT_LEFT:	case SIZE_GRECT_TOP:
	case SIZE_GRECT_RIGHT:	case SIZE_GRECT_BOTTOM:
		if(parent) return parent->GetSize(select);
		break;
		}
	return 0.0;
}

bool
Arrow::SetSize(int select, double value)
{
	switch(select & 0xfff) {
	case SIZE_ARROW_LINE:		LineDef.width = value;		return true;
	case SIZE_ARROW_CAPWIDTH:	cw = value;					return true;
	case SIZE_ARROW_CAPLENGTH:	cl = value;					return true;
	case SIZE_XPOS:				pos1.fx = value;			return true;
	case SIZE_XPOS+1:			pos2.fx = value;			return true;
	case SIZE_YPOS:				pos1.fy = value;			return true;
	case SIZE_YPOS+1:			pos2.fy = value;			return true;
		}
	return false;
}

bool
Arrow::SetColor(int select, DWORD col)
{
	switch(select & 0xfff) {
	case COL_ARROW:
		LineDef.color = col;
		return true;
		}
	return false;
}

void
Arrow::DoPlot(anyOutput *o)
{
	double si, csi, tmp, fix1, fiy1, fix2, fiy2, dx, dy;

	if(!o || !parent) return;
	if(type & ARROW_UNITS) {
		dx = parent->GetSize(SIZE_GRECT_LEFT);		dy = parent->GetSize(SIZE_GRECT_TOP);
		fix1 = o->co2fix(pos1.fx+dx);	fix2 = o->co2fix(pos2.fx+dx);
		fiy1 = o->co2fiy(pos1.fy+dy);	fiy2 = o->co2fiy(pos2.fy+dy);
		}
	else {
		fix1 = o->fx2fix(pos1.fx);		fix2 = o->fx2fix(pos2.fx);
		fiy1 = o->fy2fiy(pos1.fy);		fiy2 = o->fy2fiy(pos2.fy);
		}
	if(fix1 == fix2 && fiy1 == fiy2) return;	//zero length
	//draw arrow line
	pts[0].x = iround(fix1);		pts[1].x = iround(fix2);
	pts[0].y = iround(fiy1);		pts[1].y = iround(fiy2);
	SetMinMaxRect(&rDims, pts[0].x, pts[0].y, pts[1].x, pts[1].y);
	//calculate sine and cosine for cap
	si = fiy1-fiy2;
	tmp = fix2 - fix1;
	si = si/sqrt(si*si + tmp*tmp);
	csi = fix2-fix1;
	tmp = fiy2 - fiy1;
	csi = csi/sqrt(csi*csi + tmp*tmp);
	//draw cap
	pts[2].x = pts[1].x - o->un2ix(csi*cl + si*cw/2.0);
	pts[2].y = pts[1].y + o->un2iy(si*cl - csi*cw/2.0);
	pts[3].x = pts[1].x;		pts[3].y = pts[1].y;
	pts[4].x = pts[1].x - o->un2ix(csi*cl - si*cw/2.0);
	pts[4].y = pts[1].y + o->un2iy(si*cl + csi*cw/2.0);
	switch(type & 0xff) {
	case ARROW_NOCAP:
		pts[2].x = pts[3].x = pts[4].x = pts[1].x;
		pts[2].y = pts[3].y = pts[4].y = pts[1].y;
		break;
		}
	UpdateMinMaxRect(&rDims, pts[2].x, pts[2].y);
	UpdateMinMaxRect(&rDims, pts[4].x, pts[4].y);
	IncrementMinMaxRect(&rDims, 3*o->un2ix(LineDef.width)+3);
	Redraw(o);
}

void
Arrow::DoMark(anyOutput *o, bool mark)
{
	LineDEF OldLine;

	if(type & ARROW_UNITS) {
		if(!dh1) dh1 = new dragHandle(this, DH_12);
		if(!dh2) dh2 = new dragHandle(this, DH_22);
		}
	else {
		if (dh1) DeleteGO(dh1);		if (dh2) DeleteGO(dh2);
		dh1 = dh2 = 0L;
		}
	if(mark) {
		if(mo) DelBitmapClass(mo);					mo = 0L;
		if(dh1 && dh2) {
			memcpy(&mrc, &rDims, sizeof(RECT));		memcpy(&OldLine, &LineDef, sizeof(LineDEF));
			mo = GetRectBitmap(&mrc, o);			Redraw(o);
			dh1->DoPlot(o);		dh2->DoPlot(o);
			}
		else {
			memcpy(&mrc, &rDims, sizeof(RECT));		memcpy(&OldLine, &LineDef, sizeof(LineDEF));
			mo = GetRectBitmap(&mrc, o);			LineDef.color = 0x00000000L;
			LineDef.width = OldLine.width *3.0;		Redraw(o);
			LineDef.width = OldLine.width;			LineDef.color = OldLine.color ^ 0x00ffffffL;
			Redraw(o);								o->UpdateRect(&mrc, false);
			memcpy(&LineDef, &OldLine, sizeof(LineDEF));
			}
		}
	else if(mo) RestoreRectBitmap(&mo, &mrc, o);
}

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

	switch (cmd) {
	case CMD_SAVEPOS:
		bModified = true;
		Undo.SaveLFP(this, &pos1, 0L);
		Undo.SaveLFP(this, &pos2, UNDO_CONTINUE);
		return true;
	case CMD_SCALE:
		LineDef.width *= ((scaleINFO*)tmpl)->sy.fy;		LineDef.patlength *= ((scaleINFO*)tmpl)->sy.fy;
		cw *= ((scaleINFO*)tmpl)->sy.fy;				cl *= ((scaleINFO*)tmpl)->sy.fy;
		if(type & ARROW_UNITS) {
			pos1.fx = ((scaleINFO*)tmpl)->sx.fx + pos1.fx * ((scaleINFO*)tmpl)->sx.fy;
			pos1.fy = ((scaleINFO*)tmpl)->sy.fx + pos1.fy * ((scaleINFO*)tmpl)->sy.fy;
			pos2.fx = ((scaleINFO*)tmpl)->sx.fx + pos2.fx * ((scaleINFO*)tmpl)->sx.fy;
			pos2.fy = ((scaleINFO*)tmpl)->sy.fx + pos2.fy * ((scaleINFO*)tmpl)->sy.fy;
			}
		return true;
	case CMD_FLUSH:
		if (dh1) DeleteGO(dh1);			dh1 = 0L;
		if (dh2) DeleteGO(dh2);			dh2 = 0L;
		if(mo) DelBitmapClass(mo);		mo = 0L;
		if(ssRef) free(ssRef);			ssRef = 0L;
		if(name) free(name);			name = 0L;
		return true;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
		if(!CurrGO && ObjThere(mev->x, mev->y)){
			o->ShowMark(this, MRK_GODRAW);
			return true;
			}
		}
		break;
	case CMD_ARROW_ORG:
		memcpy(&pos1, tmpl, sizeof(lfPOINT));
		if(ssRef && cssRef >3) 
			ssRef[0].x = ssRef[0].y = ssRef[1].x = ssRef[1].y = -1;
		return true;
	case CMD_ARROW_TYPE:
		if(tmpl) {
			type &= ~0xff;		type |= (*((int*)tmpl));
			return true;
			}
		return false;
	case CMD_SET_DATAOBJ:
		Id = GO_ARROW;
		data = (DataObj *)tmpl;
		return true;
	case CMD_UPDATE:
		if(ssRef && cssRef >3 && data) {
			data->GetValue(ssRef[0].y, ssRef[0].x, &pos1.fx);
			data->GetValue(ssRef[1].y, ssRef[1].x, &pos1.fy);
			data->GetValue(ssRef[2].y, ssRef[2].x, &pos2.fx);
			data->GetValue(ssRef[3].y, ssRef[3].x, &pos2.fy);
			return true;
			}
		return false;
	case CMD_AUTOSCALE:
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH) {
			((Plot*)parent)->CheckBounds(pos1.fx, pos1.fy);
			((Plot*)parent)->CheckBounds(pos2.fx, pos2.fy);
			return true;
			}
		break;
	case CMD_MRK_DIRTY:			//from Undo ?
	case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_MOVE:
		bModified = true;
	case CMD_UNDO_MOVE:
		if(type & ARROW_UNITS) {
			if(cmd == CMD_MOVE) Undo.MoveObj(this, (lfPOINT*)tmpl, 0L);
			pos1.fx += ((lfPOINT*)tmpl)[0].fx;	pos1.fy += ((lfPOINT*)tmpl)[0].fy;
			pos2.fx += ((lfPOINT*)tmpl)[0].fx;	pos2.fy += ((lfPOINT*)tmpl)[0].fy;
			if(o){
				o->StartPage();		parent->DoPlot(o);		o->EndPage();
				}
			return true;
			}
		break;
		}
	return false;
}

void
Arrow::Redraw(anyOutput *o)
{
	FillDEF FillCap;

	o->SetLine(&LineDef);
	o->oSolidLine(pts);
	switch(type & 0xff) {
	case ARROW_NOCAP:
		break;
	case ARROW_LINE:
		o->oSolidLine(pts+2);
		o->oSolidLine(pts+3);
		break;
	case ARROW_TRIANGLE:
		FillCap.type = FILL_NONE;
		FillCap.color = LineDef.color;
		FillCap.scale = 1.0f;
		FillCap.hatch = 0L;
		o->SetFill(&FillCap);
		o->oPolygon(pts+2, 3);
		break;
		}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// universal boxes
Box::Box(GraphObj * par, DataObj *d, lfPOINT fp1, lfPOINT fp2, int which,
	int xc1, int xr1, int yc1, int yr1, int xc2, int xr2,
		int yc2, int yr2):GraphObj(par, d)
{
	FileIO(INIT_VARS);
	memcpy(&pos1, &fp1, sizeof(lfPOINT));
	memcpy(&pos2, &fp2, sizeof(lfPOINT));
	type = which;
	Id = GO_BOX;
	if(xc1 >= 0 || xr1 >= 0 || yc1 >= 0 || yr1 >= 0 || xc2 >= 0 || xr2 >= 0 || 
		yc2 >= 0 || yr2 >= 0) {
		if(ssRef = (POINT*)malloc(sizeof(POINT)*4)) {
			ssRef[0].x = xc1;	ssRef[0].y = xr1;
			ssRef[1].x = yc1;	ssRef[1].y = yr1;
			ssRef[2].x = xc2;	ssRef[2].y = xr2;
			ssRef[3].x = yc2;	ssRef[3].y = yr2;
			cssRef = 4;
			}
		}
}

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

Box::~Box()
{
	Command(CMD_FLUSH, 0L, 0L);
}

double
Box::GetSize(int select)
{
	switch(select) {
	case SIZE_XPOS:
		return (pos1.fx + pos2.fx)/2.0;
	case SIZE_YPOS:
		return (pos1.fy + pos2.fy)/2.0;
		}
	return 1.0;
}

bool
Box::SetSize(int select, double value)
{
	switch(select & 0xfff) {
	case SIZE_BOX_LINE:		Outline.width = value;		return true;
	case SIZE_BOX:			size = value;				return true;
	case SIZE_XPOS:			pos1.fx = value;			return true;
	case SIZE_XPOS+1:		pos2.fx = value;			return true;
	case SIZE_YPOS:			pos1.fy = value;			return true;
	case SIZE_YPOS+1:		pos2.fy = value;			return true;
		}
	return false;
}

bool
Box::SetColor(int select, DWORD col)
{
	switch(select & 0xfff) {
	case COL_BOX_LINE:
		Outline.color = col;
		return true;
		}
	return false;
}

void
Box::DoPlot(anyOutput *o)
{
	double si, csi, tmp, fix1, fiy1, fix2, fiy2, fsize, dx, dy;

	if(!parent || !o || size <= 0.001) return;
	o->SetLine(&Outline);		o->SetFill(&Fill);
	if(mo) DelBitmapClass(mo);	mo = 0L;
	//calculate coordinates
	fix1 = o->fx2fix(pos1.fx);	fix2 = o->fx2fix(pos2.fx);
	fiy1 = o->fy2fiy(pos1.fy);	fiy2 = o->fy2fiy(pos2.fy);
	//calculate sine and cosine
	si = fiy1-fiy2;
	tmp = fix2 - fix1;
	si = si/sqrt(si*si + tmp*tmp);
	csi = fix2-fix1;
	tmp = fiy2 - fiy1;
	csi = csi/sqrt(csi*csi + tmp*tmp);
	if(type & BAR_WIDTHDATA) {			//use e.g. for density distribution
		dx = si * size;					dy = csi * size;
		pts[0].x = o->fx2ix(pos1.fx + dx);	pts[1].x = o->fx2ix(pos2.fx + dx);
		pts[2].x = o->fx2ix(pos2.fx - dx);	pts[3].x = o->fx2ix(pos1.fx - dx);
		pts[0].y = o->fy2iy(pos1.fy + dy);	pts[1].y = o->fy2iy(pos2.fy + dy);
		pts[2].y = o->fy2iy(pos2.fy - dy);	pts[3].y = o->fy2iy(pos1.fy - dy);
		}
	else if(type & BAR_RELWIDTH) {
		if(!parent || (pos1.fy == pos2.fy && pos1.fx == pos2.fx)) return;
		fsize = parent->GetSize(pos1.fy == pos2.fy ? SIZE_BOXMINY : SIZE_BOXMINX);
		fsize = fsize * size /200.0;
		dx = si * fsize;					dy = csi * fsize;
		pts[0].x = o->fx2ix(pos1.fx + dx);	pts[1].x = o->fx2ix(pos2.fx + dx);
		pts[2].x = o->fx2ix(pos2.fx - dx);	pts[3].x = o->fx2ix(pos1.fx - dx);
		pts[0].y = o->fy2iy(pos1.fy + dy);	pts[1].y = o->fy2iy(pos2.fy + dy);
		pts[2].y = o->fy2iy(pos2.fy - dy);	pts[3].y = o->fy2iy(pos1.fy - dy);
		}
	else {
		dx = o->un2fix(si*size/2.0);		dy = o->un2fiy(csi*size/2.0);
		pts[0].x = iround(fix1 + dx);	pts[1].x = iround(fix2 + dx);
		pts[2].x = iround(fix2 - dx);	pts[3].x = iround(fix1 - dx);
		pts[0].y = iround(fiy1 + dy);	pts[1].y = iround(fiy2 + dy);
		pts[2].y = iround(fiy2 - dy);	pts[3].y = iround(fiy1 - dy);
		}
	pts[4].x = pts[0].x;		pts[4].y = pts[0].y;	//close polygon
	if(pts[0].x == pts[1].x){
		o->oRectangle(pts[3].x, pts[3].y, pts[1].x, pts[1].y, name);
		}
	else {
		o->oPolygon(pts, 5);
		}
	SetMinMaxRect(&rDims, pts[0].x, pts[0].y, pts[2].x, pts[2].y);
	UpdateMinMaxRect(&rDims, pts[1].x, pts[1].y);
	UpdateMinMaxRect(&rDims, pts[3].x, pts[3].y);
	IncrementMinMaxRect(&rDims, o->un2ix(Outline.width*6)+3);
}

void
Box::DoMark(anyOutput *o, bool mark)
{
	if(mark){
		if(mo) DelBitmapClass(mo);			mo = 0L;
		memcpy(&mrc, &rDims, sizeof(RECT));
		IncrementMinMaxRect(&mrc, o->un2ix(Outline.width*6)+3);
		mo = GetRectBitmap(&mrc, o);
		InvertLine(pts, 5, &Outline, &rDims, o, mark);
		}
	else if(mo) RestoreRectBitmap(&mo, &mrc, o);
}

bool
Box::Command(int cmd, void *tmpl, anyOutput *o)
{
	MouseEvent *mev;
	POINT p;

	switch (cmd) {
	case CMD_SCALE:
		Outline.width *= ((scaleINFO*)tmpl)->sy.fy;
		Hatchline.width *= ((scaleINFO*)tmpl)->sy.fy;
		Fill.scale *= ((scaleINFO*)tmpl)->sy.fy;
		if(!(type & BAR_RELWIDTH) && parent->Id!= GO_DENSDISP)size *= ((scaleINFO*)tmpl)->sy.fy;
		return true;
	case CMD_FLUSH:
		if(ssRef) free(ssRef);		ssRef = 0L;
		if(name)free(name);			name = 0L;
		if(mo) DelBitmapClass(mo);		mo = 0L;
		return true;
	case CMD_LEGEND:
		if(!tmpl || ((GraphObj*)tmpl)->Id != GO_LEGEND) return false;
		((Legend*)tmpl)->HasFill(&Outline, &Fill, name);
		break;
	case CMD_MRK_DIRTY:				case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(IsInRect(&rDims, p.x = mev->x, p.y = mev->y) && !CurrGO) {
				if(IsInPolygon(&p, pts, 5)) {
					o->ShowMark(CurrGO = this, MRK_GODRAW);
					return true;
					}
				}
			break;
			}
		return false;
	case CMD_SET_DATAOBJ:
		Id = GO_BOX;
		data = (DataObj *)tmpl;
		return true;
	case CMD_UPDATE:
		if(ssRef && cssRef >3 && data) {
			data->GetValue(ssRef[0].y, ssRef[0].x, &pos1.fx);
			data->GetValue(ssRef[1].y, ssRef[1].x, &pos1.fy);
			data->GetValue(ssRef[2].y, ssRef[2].x, &pos2.fx);
			data->GetValue(ssRef[3].y, ssRef[3].x, &pos2.fy);
			return true;
			}
		return false;
	case CMD_BOX_TYPE:
		if(tmpl)type = *((int*)tmpl);
		return true;
	case CMD_BOX_FILL:
		if(tmpl) {
			memcpy(&Fill, tmpl, sizeof(FillDEF));
			if(Fill.hatch) memcpy(&Hatchline, Fill.hatch, sizeof(LineDEF));
			Fill.hatch = &Hatchline;
			return true;
			}
		break;
	case CMD_AUTOSCALE:
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH) {
			if(type & BAR_WIDTHDATA) {
				if(pos1.fy != pos2.fy) {
					((Plot*)parent)->CheckBounds(pos1.fx+size, pos1.fy);
					((Plot*)parent)->CheckBounds(pos2.fx-size, pos2.fy);
					}
				else {
					((Plot*)parent)->CheckBounds(pos1.fx, pos1.fy+size);
					((Plot*)parent)->CheckBounds(pos2.fx, pos2.fy-size);
					}
				}
			else {
				((Plot*)parent)->CheckBounds(pos1.fx, pos1.fy);
				((Plot*)parent)->CheckBounds(pos2.fx, pos2.fy);
				if(parent && (type & BAR_RELWIDTH)) {
					if(pos1.fy == pos2.fy) {
						((Plot*)parent)->CheckBounds(pos2.fx, pos2.fy + parent->GetSize(SIZE_BOXMINY));
						((Plot*)parent)->CheckBounds(pos2.fx, pos2.fy - parent->GetSize(SIZE_BOXMINY));
						}
					else if(pos1.fx == pos2.fx) {
						((Plot*)parent)->CheckBounds(pos2.fx + parent->GetSize(SIZE_BOXMINX), pos2.fy);
						((Plot*)parent)->CheckBounds(pos2.fx - parent->GetSize(SIZE_BOXMINX), pos2.fy);
						}
					}
				}
			return true;
			}
		break;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// whisker 
Whisker::Whisker(GraphObj *par, DataObj *d, lfPOINT fp1, lfPOINT fp2, int which,
	int xc1, int xr1, int yc1, int yr1, int xc2, int xr2,
	int yc2, int yr2):GraphObj(par, d)
{
	FileIO(INIT_VARS);
	memcpy(&pos1, &fp1, sizeof(lfPOINT));
	memcpy(&pos2, &fp2, sizeof(lfPOINT));
	type = which;
	Id = GO_WHISKER;
	if(xc1 >= 0 || xr1 >= 0 || yc1 >= 0 || yr1 >= 0 || xc2 >= 0 || xr2 >= 0 || 
		yc2 >= 0 || yr2 >= 0) {
		if(ssRef = (POINT*)malloc(sizeof(POINT)*4)) {
			ssRef[0].x = xc1;	ssRef[0].y = xr1;
			ssRef[1].x = yc1;	ssRef[1].y = yr1;
			ssRef[2].x = xc2;	ssRef[2].y = xr2;
			ssRef[3].x = yc2;	ssRef[3].y = yr2;
			cssRef = 4;
			}
		}
	Command(CMD_AUTOSCALE, 0L, 0L);
}

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

Whisker::~Whisker()
{
	if(mo) DelBitmapClass(mo);	mo = 0L;
	if(ssRef) free(ssRef);		ssRef = 0L;
	if(name) free(name);		name = 0L;
}

bool
Whisker::SetSize(int select, double value)
{
	switch(select & 0xfff) {
	case SIZE_WHISKER: 
		size = value;
		return true;
	case SIZE_WHISKER_LINE:
		LineDef.width = value;
		return true;
		}
	return false;
}

bool
Whisker::SetColor(int select, DWORD col)
{
	switch(select & 0xfff) {
	case COL_WHISKER:
		LineDef.color = col;
		return true;
		}
	return false;
}

void
Whisker::DoPlot(anyOutput *o)
{
	double si, csi, tmp, fix1, fiy1, fix2, fiy2, dx, dy;
	int i;

	fix1 = o->fx2fix(pos1.fx);	fix2 = o->fx2fix(pos2.fx);
	fiy1 = o->fy2fiy(pos1.fy);	fiy2 = o->fy2fiy(pos2.fy);
	if(fix1 == fix2 && fiy1 == fiy2) return;	//zero length
	pts[2].x = iround(fix1);		pts[3].x = iround(fix2);
	pts[2].y = iround(fiy1);		pts[3].y = iround(fiy2);
	//calculate sine and cosine
	si = fiy1-fiy2;
	tmp = fix2 - fix1;
	si = si/sqrt(si*si + tmp*tmp);
	csi = fix2-fix1;
	tmp = fiy2 - fiy1;
	csi = csi/sqrt(csi*csi + tmp*tmp);
	dx = o->un2fix(si*size/2.0);
	dy = o->un2fiy(csi*size/2.0);
	//calc cap
	pts[0].x = iround(fix1 - dx);	pts[4].x = iround(fix2 - dx);
	pts[0].y = iround(fiy1 - dy);	pts[4].y = iround(fiy2 - dy);
	pts[1].x = iround(fix1 + dx);	pts[5].x = iround(fix2 + dx);
	pts[1].y = iround(fiy1 + dy);	pts[5].y = iround(fiy2 + dy);
	//draw whisker
	SetMinMaxRect(&rDims, pts[0].x, pts[0].y, pts[1].x, pts[1].y);
	UpdateMinMaxRect(&rDims, pts[4].x, pts[4].y);
	UpdateMinMaxRect(&rDims, pts[5].x, pts[5].y);
	IncrementMinMaxRect(&rDims, 3+(o->un2ix(LineDef.width)<<1));
	o->SetLine(&LineDef);
	switch(type & 0x0f) {
	case 2:
		pts[4].x = pts[3].x;	pts[4].y = pts[3].y;
		pts[1].x = pts[2].x;	pts[1].y = pts[2].y;
	case 3:
		if((type & 0x0f) == 3){
			pts[5].x = pts[3].x;	pts[5].y = pts[3].y;
			pts[0].x = pts[2].x;	pts[0].y = pts[2].y;
			}
	case 0:
		for(i = 0; i < 5; i+=2) o->oSolidLine(pts+i);
		break;
	case 1:
		pts[4].x = pts[5].x = pts[3].x;		pts[4].y = pts[5].y = pts[3].y;
		pts[1].x = pts[0].x = pts[2].x;		pts[1].y = pts[0].y = pts[2].y;
		o->oSolidLine(pts+2);
		break;
		}
}

void
Whisker::DoMark(anyOutput *o, bool mark)
{
	int i;
	LineDEF OldLine;

	if(mark){
		if(mo) DelBitmapClass(mo);					mo = 0L;
		memcpy(&mrc, &rDims, sizeof(RECT));
		memcpy(&OldLine, &LineDef, sizeof(LineDEF));
		i = 3*o->un2ix(LineDef.width);		//increase size of rectangle for marks
		IncrementMinMaxRect(&mrc, i);		mo = GetRectBitmap(&mrc, o);
		LineDef.width *= 5.0;				DoPlot(o);
		LineDef.width = OldLine.width;		LineDef.color = OldLine.color ^ 0x00ffffffL;
		DoPlot(o);							o->UpdateRect(&mrc, false);
		memcpy(&LineDef, &OldLine, sizeof(LineDEF));
		}
	else RestoreRectBitmap(&mo, &mrc, o);
}

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

	switch (cmd) {
	case CMD_SCALE:
		size *= ((scaleINFO*)tmpl)->sy.fy;
		LineDef.width *= ((scaleINFO*)tmpl)->sy.fy;
		LineDef.patlength *= ((scaleINFO*)tmpl)->sy.fy;
		return true;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(IsInRect(&rDims, mev->x, mev->y) && !CurrGO) {
				if(IsCloseToLine(&pts[2], &pts[3], mev->x, mev->y) ||
					IsCloseToLine(&pts[0], &pts[1], mev->x, mev->y) ||
					IsCloseToLine(&pts[4], &pts[5], mev->x, mev->y)) {
						o->ShowMark(this, MRK_GODRAW);
						return true;
						}
				}
			break;
			}
		return false;
	case CMD_ERRDESC:
		if(tmpl && *((char*)tmpl)) {
			cb = (int)strlen((char*)tmpl)+2;
			if(name = (char*)realloc(name, cb)) rlp_strcpy(name, cb, (char*)tmpl);
			return true;
			}
		return false;
	case CMD_LEGEND:
		if(!tmpl || ((GraphObj*)tmpl)->Id != GO_LEGEND) return false;
		switch(type & 0x0f) {
		case 1:		((Legend*)tmpl)->HasErr(&LineDef, 5, name);		break;
		case 2:		((Legend*)tmpl)->HasErr(&LineDef, 4, name);		break;
		case 3:		((Legend*)tmpl)->HasErr(&LineDef, 3, name);		break;
		default:
			if((rDims.right - rDims.left) < (rDims.bottom - rDims.top))
				((Legend*)tmpl)->HasErr(&LineDef, 1, name);
			else ((Legend*)tmpl)->HasErr(&LineDef, 2, name);
			break;
			}
		break;
	case CMD_SET_DATAOBJ:
		Id = GO_WHISKER;		data = (DataObj *)tmpl;
		return true;
	case CMD_UPDATE:
		if(ssRef && cssRef >3 && data) {
			data->GetValue(ssRef[0].y, ssRef[0].x, &pos1.fx);
			data->GetValue(ssRef[1].y, ssRef[1].x, &pos1.fy);
			data->GetValue(ssRef[2].y, ssRef[2].x, &pos2.fx);
			data->GetValue(ssRef[3].y, ssRef[3].x, &pos2.fy);
			return true;
			}
		return false;
	case CMD_AUTOSCALE:
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH) {
			((Plot*)parent)->CheckBounds(pos1.fx, pos1.fy);
			((Plot*)parent)->CheckBounds(pos2.fx, pos2.fy);
			return true;
			}
		break;
	case CMD_WHISKER_STYLE:
		if(tmpl) type = *((int*)tmpl);
		return true;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// drop line 
DropLine::DropLine(GraphObj *par, DataObj *d, double x, double y, int which, int xc, 
	int xr, int yc, int yr):GraphObj(par, d)
{
	FileIO(INIT_VARS);
	fPos.fx = x;
	fPos.fy = y;
	type = which;
	Id = GO_DROPLINE;
	if(xc >= 0 && xr >= 0 && yc >= 0 && yr >= 0) {
		if(ssRef = (POINT*)malloc(sizeof(POINT)*2)) {
			ssRef[0].x = xc;	ssRef[0].y = xr;
			ssRef[1].x = yc;	ssRef[1].y = yr;
			cssRef = 2;
			}
		}
	bModified = false;
}

DropLine::DropLine(int src):GraphObj(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
	bModified = false;
}

DropLine::~DropLine()
{
	if(bModified) Undo.InvalidGO(this);
	if(ssRef) free(ssRef);		ssRef = 0L;
}

void
DropLine::DoPlot(anyOutput *o)
{
	int tmp;

	o->RLP.fp = 0.0f;		//reset line pattern start
	if(parent) {
		pts[0].x = pts[1].x = pts[2].x = pts[3].x = o->fx2ix(fPos.fx);
		pts[0].y = pts[1].y = pts[2].y = pts[3].y = o->fy2iy(fPos.fy);
		if(type & DL_LEFT) {
			tmp = o->fx2ix(parent->GetSize(SIZE_BOUNDS_LEFT));
			if(tmp < pts[0].x) pts[0].x = tmp;
			if(tmp > pts[1].x) pts[1].x = tmp;
			}
		if(type & DL_RIGHT) {
			tmp = o->fx2ix(parent->GetSize(SIZE_BOUNDS_RIGHT));
			if(tmp < pts[0].x) pts[0].x = tmp;
			if(tmp > pts[1].x) pts[1].x = tmp;
			}
		if(type & DL_YAXIS) {
			tmp = iround(parent->GetSize(SIZE_YAXISX));
			if(tmp < pts[0].x) pts[0].x = tmp;
			if(tmp > pts[1].x) pts[1].x = tmp;
			}
		if(type & DL_TOP) {
			tmp = o->fy2iy(parent->GetSize(SIZE_BOUNDS_TOP));
			if(tmp < pts[2].y) pts[2].y = tmp;
			if(tmp > pts[3].y) pts[3].y = tmp;
			}
		if(type & DL_BOTTOM) {
			tmp = o->fy2iy(parent->GetSize(SIZE_BOUNDS_BOTTOM));
			if(tmp < pts[2].y) pts[2].y = tmp;
			if(tmp > pts[3].y) pts[3].y = tmp;
			}
		if(type & DL_XAXIS) {
			tmp = iround(parent->GetSize(SIZE_XAXISY));
			if(tmp < pts[2].y) pts[2].y = tmp;
			if(tmp > pts[3].y) pts[3].y = tmp;
			}
		SetMinMaxRect(&rDims, pts[0].x, pts[2].y, pts[1].x, pts[3].y);
		IncrementMinMaxRect(&rDims, 3);
		o->SetLine(&LineDef);
		o->oPolyline(pts, 2);
		o->oPolyline(pts+2, 2);
		}
}

void
DropLine::DoMark(anyOutput *o, bool mark)
{

	InvertLine(pts, 2, &LineDef, 0L, o, mark);
	InvertLine(pts+2, 2, &LineDef, &rDims, o, mark);
}

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

	switch (cmd) {
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(IsInRect(&rDims, mev->x, mev->y) && !CurrGO) {
				if(IsCloseToLine(&pts[0], &pts[1], mev->x, mev->y) ||
					IsCloseToLine(&pts[2], &pts[3], mev->x, mev->y)) {
					o->ShowMark(this, MRK_GODRAW);
					return true;
					}
				}
			break;
			}
		return false;
	case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_DL_LINE:
		if(tmpl) memcpy(&LineDef, tmpl, sizeof(LineDEF));
		return true;
	case CMD_DL_TYPE:
		if(tmpl)type = *((int*)tmpl);
		return true;
	case CMD_SET_DATAOBJ:
		Id = GO_DROPLINE;
		data = (DataObj *)tmpl;
		return true;
	case CMD_UPDATE:
		if(ssRef && cssRef >1 && data) {
			data->GetValue(ssRef[0].y, ssRef[0].x, &fPos.fx);
			data->GetValue(ssRef[1].y, ssRef[1].x, &fPos.fy);
			return true;
			}
		return false;
	case CMD_AUTOSCALE:
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH) {
			((Plot*)parent)->CheckBounds(fPos.fx, fPos.fy);
			return true;
			}
		break;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// define a spherical scanline used for clipping spheres
sph_scanline::sph_scanline(POINT3D *center, int radius, bool bVert):GraphObj(0, 0)
{
	Id = GO_SPHSCANL;
	rad = radius >= 0 ? radius : -radius;
	memcpy(&p1, center, sizeof(POINT3D));	memcpy(&p2, center, sizeof(POINT3D));
	memcpy(&cent, center, sizeof(POINT3D));
	if(vert = bVert) {
		p1.y -= rad;		p2.y += rad;
		}
	else {
		p1.x -= rad;		p2.x += rad;
		}
	if(p1.x < 1) p1.x = 1;			if(p1.y < 1) p1.y = 1;
	if(p2.x < p1.x) p2.x = p1.x;	if(p2.y < p1.y) p2.y = p1.y;
	bValid1 = bValid2 = true;
}

bool
sph_scanline::Command(int cmd, void *tmpl, anyOutput *o)
{
	switch(cmd) {
	case CMD_ADDTOLINE:
		if(bValid1 && tmpl){
			memcpy(&p2, tmpl, sizeof(POINT3D));
			bValid2 = true;
			return true;
			}
		break;
	case CMD_STARTLINE:
		if(!bValid1 && tmpl){
			memcpy(&p1, tmpl, sizeof(POINT3D));
			bValid1 = true;
			}
		break;
		return true;
		}
	return false;
}

void
sph_scanline::DoClip(GraphObj *co)
{
	POINT3D *pla;
	int np, i;

	if(!bValid1 || !bValid2) return;
	switch(co->Id){
	case GO_SPHERE:
		bValid1 = bValid2 = false;
		clip_sphline_sphere(this, &p1, &p2, &cent, rad, iround(co->GetSize(SIZE_RADIUS1)), 
			iround(co->GetSize(SIZE_XPOS)), iround(co->GetSize(SIZE_YPOS)), 
			iround(co->GetSize(SIZE_ZPOS)));
		break;
	case GO_PLANE:
		for(i=0; ((plane*)co)->GetPolygon(&pla, &np, i); i++) {
			bValid1 = bValid2 = false;
			clip_sphline_plane(this, &p1, &p2, &cent, rad, pla, np, ((plane*)co)->GetVec());
			}
		break;
		}
}
	
bool
sph_scanline::GetPoint(POINT *p, int sel)
{
	if(!bValid1 || !bValid2) {
		p->x = p->y = 0;
		return false;
		}
	switch(sel) {
	case 1:
		p->x = p1.x;	p->y = p1.y;
		return true;
	case 2:
		p->x = p2.x;	p->y = p2.y;
		return true;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Sphere: a symbol in three dimensional space
Sphere::Sphere(GraphObj *par, DataObj *d, int sel, double x, double y, double z, 
	double r, int xc, int xr, int yc, int yr, int zc, int zr, int rc, int rr)
	:GraphObj(par, d)
{
	FileIO(INIT_VARS);
	Id = GO_SPHERE;
	fPos.fx = x;	fPos.fy = y;	fPos.fz = z;	size = r;
	if(xc >=0 || xr >=0 || yc >=0 || yr >=0 || zc >=0 || zr >=0 || rc >=0 || rr >=0) {
		if(ssRef = (POINT*)malloc(sizeof(POINT)*4)) {
			ssRef[0].x = xc;	ssRef[0].y = xr;
			ssRef[1].x = yc;	ssRef[1].y = yr;
			ssRef[2].x = zc;	ssRef[2].y = zr;
			ssRef[3].x = rc;	ssRef[3].y = rr;
			cssRef = 4;
			}
		}
	type = sel;
	bModified = false;
}

Sphere::Sphere(int src):GraphObj(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
	bModified = false;
}

Sphere::~Sphere()
{
	if(bModified) Undo.InvalidGO(this);
	Command(CMD_FLUSH, 0L, 0L);
}

double
Sphere::GetSize(int select)
{
	switch(select) {
	case SIZE_MIN_X:		return fip.fx - (double)rx;
	case SIZE_MAX_X:		return fip.fx + (double)rx;
	case SIZE_MIN_Y:		return fip.fy - (double)ry;
	case SIZE_MAX_Y:		return fip.fy + (double)ry;
	case SIZE_MIN_Z:		return fip.fz - (double)rx;
	case SIZE_MAX_Z:		return fip.fz + (double)rx;
	case SIZE_XPOS:			return fip.fx;
	case SIZE_YPOS:			return fip.fy;
	case SIZE_ZPOS:			return fip.fz;
	case SIZE_RADIUS1:	case SIZE_RADIUS2:	return (double)rx;
	case SIZE_XCENT:		return fPos.fx;
	case SIZE_YCENT:		return fPos.fy;
	case SIZE_ZCENT:		return fPos.fz;
	case SIZE_DRADIUS:		return size;
	case SIZE_SYMBOL:
		if(!type) return size;
		else return DefSize(SIZE_SYMBOL);
	case SIZE_SYM_LINE:		return Line.width;
		}
	return 0.0;
}

bool
Sphere::SetSize(int select, double value)
{
	switch(select & 0xfff) {
	case SIZE_SYMBOL:		size = value;			break;
	case SIZE_SYM_LINE:		Line.width = value;		break;
		}
	return true;
}

DWORD
Sphere::GetColor(int select)
{
	switch(select) {
	case COL_SYM_LINE:		return Line.color;
	case COL_SYM_FILL:		return Fill.color;
	default: return defs.Color(select);
		}
}

bool
Sphere::SetColor(int select, DWORD col)
{
	switch(select & 0xfff) {
	case COL_SYM_LINE:		Line.color = col;		break;
	case COL_SYM_FILL:		Fill.color = col;		break;
		}
	return true;
}

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

	if(size <= 0.001 || !o) return;
	if(!o->fvec2ivec(&fPos, &fip)) return;
	bDrawDone = false;
	if(scl){
		for(i = 0; i < nscl; i++) if(scl[i]) delete(scl[i]);
		free(scl);
		scl = 0L;	nscl = 0;
		}
	ix = iround(fip.fx);			iy = iround(fip.fy);
	switch (type) {
	case 1:
		rx = ry = iround(size * 0.5 * o->ddx);		break;
	case 2:
		rx = ry = iround(size * 0.5 * o->ddy);		break;
	case 3:
		rx = ry = iround(size * 0.5 * o->ddz);		break;
	default:
		type = 0;
	case 5:
		rx = o->un2ix(size/2.0);	ry = o->un2iy(size/2.0);
		break;
		}
	rDims.left = ix - rx;			rDims.right = ix + rx;
	rDims.top = iy - ry;			rDims.bottom = iy + ry;
	if(o->VPscale > 1.5 && (
		(rDims.right < defs.clipRC.left) || (rDims.left > defs.clipRC.right) ||
		(rDims.bottom < defs.clipRC.top) || (rDims.top > defs.clipRC.bottom))){
		bDrawDone = true;		return;
		}
	if(parent && parent->Command(CMD_SET_GO3D, this, o)) return;
	Command(CMD_REDRAW, 0L, o);
}

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

	switch (cmd) {
	case CMD_FLUSH:
		if(ssRef) free(ssRef);		ssRef = 0L;
		if(name) free(name);		name = 0L;
		if(scl){
			for(i = 0; i < nscl; i++) if(scl[i]) delete(scl[i]);
			free(scl);
			scl = 0L;	nscl = 0;
			}
		return true;
	case CMD_SCALE:
		Line.patlength *= ((scaleINFO*)tmpl)->sy.fy;		Line.width *= ((scaleINFO*)tmpl)->sy.fy;
		Fill.scale *= ((scaleINFO*)tmpl)->sy.fy;
		if(!type || type == 5)size *= ((scaleINFO*)tmpl)->sy.fy;;
		return true;
	case CMD_LEGEND:
		if(!tmpl || ((GraphObj*)tmpl)->Id != GO_LEGEND) return false;
		((Legend*)tmpl)->HasFill(&Line, &Fill, 0L);
		break;
	case CMD_SYM_FILL:
		if(tmpl) memcpy(&Fill, tmpl, sizeof(FillDEF));
		return true;
	case CMD_MRK_DIRTY:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_SET_DATAOBJ:
		Id = GO_SPHERE;
		data = (DataObj *)tmpl;
		return true;
	case CMD_REDRAW:
		//Note: this command is issued either by Undo (no output given) or
		//  by Plot3D::DoPlot after sorting all objects (output specified)
		if(!parent) return false;
		if(!o) return parent->Command(cmd, tmpl, o);
		if(bDrawDone) return false;
		bDrawDone = true;
		if(scl) DrawPG(o, 0);
		else {
			o->SetLine(&Line);				o->SetFill(&Fill);
			if(Fill.type & FILL_LIGHT3D) o->oSphere(ix, iy, rx, 0L, 0, 0L);
			else o->oCircle(ix-rx, iy-ry, ix+rx+1, iy+ry+1, name);
			}
		rx--;ry--;			//smaller marking rectangle
		rDims.left = ix-rx-1;				rDims.right = ix+rx+1;
		rDims.top = iy-ry-1;				rDims.bottom = iy+ry+1;
		return true;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(IsInRect(&rDims, mev->x, mev->y) && !CurrGO) {
				o->ShowMark(&rDims, MRK_INVERT);
				CurrGO = this;
				return true;
				}
			break;
			}
		break;
	case CMD_UPDATE:
		if(ssRef && cssRef >1 && data) {
			data->GetValue(ssRef[0].y, ssRef[0].x, &fPos.fx);
			data->GetValue(ssRef[1].y, ssRef[1].x, &fPos.fy);
			data->GetValue(ssRef[2].y, ssRef[2].x, &fPos.fz);
			if(cssRef >3) data->GetValue(ssRef[3].y, ssRef[3].x, &size);
			return true;
			}
		return false;
	case CMD_CLIP:
		if(co = (GraphObj*)tmpl){
			switch(co->Id) {
			case GO_PLANE:
			case GO_SPHERE:
				DoClip(co);
				break;
				}
			}
		return false;
	case CMD_AUTOSCALE:
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH) {
			((Plot*)parent)->CheckBounds3D(fPos.fx, fPos.fy, fPos.fz);
			return true;
			}
		break;
		}
	return false;
}

void
Sphere::DoClip(GraphObj *co)
{
	RECT cliprc;
	double d, d1;
	POINT3D cscl;
	int x, y, q, di, de, lim, cx, cy;
	int i;

	if(co && co->Id == GO_SPHERE) {
		if(co->GetSize(SIZE_ZPOS) > fip.fz) return;
		d1 = (d = co->GetSize(SIZE_XPOS) - fip.fx) * d;
		d1 += (d = co->GetSize(SIZE_YPOS) - fip.fy) * d;
		d1 = sqrt(d1 + (d = co->GetSize(SIZE_ZPOS) - fip.fz) * d);
		if(d1 >= (co->GetSize(SIZE_RADIUS1) + rx))return;
		}
	else {
		cliprc.left = iround(co->GetSize(SIZE_MIN_X));
		cliprc.right = iround(co->GetSize(SIZE_MAX_X));
		cliprc.top = iround(co->GetSize(SIZE_MIN_Y));
		cliprc.bottom = iround(co->GetSize(SIZE_MAX_Y));
		if(!OverlapRect(&rDims, &cliprc))return;
		}
	//use a list of horizontal scanlines created by a circular Bresenham's algorithm
	//Ref: C. Montani, R. Scopigno (1990) "Speres-To-Voxel Conversion", in:
	//   Graphic Gems (A.S. Glassner ed.) Academic Press, Inc.; 
	//   ISBN 0-12-288165-5 
	if(!scl && (scl = (sph_scanline**)calloc(rx*7+2, sizeof(sph_scanline*)))) {
		cscl.z = iround(fip.fz);			nscl = 0;
		for(q = 0; q < 2; q++) {
			x = lim = 0;	y = rx;		di = 2*(1-rx);
			while( y >= lim) {
				if(di < 0) {
					de = 2*di + 2*y -1;
					if(de > 0) {
						x++;	y--;	di += (2*x -2*y +2);
						}
					else {
						x++;	di += (2*x +1);
						}
					}
				else {
					de = 2*di -2*x -1;
					if(de > 0) {
						y--;	di += (-2*y +1);
						}
					else {
						x++;	y--;	di += (2*x -2*y +2);
						}
					}
				switch(q) {
				case 0:
					cy = rx - y;			cx = x;
					break;
				case 1:
					cy = rx + x;			cx = y; 
					break;
					}
				cscl.y = iround(fip.fy);		cscl.x = iround(fip.fx);
				cscl.y = cscl.y - rx + cy;
				if(cx > 1) scl[nscl++] = new sph_scanline(&cscl, cx, false);
				}
			}
		}
	if(!scl) return;
	//do clip for every scanline
	for(i = 0; i < nscl; i++) if(scl[i]) scl[i]->DoClip(co);
}

void
Sphere::DrawPG(anyOutput *o, int start)
{
	POINT *pts, np;
	long cp = 0;
	int i = start, step = 1;

	if(!o || !scl ||!nscl) return;
	if((pts = (POINT*)calloc(nscl*2, sizeof(POINT)))) {
		do {
			if(scl[i]) {
				if(step > 0) scl[i]->GetPoint(&np, 1);
				else scl[i]->GetPoint(&np, 2);
				if(np.x && np.y){
					AddToPolygon(&cp, pts, &np);
					}
				else if(cp){
					if(step > 0) DrawPG(o, i);			//split sphere
					step = -1;
					}
				}
			if(i == nscl && step > 0) step = -1;
			else i += step;
			}while(i > start);
		}
	o->SetLine(&Line);				o->SetFill(&Fill);
	if(cp) o->oSphere(ix, iy, rx, pts, cp, name);
	free(pts);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// plane: utility object to draw a flat plane in 3D space
plane::plane(GraphObj *par, DataObj *d, fPOINT3D *pts, int nPts, LineDEF *line, 
	  FillDEF *fill):GraphObj(par, d)
{
	int i;
	long area;
	double vlength, v1[3], v2[3], vp[3], area1;
	bool v_valid = false;
	
	nli = n_ipts = n_lines = 0;
	nldata = 0L;  ldata = 0L;	co = 0L;	lines = 0L;		PlaneVec = 0L;
	Id = GO_PLANE;	totalArea = 0;	bSigPol = false;
	memcpy(&Line, line, sizeof(LineDEF));
	memcpy(&Fill, fill, sizeof(FillDEF));
	rDims.left = rDims.right = rDims.top = rDims.bottom = 0;
	if(nPts > 2 && (ldata =(POINT3D**)calloc(1, sizeof(POINT3D*))) &&
		(ldata[0] = (POINT3D *)calloc(nPts+1, sizeof(POINT3D))) &&
		(nldata = (int*)calloc(1, sizeof(int)))	&&
		(ipts = (POINT*)calloc(nPts, sizeof(POINT)))){
		for(i = 0; i < nPts; i++) {
			ipts[i].x = ldata[0][i].x = iround(pts[i].fx);
			ipts[i].y = ldata[0][i].y = iround(pts[i].fy);
			ldata[0][i].z = iround(pts[i].fz);
			}
		nldata[0] = nPts;		nli = 1;	n_ipts = nPts;
		xBounds.fx = xBounds.fy = pts[0].fx;	yBounds.fx = yBounds.fy = pts[0].fy;
		zBounds.fx = zBounds.fy = pts[0].fz;
		rDims.left = rDims.right = ipts[0].x;	rDims.top = rDims.bottom = ipts[0].y;
		for(i = 1; i < nPts; i++){
			UpdateMinMaxRect(&rDims, ipts[i].x, ipts[i].y);
			if(pts[i].fx < xBounds.fx) xBounds.fx = pts[i].fx;
			if(pts[i].fx > xBounds.fy) xBounds.fy = pts[i].fx;
			if(pts[i].fy < yBounds.fx) yBounds.fx = pts[i].fy;
			if(pts[i].fy > yBounds.fy) yBounds.fy = pts[i].fy;
			if(pts[i].fz < zBounds.fx) zBounds.fx = pts[i].fz;
			if(pts[i].fz > zBounds.fy) zBounds.fy = pts[i].fz;
			}
		//test if plane vertical
		area1 = (xBounds.fx - xBounds.fy) * (yBounds.fx - yBounds.fy);
		for(area = 0, i = 1; i < nPts; i++) {
			area += (ipts[i].x - ipts[i-1].x) * ((ipts[i].y + ipts[i-1].y)>>1);
			}
		totalArea= area = abs(area);
		area1 = area ? fabs(area1/area) : 101.0;
		if(area < 100 && area1 > 100.0) {			//its small or vertical !
			if(lines=(line_segment**)calloc(nPts, sizeof(line_segment*))){
				for(i = 1; i < nPts; i++) {
					lines[i-1] = new line_segment(par, d, line, &ldata[0][i-1], &ldata[0][i]);
					}
				n_lines = nPts-1;
				}
			}
		else {					//for a visible plane get vector perpendicular to plane
			for (i = 1; i < (nPts-1); i++) {
				v1[0] = pts[i].fx - pts[i-1].fx;	v1[1] = pts[i].fy - pts[i-1].fy;
				v1[2] = pts[i].fz - pts[i-1].fz;	v2[0] = pts[i+1].fx - pts[i].fx;
				v2[1] = pts[i+1].fy - pts[i].fy;	v2[2] = pts[i+1].fz - pts[i].fz;
				vp[0] = v1[1]*v2[2] - v1[2]*v2[1];	vp[1] = v1[2]*v2[0] - v1[0]*v2[2];
				vp[2] = v1[0]*v2[1] - v1[1]*v2[0];
				vlength = sqrt(vp[0]*vp[0]+vp[1]*vp[1]+vp[2]*vp[2]);
				if(v_valid = (vlength > 100.0)) break;
				}
			if(v_valid && (PlaneVec = (double*)malloc(4 * sizeof(double)))) {
				PlaneVec[0] = vp[0]/vlength;		PlaneVec[1] = vp[1]/vlength;
				PlaneVec[2] = -vp[2]/vlength;
				PlaneVec[3] = PlaneVec[0] * pts[i].fx + PlaneVec[1] * pts[i].fy - PlaneVec[2] * pts[i].fz;
				}
			}
		}
}

plane::~plane() 
{
	int i;

	if(ldata) {
		for(i = 0; i < nli; i++) if(ldata[i]) free(ldata[i]);
		free(ldata);	ldata = 0L;		nli = 0;
		}
	if(lines) {
		for(i = 0; i < n_lines; i++) if(lines[i]) delete(lines[i]);
		free(lines);	lines = 0L;		n_lines = 0;
		}
	if(nldata) free(nldata);		nldata = 0L;
	if(ipts) free(ipts);			ipts = 0L;
	if(PlaneVec) free(PlaneVec);	PlaneVec = 0L;
}

double
plane::GetSize(int select)
{
	switch(select) {
	case SIZE_MIN_X:		return xBounds.fx;
	case SIZE_MAX_X:		return xBounds.fy;
	case SIZE_MIN_Y:		return yBounds.fx;
	case SIZE_MAX_Y:		return yBounds.fy;
	case SIZE_MIN_Z:		return zBounds.fx;
	case SIZE_MAX_Z:		return zBounds.fy;
		}
	return 0.0;
}

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

	bDrawDone = bReqPoint = false;
	if(Fill.type & FILL_LIGHT3D){
		Fill.color = o->VecColor(PlaneVec, Fill.color2, Fill.color);
		Fill.type &= ~FILL_LIGHT3D;
		}
	if(o->VPscale > 1.5 && (
		//ignore objects outside the display ara
		(rDims.right < defs.clipRC.left) || (rDims.left > defs.clipRC.right) ||
		(rDims.bottom < defs.clipRC.top) || (rDims.top > defs.clipRC.bottom))){
		bDrawDone = true;		return;
		}
	if(lines) {
		if(Line.width == 0.0) return;
		//draw line segments for vertical plane
		for(i = 0; i < n_lines; i++) if(lines[i]) lines[i]->DoPlot(o);
		bDrawDone = true;		return;
		}
	if(parent && parent->Command(CMD_SET_GO3D, this, o)) return;
	Command(CMD_REDRAW, 0L, o);
}

void
plane::DoMark(anyOutput *o, bool mark)
{
	FillDEF tmpfill;
	LineDEF tmpline;

	memcpy(&tmpfill, &Fill, sizeof(FillDEF));
	memcpy(&tmpline, &Line, sizeof(LineDEF));
	if(mark){
		tmpfill.color ^= 0x00ffffffL;		tmpline.color ^= 0x00ffffffL;
		}
	o->SetLine(&tmpline);					o->SetFill(&tmpfill);
	o->oPolygon(ipts, n_ipts);
}

bool 
plane::Command(int cmd, void *tmpl, anyOutput *o)
{
	POINT *pt;
	POINT3D *ap;
	int i, j;
	
	switch (cmd) {
	case CMD_MOUSE_EVENT:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_REDRAW:
		if(bDrawDone) return false;
		bDrawDone = true;
		if(o && nldata){
			if(Line.width == 0.0) Line.color = Fill.color;
			o->SetLine(&Line);			o->SetFill(&Fill);

//FILE *dbg;
//dbg = fopen("c:/test2.txt", "a");
//fprintf(dbg, "\nplane with %d parts\n", nli);
			for(i = 0; i < nli; i++){
//fprintf(dbg, "\n");
				if(nldata[i] > 2 && (pt = (POINT*)malloc(nldata[i]*sizeof(POINT)))){
					for(j = 0; j < nldata[i]; j++) {
//fprintf(dbg, "  %d %d\n", ldata[i][j].x, ldata[i][j].y);
						pt[j].x = ldata[i][j].x;	pt[j].y = ldata[i][j].y;
						}
					o->oPolygon(pt, nldata[i]);
					free(pt);
					}
				}
//fclose(dbg);
			}
		return true;
	case CMD_STARTLINE:
		if(ap = (POINT3D*)tmpl) {
			if(ldata && nldata && nli) {
				if(bReqPoint) {
					Command(CMD_ADDTOLINE, &ReqPoint, o);
					bReqPoint = false;
					}
				i = nli-1;			j = nldata[i]-1;
				if(ldata[i][j].x == ap->x && ldata[i][j].y == ap->y && ldata[i][j].z == ap->z){
					return true;
					}
				if(IsValidPG(ldata[i], nldata[i])) {
					//close previous polygon first
					if(ldata[i][0].x != ldata[i][j].x || ldata[i][0].y != ldata[i][j].y ||
						ldata[i][0].z != ldata[i][j].z){
						j++;
						ldata[i][j].x = ldata[i][0].x;	ldata[i][j].y = ldata[i][0].y;
						ldata[i][j].z = ldata[i][0].z;	nldata[i]++;
						}
					ldata = (POINT3D**)realloc(ldata, sizeof(POINT3D*) * (nli+1));
					ldata[nli] = (POINT3D*)malloc(sizeof(POINT3D)*2);
					nldata = (int*)realloc(nldata, sizeof(int) * (nli+1));
					}
				else {					//drop incomplete or invalid polygon
					nli--;
					}
				}
			else {
				ldata = (POINT3D**)calloc(1, sizeof(POINT3D*));
				ldata[nli = 0] = (POINT3D*)malloc(sizeof(POINT3D));
				nldata = (int*)calloc(1, sizeof(int));
				bReqPoint = false;
				}
			if(bSigPol) {
				n_linept = 1;	bSigPol = false;
				}
//			else n_linept = 0;
			if(ldata && nldata) {
				ldata[nli][0].x = ap->x;	ldata[nli][0].y = ap->y;
				ldata[nli][0].z = ap->z;	nldata[nli++] = 1;
				return true;
				}
			}
		break;
	case CMD_REQ_POINT:
		if(ap = (POINT3D*)tmpl) {
			ReqPoint.x = ap->x;		ReqPoint.y = ap->y;		ReqPoint.z = ap->z;
			bReqPoint = true;
			}
		return true;
	case CMD_SIGNAL_POL:			//signal: next point is on outline
		bSigPol = true;
		return true;
	case CMD_ADDTOLINE:
		if((ap = (POINT3D*)tmpl) && ldata && nldata && nli) {
			i= nli-1;	j=nldata[i]-1;
			if(ldata[i][j].x == ap->x && ldata[i][j].y == ap->y && ldata[i][j].z == ap->z){
				//probably nothing to add
				return j>0 ? true : false;
				}
			ldata[i] = (POINT3D*)realloc(ldata[i], ((j=nldata[i])+2) * sizeof(POINT3D));
			ldata[i][j].x = ap->x;			ldata[i][j].y = ap->y;
			ldata[i][j].z = ap->z;			nldata[i]++;
			if(bSigPol) n_linept++;
			bSigPol = false;
			return true;
			}
	case CMD_CLIP:
		bSigPol = false;
		if(co = (GraphObj*)tmpl){
			switch(co->Id) {
			case GO_PLANE:
				if(nli){
					DoClip(co);
					if(nli && ldata) {
						i = nli-1;			j = nldata[i]-1;
						//is last part valid ?
						if(j < 2) {
							free(ldata[i]);		ldata[i] = 0L;
							nldata[i] = 0;
							nli--;
							}
						//close last polygon
						else if(ldata[i][0].x != ldata[i][j].x ||
							ldata[i][0].y != ldata[i][j].y ||
							ldata[i][0].z != ldata[i][j].z){
							if(bSigPol) {
								nldata[i]--;
								}
							else j++;
							ldata[i][j].x = ldata[i][0].x;
							ldata[i][j].y = ldata[i][0].y;
							ldata[i][j].z = ldata[i][0].z;
							nldata[i]++;
							}
						}
					}
				else xBounds.fx=xBounds.fy=yBounds.fx=yBounds.fy=zBounds.fx=zBounds.fy=0.0;
				break;
				}
			}
		break;
		}
	return false;
}

void * 
plane::ObjThere(int x, int y)
{
	POINT p1;

	if(bDrawDone && IsInRect(&rDims, p1.x = x, p1.y = y) &&
		(IsInPolygon(&p1, ipts, n_ipts) || IsCloseToPL(p1, ipts, n_ipts))) return this;
	return 0L;
}

bool
plane::GetPolygon(POINT3D **pla, int *npt, int n)
{
	if(n < nli && ldata && ldata[n]) {
		*pla = ldata[n];	*npt = nldata[n];
		return true;
		}
	return false;
}

void
plane::DoClip(GraphObj *co)
{
	RECT cliprc;
	int o_nli, *o_nldata = 0L;
	POINT3D **o_ldata = 0L;
	POINT3D *tpg;
	int i, j, tnpt;
	bool is_valid = false;
	double *co_vec, d;

	//if two planes have the same parent it means they are part of one object
	// do not clip!
	if(co->parent == parent && co->Id == GO_PLANE) return;
	//test if two planes are part of the same superplane
#define TOL3D 1.0e-12
	if(co->Id == GO_PLANE && (co_vec = ((plane*)co)->GetVec()) && PlaneVec && fabs(co_vec[0] - PlaneVec[0]) < TOL3D
		&& fabs(co_vec[1]-PlaneVec[1]) < TOL3D && fabs(co_vec[2]-PlaneVec[2]) < TOL3D && ldata && nldata) {
		d = (ldata[0][0].x * co_vec[0] + ldata[0][0].y * co_vec[1] - co_vec[3])/co_vec[2] - ldata[0][0].z;
		if(fabs(d) < 2) return;
		}
#undef TOL3D
	cliprc.left = iround(co->GetSize(SIZE_MIN_X));
	cliprc.right = iround(co->GetSize(SIZE_MAX_X));
	cliprc.top = iround(co->GetSize(SIZE_MIN_Y));
	cliprc.bottom = iround(co->GetSize(SIZE_MAX_Y));
	if(OverlapRect(&rDims, &cliprc) && co != this) {
		o_nli = nli;		nli = 0;
		o_nldata = nldata;	nldata = 0L;
		o_ldata = ldata;	ldata = 0L;
		switch(co->Id) {
		case GO_PLANE:
			//clip all parts of this plane with all from another plane
			for(i = 0; ((plane*)co)->GetPolygon(&tpg, &tnpt, i); i++){
				for(j = 0; j < o_nli; j++) {
					n_linept = 0;
					if(o_nldata[j] >2) clip_plane_plane(this, o_ldata[j], o_nldata[j], PlaneVec, 
						tpg, tnpt, ((plane*)co)->GetVec(), ipts, n_ipts );
					}
				if(bReqPoint){
					if(bSigPol) nldata[nli-1]--;
					Command(CMD_ADDTOLINE, &ReqPoint, 0L);
					bReqPoint = false;
					}
				if(nli) is_valid = true;
				if(o_ldata) {
					for(j = 0; j < o_nli; j++) if(o_ldata[j]) free(o_ldata[j]);
					free(o_ldata);
					}
				if(o_nldata) free(o_nldata);
				o_nli = nli;		nli = 0;
				o_nldata = nldata;	nldata = 0L;
				o_ldata = ldata;	ldata = 0L;
				if(!o_nli) {					//plane is completly hidden
					return;
					}
				}
			if(is_valid || i==0){
				nli = o_nli;		o_nli = 0;
				nldata = o_nldata;	o_nldata = 0L;
				ldata = o_ldata;	o_ldata = 0L;
				}
			if(nli > 1) for(i = 1; i < nli; i++) {
				if(nldata[i] > 3 && ldata[i][nldata[i]-1].x == ldata[i-1][0].x
					&& ldata[i][nldata[i]-1].y == ldata[i-1][0].y) {
					nldata[i]--;	//bad vis: ignore last point
					}
				}
			break;
		default:
			nli = o_nli;		o_nli = 0;
			nldata = o_nldata;	o_nldata = 0L;
			ldata = o_ldata;	o_ldata = 0L;
			break;
			}
		//check shape and recalc some values
		if(is_valid && nli) {
			xBounds.fx = xBounds.fy = ldata[0][0].x;	yBounds.fx = yBounds.fy = ldata[0][0].y;
			zBounds.fx = zBounds.fy = ldata[0][0].z;
			rDims.left = rDims.right = ldata[0][0].x;	rDims.top = rDims.bottom = ldata[0][0].y;
			for(i = 0; i < nli; i++) if(nldata[i] > 2){
				if(ldata[i][0].x != ldata[i][nldata[i]-1].x || ldata[i][0].y != ldata[i][nldata[i]-1].y
					|| ldata[i][0].z != ldata[i][nldata[i]-1].z) {
					ldata[i][nldata[i]].x = ldata[i][0].x;	ldata[i][nldata[i]].y = ldata[i][0].y;
					ldata[i][nldata[i]].z = ldata[i][0].z;	nldata[i]++;
					}
				for(j = 0; j < (nldata[i]-1); j++) {
					UpdateMinMaxRect(&rDims, ldata[i][j].x, ldata[i][j].y);
					if(ldata[i][j].x < xBounds.fx) xBounds.fx = ldata[i][j].x;
					if(ldata[i][j].x > xBounds.fy) xBounds.fy = ldata[i][j].x;
					if(ldata[i][j].y < yBounds.fx) yBounds.fx = ldata[i][j].y;
					if(ldata[i][j].y > yBounds.fy) yBounds.fy = ldata[i][j].y;
					if(ldata[i][j].z < zBounds.fx) zBounds.fx = ldata[i][j].z;
					if(ldata[i][j].z > zBounds.fy) zBounds.fy = ldata[i][j].z;
					}
				}
			}
		}
	if(o_ldata) {
		for(i = 0; i < o_nli; i++) if(o_ldata[i]) free(o_ldata[i]);
		free(o_ldata);
		}
	if(o_nldata) free(o_nldata);
}

bool
plane::IsValidPG(POINT3D *pg, int npg)
{
	int i;
	long area, ratio;

	//if all points on an edge: not valid
	if(n_linept >= npg) return false;
	//a polygon must have more than 3 Points
	if(npg < 3) return false;
	//check for a reasonable size
	for(area = 0, i = 1; i < npg; i++) {
		area += (pg[i].x - pg[i-1].x) * ((pg[i].y + pg[i-1].y)>>1);
		}
	area += (pg[0].x - pg[i-1].x) * ((pg[0].y + pg[i-1].y)>>1);
	area = abs(area);					if(area < 25) return false;
	ratio = totalArea/area;
	if(ratio > 100) return false;
	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// a simple plane in three dimensional space
Plane3D::Plane3D(GraphObj *par, DataObj *da, fPOINT3D *pt, long npt)
	:GraphObj(par, da) 
{
	FileIO(INIT_VARS);
	Id = GO_PLANE3D;
	dt = (fPOINT3D*) memdup(pt, sizeof(fPOINT3D)*npt, 0L);
	ndt = npt;
}

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

Plane3D::~Plane3D()
{
	if(dt) free(dt);	if(pts) free(pts);
	dt = 0L;		ndt = 0L;
	if(ipl) delete (ipl);	ipl = 0L;
}

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

	if((select & 0xfff) >= SIZE_XPOS && (select & 0xfff) <=  SIZE_XPOS_LAST){
		if((i = select-SIZE_XPOS) >=0 && i <= 200 && i < (int)ndt){
			dt[i].fx = value;			return true;
			}
		}
	else if((select & 0xfff) >= SIZE_YPOS && (select & 0xfff) <= SIZE_YPOS_LAST){
		if((i = select-SIZE_YPOS) >=0 && i <= 200 && i < (int)ndt){
			dt[i].fy = value;			return true;
			}
		}
	else if((select & 0xfff) >= SIZE_ZPOS && (select & 0xfff) <= SIZE_ZPOS_LAST){
		if((i = select-SIZE_ZPOS) >=0 && i <= 200 && i < (int)ndt){
			dt[i].fz = value;			return true;
			}
		}
	else switch(select) {
	case SIZE_SYM_LINE:
		Line.width = value;
		}
	return false;
}

bool
Plane3D::SetColor(int select, DWORD col)
{
	switch(select) {
	case COL_POLYLINE:		Line.color = col;		return true;
	case COL_POLYGON:		Fill.color = col;		return true;
		}
	return false;
}

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

	if(ipl) delete ipl;		ipl = 0L;
	if(pts) free(pts);		pts = 0L;
	if(!(o->ActualSize(&rDims)))return;
	rDims.left = rDims.right;	rDims.top = rDims.bottom;
	rDims.right = rDims.bottom = 0;
	if((pts = (fPOINT3D*)malloc(sizeof(fPOINT3D)*ndt))){
		for(i = 0; i < ndt; i++) {
			if(!o->fvec2ivec(&dt[i], &pts[i])){
				free(pts);	pts = 0L; return;
				}
			UpdateMinMaxRect(&rDims, iround(pts[i].fx), iround(pts[i].fy));
			}
		if(ipl = new plane(this, data, pts, i, &Line, &Fill))ipl->DoPlot(o);
		}
	IncrementMinMaxRect(&rDims, o->un2ix(Line.width)+1);
}

void
Plane3D::DoMark(anyOutput *o, bool mark)
{
	if(mark){
		if(pts && ipl)ipl->DoMark(o, mark);
		o->UpdateRect(&rDims, false);
		}
	else if(parent) parent->Command(CMD_REDRAW, 0L, o);
}

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

	switch (cmd) {
	case CMD_SET_DATAOBJ:
		Id = GO_PLANE3D;
		data = (DataObj *)tmpl;
		return true;
	case CMD_SCALE:
		Line.patlength *= ((scaleINFO*)tmpl)->sy.fy;		Line.width *= ((scaleINFO*)tmpl)->sy.fy;
		Fill.scale *= ((scaleINFO*)tmpl)->sy.fy;
		return true;
	case CMD_LEGEND:
		if(!tmpl || ((GraphObj*)tmpl)->Id != GO_LEGEND) return false;
		if(parent && parent->Id > GO_PLOT && parent->Id < GO_GRAPH) {
			((Legend*)tmpl)->HasFill(&Line, &Fill, ((Plot*)parent)->data_desc);
			}
		else ((Legend*)tmpl)->HasFill(&Line, &Fill, 0L);
		break;
	case CMD_MRK_DIRTY:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_SYM_FILL:
		if(tmpl) memcpy(&Fill, tmpl, sizeof(FillDEF));
		return true;
	case CMD_REDRAW:
		//Note: this command is issued either by Undo (no output given) or
		//  by Plot3D::DoPlot after sorting all objects (output specified)
		if(!parent) return false;
		if(!o) return parent->Command(cmd, tmpl, o);
		return true;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(IsInRect(&rDims, mev->x, mev->y) && !CurrGO) {
				if(ipl && ipl->ObjThere(mev->x, mev->y)){
					o->ShowMark(CurrGO=this, MRK_GODRAW);
					return true;
					}
				}
			break;
			}
		break;
	case CMD_PG_FILL:
		if(tmpl) {
			memcpy((void*)&Fill, tmpl, sizeof(FillDEF));
			Fill.hatch = 0L;
			}
		break;
	case CMD_AUTOSCALE:
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH && dt) {
			for(i = 0; i < ndt; i++)
				((Plot*)parent)->CheckBounds3D(dt[i].fx, dt[i].fy, dt[i].fz);
			return true;
			}
		break;
	case CMD_SET_GO3D:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Brick: a bar in three dimensional space
Brick::Brick(GraphObj *par, DataObj *da, double x, double y, double z, 
		double d, double w, double h, DWORD flg, int xc, int xr, int yc,
		int yr, int zc, int zr, int dc, int dr, int wc, int wr, int hc,
		int hr):GraphObj(par, da)
{
	FileIO(INIT_VARS);
	Id = GO_BRICK;
	fPos.fx = x;	fPos.fy = y;	fPos.fz = z;
	depth = d;		width = w;		height = h;
	flags = flg;
	if(xc >= 0 || xr >= 0 || yc >= 0 || yr >= 0 || zc >= 0 || zr >= 0 ||
		dc >= 0 || dr >= 0 || wc >= 0 || wr >= 0 || hc >= 0 || hr >= 0) {
		if(ssRef = (POINT*)malloc(sizeof(POINT)*6)) {
			ssRef[0].x = xc;	ssRef[0].y = xr;
			ssRef[1].x = yc;	ssRef[1].y = yr;
			ssRef[2].x = zc;	ssRef[2].y = zr;
			ssRef[3].x = dc;	ssRef[3].y = dr;
			ssRef[4].x = wc;	ssRef[4].y = wr;
			ssRef[5].x = hc;	ssRef[5].y = hr;
			cssRef = 6;
			}
		}
	bModified = false;
}

Brick::Brick(int src):GraphObj(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
	bModified = false;
}
	
Brick::~Brick()
{
	int i;

	if(faces) {
		for(i = 0; i < 6; i++) if(faces[i]) delete(faces[i]);
		free(faces);
		}
	faces = 0L;
	if(mo) DelBitmapClass(mo);		mo = 0L;
	Command(CMD_FLUSH, 0L, 0L);
	if(bModified) Undo.InvalidGO(this);
}

bool
Brick::SetSize(int select, double value)
{
	switch(select & 0xfff) {
	case SIZE_BAR_LINE:		Line.width = value;		return true;
	case SIZE_BAR_BASE:		fPos.fy = value;		return true;
	case SIZE_BAR:			width = value;			return true;
	case SIZE_BAR_DEPTH:	depth = value;			return true;
		}
	return false;
}

bool
Brick::SetColor(int select, DWORD col)
{
	switch(select & 0xfff) {
	case COL_BAR_LINE:		Line.color = col;		return true;
	case COL_BAR_FILL:		Fill.color = col;		return true;
		}
	return false;
}

void
Brick::DoPlot(anyOutput *o)
{
	fPOINT3D cpt[8], fip1, fip2, tmp, itmp, *pg;
	plane *npl;
	double dtmp;
	int i;

	if(mo) DelBitmapClass(mo);		mo = 0L;
	if(!faces || !o || !o->fvec2ivec(&fPos, &fip1)) return;
	if(!(pg = (fPOINT3D *)malloc(5*sizeof(fPOINT3D)))) return;
	for(i = 0; i < 6; i++) {
		if(faces[i]) delete(faces[i]);
		faces[i] = 0L;
		}
	if(flags & 0x800L) {		//height is data
		tmp.fx = fPos.fx;			tmp.fy = height;
		tmp.fz = fPos.fz;			o->fvec2ivec(&tmp, &fip2);
		}
	else {						//height is units
		tmp.fx = tmp.fz = 0.0;		tmp.fy = height;
		o->uvec2ivec(&tmp, &fip2);
		fip2.fx += fip1.fx;			fip2.fy += fip1.fy;
		fip2.fz += fip1.fz;
		}
	//calc output-device coordinates of cubic brick: 8 corners
	tmp.fx = -(width/2.0);			tmp.fz = (depth/2.0);	tmp.fy = 0.0;
	o->uvec2ivec(&tmp, &itmp);
	cpt[0].fx= fip1.fx+itmp.fx;	cpt[0].fy= fip1.fy+itmp.fy;	cpt[0].fz= fip1.fz+itmp.fz;
	cpt[2].fx= fip1.fx-itmp.fx;	cpt[2].fy= fip1.fy-itmp.fy;	cpt[2].fz= fip1.fz-itmp.fz;
	cpt[4].fx= fip2.fx+itmp.fx;	cpt[4].fy= fip2.fy+itmp.fy;	cpt[4].fz= fip2.fz+itmp.fz;
	cpt[6].fx= fip2.fx-itmp.fx;	cpt[6].fy= fip2.fy-itmp.fy;	cpt[6].fz= fip2.fz-itmp.fz;
	tmp.fx = (width/2.0);			tmp.fz = (depth/2.0);	tmp.fy = 0.0;
	o->uvec2ivec(&tmp, &itmp);
	cpt[1].fx= fip1.fx+itmp.fx;	cpt[1].fy= fip1.fy+itmp.fy;	cpt[1].fz= fip1.fz+itmp.fz;
	cpt[3].fx= fip1.fx-itmp.fx;	cpt[3].fy= fip1.fy-itmp.fy;	cpt[3].fz= fip1.fz-itmp.fz;
	cpt[5].fx= fip2.fx+itmp.fx;	cpt[5].fy= fip2.fy+itmp.fy;	cpt[5].fz= fip2.fz+itmp.fz;
	cpt[7].fx= fip2.fx-itmp.fx;	cpt[7].fy= fip2.fy-itmp.fy;	cpt[7].fz= fip2.fz-itmp.fz;
	//set up 6 faces
	pg[0].fx = pg[4].fx = cpt[0].fx;	pg[1].fx = cpt[1].fx;	
	pg[2].fx = cpt[2].fx;				pg[3].fx = cpt[3].fx;
	pg[0].fy = pg[4].fy = cpt[0].fy;	pg[1].fy = cpt[1].fy;	
	pg[2].fy = cpt[2].fy;				pg[3].fy = cpt[3].fy;
	pg[0].fz = pg[4].fz = cpt[0].fz;	pg[1].fz = cpt[1].fz;	
	pg[2].fz = cpt[2].fz;				pg[3].fz = cpt[3].fz;
	faces[0] = new plane(this, data, pg, 5, &Line, &Fill);
	pg[2].fx = cpt[5].fx;				pg[3].fx = cpt[4].fx;
	pg[2].fy = cpt[5].fy;				pg[3].fy = cpt[4].fy;
	pg[2].fz = cpt[5].fz;				pg[3].fz = cpt[4].fz;
	npl = new plane(this, data, pg, 5, &Line, &Fill);
	if(npl->GetSize(SIZE_MAX_Z) > faces[0]->GetSize(SIZE_MAX_Z)) faces[1] = npl;
	else {
		faces[1] = faces[0];		faces[0] = npl;		
		}
	pg[0].fx = pg[4].fx = cpt[2].fx;	pg[1].fx = cpt[6].fx;	
	pg[2].fx = cpt[5].fx;				pg[3].fx = cpt[1].fx;
	pg[0].fy = pg[4].fy = cpt[2].fy;	pg[1].fy = cpt[6].fy;	
	pg[2].fy = cpt[5].fy;				pg[3].fy = cpt[1].fy;
	pg[0].fz = pg[4].fz = cpt[2].fz;	pg[1].fz = cpt[6].fz;	
	pg[2].fz = cpt[5].fz;				pg[3].fz = cpt[1].fz;
	npl = new plane(this, data, pg, 5, &Line, &Fill);
	if((dtmp = npl->GetSize(SIZE_MAX_Z)) > faces[1]->GetSize(SIZE_MAX_Z)) faces[2] = npl;
	else {
		faces[2] = faces[1];
		if(dtmp > faces[0]->GetSize(SIZE_MAX_Z)) faces[1] = npl;
		else {
			faces[1] = faces[0];	faces[0] = npl;
			}
		}
	pg[2].fx = cpt[7].fx;				pg[3].fx = cpt[3].fx;
	pg[2].fy = cpt[7].fy;				pg[3].fy = cpt[3].fy;
	pg[2].fz = cpt[7].fz;				pg[3].fz = cpt[3].fz;
	npl = new plane(this, data, pg, 5, &Line, &Fill);
	dtmp = npl->GetSize(SIZE_MAX_Z);
	for (i = 3; i; i--) {
		if(dtmp >faces[i-1]->GetSize(SIZE_MAX_Z)) {
			faces[i] = npl;			break;
			}
		else faces[i] = faces[i-1];
		}
	if(!i) faces[0] = npl;
	pg[0].fx = pg[4].fx = cpt[4].fx;	pg[1].fx = cpt[7].fx;	
	pg[2].fx = cpt[3].fx;				pg[3].fx = cpt[0].fx;
	pg[0].fy = pg[4].fy = cpt[4].fy;	pg[1].fy = cpt[7].fy;	
	pg[2].fy = cpt[3].fy;				pg[3].fy = cpt[0].fy;
	pg[0].fz = pg[4].fz = cpt[4].fz;	pg[1].fz = cpt[7].fz;	
	pg[2].fz = cpt[3].fz;				pg[3].fz = cpt[0].fz;
	npl = new plane(this, data, pg, 5, &Line, &Fill);
	dtmp = npl->GetSize(SIZE_MAX_Z);
	for (i = 4; i; i--) {
		if(dtmp >faces[i-1]->GetSize(SIZE_MAX_Z)) {
			faces[i] = npl;			break;
			}
		else faces[i] = faces[i-1];
		}
	if(!i) faces[0] = npl;
	pg[2].fx = cpt[6].fx;				pg[3].fx = cpt[5].fx;
	pg[2].fy = cpt[6].fy;				pg[3].fy = cpt[5].fy;
	pg[2].fz = cpt[6].fz;				pg[3].fz = cpt[5].fz;
	npl = new plane(this, data, pg, 5, &Line, &Fill);
	dtmp = npl->GetSize(SIZE_MAX_Z);
	for (i = 5; i; i--) {
		if(dtmp >faces[i-1]->GetSize(SIZE_MAX_Z)) {
			faces[i] = npl;			break;
			}
		else faces[i] = faces[i-1];
		}
	if(!i) faces[0] = npl;
	rDims.left = rDims.right = (int)pg[0].fx;
	rDims.top = rDims.bottom = (int)pg[0].fy;
	for (i= 3; i < 6; i++) if(faces[i]) {
		faces[i]->DoPlot(o);
		UpdateMinMaxRect(&rDims, faces[i]->rDims.left, faces[i]->rDims.top);
		UpdateMinMaxRect(&rDims, faces[i]->rDims.right, faces[i]->rDims.bottom);
		}
	free(pg);
}

void
Brick::DoMark(anyOutput *o, bool mark)
{
	int i;

	if(mark){
		if(mo) DelBitmapClass(mo);					mo = 0L;
		memcpy(&mrc, &rDims, sizeof(RECT));
		IncrementMinMaxRect(&mrc, 6 + o->un2ix(Line.width));
		mo = GetRectBitmap(&mrc, o);
		if(faces) for(i = 3; i < 6; i++)
			if(faces[i]) faces[i]->DoMark(o, mark);
		o->UpdateRect(&rDims, false);
		}
	else if(mo)	RestoreRectBitmap(&mo, &mrc, o);
}

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

	switch (cmd) {
	case CMD_FLUSH:
		if(ssRef) free(ssRef);		ssRef = 0L;
		if(name) free(name);		name = 0L;
		return true;
	case CMD_SCALE:
		Line.patlength *= ((scaleINFO*)tmpl)->sy.fy;		Line.width *= ((scaleINFO*)tmpl)->sy.fy;
		Fill.scale *= ((scaleINFO*)tmpl)->sy.fy;			depth *= ((scaleINFO*)tmpl)->sz.fy;
		width *= ((scaleINFO*)tmpl)->sx.fy;		if(!(flags & 0x800L)) height *=  ((scaleINFO*)tmpl)->sx.fy;
		return true;
	case CMD_LEGEND:
		if(!tmpl || ((GraphObj*)tmpl)->Id != GO_LEGEND) return false;
		if(parent && parent->Id > GO_PLOT && parent->Id < GO_GRAPH) {
			((Legend*)tmpl)->HasFill(&Line, &Fill, ((Plot*)parent)->data_desc);
			}
		else ((Legend*)tmpl)->HasFill(&Line, &Fill, 0L);
		break;
	case CMD_BAR_FILL:
		if(tmpl) {
			memcpy(&Fill, tmpl, sizeof(FillDEF));
			Fill.hatch = 0L;
			return true;
			}
		break;
	case CMD_MRK_DIRTY:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_SET_DATAOBJ:
		Id = GO_BRICK;
		data = (DataObj *)tmpl;
		return true;
	case CMD_REDRAW:
		//Note: this command is issued either by Undo (no output given) or
		//  by Plot3D::DoPlot after sorting all objects (output specified)
		if(!parent) return false;
		if(!o) return parent->Command(cmd, tmpl, o);
		//Should we ever come here ?
		return true;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(IsInRect(&rDims, mev->x, mev->y) && !CurrGO) {
				if(faces && faces[3] && faces[4] && faces[5] &&
					(faces[3]->ObjThere(mev->x, mev->y) || 
					faces[4]->ObjThere(mev->x, mev->y) ||
					faces[5]->ObjThere(mev->x, mev->y))){
						o->ShowMark(CurrGO=this, MRK_GODRAW);
						return true;
						}
				}
			break;
			}
		break;
	case CMD_UPDATE:
		if(ssRef && cssRef > 5 && data) {
			data->GetValue(ssRef[0].y, ssRef[0].x, &fPos.fx);
			data->GetValue(ssRef[1].y, ssRef[1].x, &fPos.fy);
			data->GetValue(ssRef[2].y, ssRef[2].x, &fPos.fz);
			data->GetValue(ssRef[3].y, ssRef[3].x, &depth);
			data->GetValue(ssRef[4].y, ssRef[4].x, &width);
			data->GetValue(ssRef[5].y, ssRef[5].x, &height);
			return true;
			}
		return false;
	case CMD_AUTOSCALE:
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH) {
			((Plot*)parent)->CheckBounds3D(fPos.fx, fPos.fy, fPos.fz);
			if(flags & 0x800L) {		//height is data
				((Plot*)parent)->CheckBounds3D(fPos.fx, height, fPos.fz);
				}
			return true;
			}
		break;
	case CMD_SET_GO3D:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// line_segment: utility object to draw a piece of a polyline in 3D space
line_segment::line_segment(GraphObj *par, DataObj *d, LineDEF *ld, POINT3D *p1, POINT3D *p2)
	:GraphObj(par, d)
{
	double tmp, tmp1, tmp2;

	nli = 0;    nldata = 0L;	  ldata = 0L;	prop = 1.0;		df_go = 0L;
	fmin.fx = fmax.fx = fmin.fy = fmax.fy = fmin.fz = fmax.fz = 0.0;
	ndf_go = 0;
	if(ld) memcpy(&Line, ld, sizeof(LineDEF));
	if(p1 && p2 &&(ldata =(POINT3D**)calloc(1, sizeof(POINT3D*))) &&
		(ldata[0] = (POINT3D*)malloc(2 * sizeof(POINT3D))) &&
		(nldata = (int*)calloc(1, sizeof(int)))){
			if(Line.pattern) {
				tmp1 = (tmp = (p2->x - p1->x)) * tmp;
				tmp2 = (tmp1 += (tmp = (p2->y - p1->y)) * tmp);
				tmp1 += (tmp = (p2->z - p1->z)) * tmp;
				if(tmp1 > 1.0) prop = sqrt(tmp2)/sqrt(tmp1);
				}
			memcpy(&ldata[0][0], p1, sizeof(POINT3D));
			memcpy(&ldata[0][1], p2, sizeof(POINT3D));
			nldata[0] = 2;		nli = 1;
			rDims.left = rDims.right = p1->x;	rDims.top = rDims.bottom = p1->y;
			UpdateMinMaxRect(&rDims, p2->x, p2->y);
			fmin.fx = (double)rDims.left;	fmin.fy = (double)rDims.top;
			fmax.fx = (double)rDims.right;	fmax.fy = (double)rDims.bottom;
			if(p2->z > p1->z) {
				fmin.fz = (double)p1->z;	fmax.fz = (double)p2->z;
				}
			else {
				fmin.fz = (double)p2->z;	fmax.fz = (double)p1->z;
				}
		}
	Id = GO_LINESEG;
}

line_segment::~line_segment()
{
	int i;

	if(ldata) {
		for(i = 0; i < nli; i++) if(ldata[i]) free(ldata[i]);
		free(ldata);
		}
	if(nldata) free(nldata);		nldata = 0L;
}

double
line_segment::GetSize(int select)
{
	switch(select) {
	case SIZE_MIN_Z:
		return fmin.fz;
	case SIZE_MAX_Z:
		return fmax.fz;
		}
	return 0.0;
}

void
line_segment::DoPlot(anyOutput *o)
{
	bDrawDone = false;		co = 0L;
	if(df_go) free(df_go);
	df_go = 0L;				ndf_go = 0;
	if(o->VPscale > 1.5 && (
		(rDims.right < defs.clipRC.left) || (rDims.left > defs.clipRC.right) ||
		(rDims.bottom < defs.clipRC.top) || (rDims.top > defs.clipRC.bottom))){
		bDrawDone = true;		return;
		}
	if(parent && parent->Command(CMD_SET_GO3D, this, o)) return;
	Command(CMD_REDRAW, 0L, o);
}

bool
line_segment::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i, j;
	POINT pts[2];
	LineDEF cLine;
	POINT3D *ap;

	switch (cmd) {
	case CMD_MOUSE_EVENT:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_DRAW_LATER:
		if(!co) return false;
		if(df_go){
			for(i = 0; i < ndf_go; i++) if(df_go[i] == co) return true;
			if(df_go = (GraphObj**)realloc(df_go, (ndf_go +1) * sizeof(GraphObj*))) {
				df_go[ndf_go++] = co;
				}
			}
		else {
			if(df_go = (GraphObj**)malloc(sizeof(GraphObj*))){
				df_go[0] = co;	ndf_go = 1;
				}
			}
		return true;
	case CMD_REDRAW:
		if(bDrawDone) return false;
		bDrawDone = true;
		if(!nli) return false;
		if(df_go) {
			for(i = 0; i < ndf_go; i++) if(df_go[i]) df_go[i]->Command(cmd, tmpl, o);
			free(df_go);	df_go = 0L;			ndf_go = 0L;
			}
		if(o && ldata && nldata){
			memcpy(&cLine, &Line, sizeof(LineDEF));
			cLine.patlength *= prop;
			o->SetLine(&cLine);
			for(i = 0; i < nli; i++) for(j = 0; j < (nldata[i]-1); j++) {
				pts[0].x = ldata[i][j].x;	pts[0].y = ldata[i][j].y;
				pts[1].x = ldata[i][j+1].x;	pts[1].y = ldata[i][j+1].y;
				if(pts[0].x != pts[1].x || pts[0].y != pts[1].y) o->oPolyline(pts, 2);
				}
			}
		return true;
	case CMD_CLIP:
		if(co = (GraphObj*)tmpl){
			switch(co->Id) {
			case GO_PLANE:
			case GO_SPHERE:
				DoClip(co);
				break;
				}
			}
		return false;
	case CMD_STARTLINE:
		if(tmpl) {
			if(ldata && nldata) {
				ldata = (POINT3D**)realloc(ldata, sizeof(POINT3D*) * (nli+1));
				ldata[nli] = (POINT3D*)malloc(2 * sizeof(POINT3D));
				nldata = (int*)realloc(nldata, sizeof(int)*(nli+1));
				}
			else {
				ldata = (POINT3D**)calloc(1, sizeof(POINT3D*));
				ldata[nli = 0] = (POINT3D*)malloc(2 * sizeof(POINT3D));
				nldata = (int*)calloc(1, sizeof(int));
				}
			if(ldata && nldata) {
				memcpy(&ldata[nli][0], tmpl, sizeof(POINT3D));
				memcpy(&ldata[nli][1], tmpl, sizeof(POINT3D));
				nldata[nli++] = 1;
				return true;
				}
			}
		break;
	case CMD_ADDTOLINE:
		if((ap = (POINT3D*)tmpl) && ldata && nldata && nli && nldata[i =(nli-1)]) {
			j = nldata[i];
			ldata[i] = (POINT3D*)realloc(ldata[i], (nldata[i]+2) * sizeof(POINT3D));
			if(j && ldata[i][j-1].x == ap->x && ldata[i][j-1].y == ap->y &&
				ldata[i][j-1].z == ap->z) return true;
			else {
				j = (nldata[i]++);
				}
			memcpy(&ldata[i][j-1], tmpl, sizeof(POINT3D));
			return true;
			}
		break;
		}
	return false;
}

void *
line_segment::ObjThere(int x, int y)
{
	int i, j;
	POINT pts[2];

	if(ldata && nldata){
		for(i = 0; i < nli; i++) for(j = 0; j < nldata[i]; j +=2) {
			pts[0].x = ldata[i][j].x;	pts[0].y = ldata[i][j].y;
			pts[1].x = ldata[i][j+1].x;	pts[1].y = ldata[i][j+1].y;
			if(IsCloseToLine(&pts[0], &pts[1], x, y))return this;
			}
		}
	return 0L;
}

void
line_segment::DoClip(GraphObj *co)
{
	RECT cliprc;
	int o_nli, *o_nldata = 0L;
	POINT3D **o_ldata = 0L, *pts = 0L, *pla;
	int i, j, k, np, r, cx, cy, cz;
	bool is_valid = false;

	cliprc.left = iround(co->GetSize(SIZE_MIN_X));
	cliprc.right = iround(co->GetSize(SIZE_MAX_X));
	cliprc.top = iround(co->GetSize(SIZE_MIN_Y));
	cliprc.bottom = iround(co->GetSize(SIZE_MAX_Y));
	if(OverlapRect(&rDims, &cliprc)) {
		if(!(pts = (POINT3D*)calloc(2, sizeof(POINT3D))))return;
		o_nli = nli;		nli = 0;
		o_nldata = nldata;	nldata = 0L;
		o_ldata = ldata;	ldata = 0L;
		switch(co->Id) {
		case GO_SPHERE:
			cx = iround(co->GetSize(SIZE_XPOS));
			cy = iround(co->GetSize(SIZE_YPOS));
			cz = iround(co->GetSize(SIZE_ZPOS));
			r = iround(co->GetSize(SIZE_RADIUS1));
			for(i = 0; i < o_nli; i++) for(j = 0; j < (o_nldata[i]-1); j ++) {
				pts[0].x = o_ldata[i][j].x;			pts[0].y = o_ldata[i][j].y;
				pts[0].z = o_ldata[i][j].z;			pts[1].x = o_ldata[i][j+1].x;
				pts[1].y = o_ldata[i][j+1].y;		pts[1].z = o_ldata[i][j+1].z;
				clip_line_sphere(this, &pts, r, cx, cy, cz);
				}
			break;
		case GO_PLANE:
			for(i = 0; ((plane*)co)->GetPolygon(&pla, &np, i); i++){
				for(j = 0; j < o_nli; j++) {
					if(o_nldata[j] >1) for(k = 0; k < (o_nldata[j]-1); k++){
						pts[0].x = o_ldata[j][k].x;			pts[0].y = o_ldata[j][k].y;
						pts[0].z = o_ldata[j][k].z;			pts[1].x = o_ldata[j][k+1].x;
						pts[1].y = o_ldata[j][k+1].y;		pts[1].z = o_ldata[j][k+1].z;
						if(pts[0].x != pts[1].x || pts[0].y != pts[1].y || pts[0].z != pts[1].z)
							clip_line_plane(this, &pts, pla, np, ((plane*)co)->GetVec());
						}
					}
				if(nli) is_valid = true;
				if(o_ldata) {
					for(j = 0; j < o_nli; j++) if(o_ldata[j]) free(o_ldata[j]);
					free(o_ldata);
					}
				if(o_nldata) free(o_nldata);
				o_nli = nli;		nli = 0;
				o_nldata = nldata;	nldata = 0L;
				o_ldata = ldata;	ldata = 0L;
				if(!o_nli) return;				//line is completly hidden
				}
			if(is_valid || i==0){
				nli = o_nli;		o_nli = 0;
				nldata = o_nldata;	o_nldata = 0L;
				ldata = o_ldata;	o_ldata = 0L;
				}
			break;
		default:
			nli = o_nli;		o_nli = 0;
			nldata = o_nldata;	o_nldata = 0L;
			ldata = o_ldata;	o_ldata = 0L;
			break;
			}
		if(pts) free(pts);
		}
	if(o_ldata) {
		for(i = 0; i < o_nli; i++) if(o_ldata[i]) free(o_ldata[i]);
		free(o_ldata);
		}
	if(o_nldata) free(o_nldata);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// define a drop line in 3D space
DropLine3D::DropLine3D(GraphObj *par, DataObj *d, fPOINT3D *p1, int xc,
		int xr, int yc, int yr, int zc, int zr):GraphObj(par, d)
{
	FileIO(INIT_VARS);
	Id = GO_DROPL3D;
	memcpy(&fPos, p1, sizeof(fPOINT3D));
	if(xc >= 0 || xr >= 0 || yc >= 0 || yr >= 0 || zc >= 0 || zr >= 0) {
		if(ssRef = (POINT*)malloc(sizeof(POINT)*3)) {
			ssRef[0].x = xc;	ssRef[0].y = xr;
			ssRef[1].x = yc;	ssRef[1].y = yr;
			ssRef[2].x = zc;	ssRef[2].y = zr;
			cssRef = 3;
			}
		}
	bModified = false;
	type = 0x01;
}

DropLine3D::DropLine3D(int src):GraphObj(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
	bModified = false;
}

DropLine3D::~DropLine3D()
{
	if(bModified) Undo.InvalidGO(this);
	if(mo) DelBitmapClass(mo);		mo = 0L;
	Command(CMD_FLUSH, 0L, 0L);
}

void
DropLine3D::DoPlot(anyOutput *o)
{
	fPOINT3D fip, fp, fp1;
	POINT3D p1, p2;
	int i;

	if(!parent || !o || !o->fvec2ivec(&fPos, &fip)) return;
	if(mo) DelBitmapClass(mo);		mo = 0L;
	for(i = 0; i < 6; i++){
		if(ls[i]) delete(ls[i]);
		ls[i] = 0L;
		}
	p1.x = iround(fip.fx);	p1.y = iround(fip.fy);	p1.z = iround(fip.fz);
	rDims.left = rDims.right = p1.x;	rDims.top = rDims.bottom = p1.y;
	for(i = 0; i < 6; i++) {
		fp.fx = fPos.fx;	fp.fy = fPos.fy;	fp.fz = fPos.fz;
		if(type & (1 << i)){
			switch (i) {
			case 0:	fp.fy = parent->GetSize(SIZE_BOUNDS_YMIN);	break;
			case 1:	fp.fy = parent->GetSize(SIZE_BOUNDS_YMAX);	break;
			case 2:	fp.fz = parent->GetSize(SIZE_BOUNDS_ZMIN);	break;
			case 3:	fp.fz = parent->GetSize(SIZE_BOUNDS_ZMAX);	break;
			case 4:	fp.fx = parent->GetSize(SIZE_BOUNDS_XMIN);	break;
			case 5:	fp.fx = parent->GetSize(SIZE_BOUNDS_XMAX);	break;
				}
			o->fvec2ivec(&fp, &fp1);		p2.x = iround(fp1.fx);
			p2.y = iround(fp1.fy);			p2.z = iround(fp1.fz);
			UpdateMinMaxRect(&rDims, p2.x, p2.y);
			if(ls[i] = new line_segment(this, data, &Line, &p1, &p2)) ls[i]->DoPlot(o);
			mpts[i][0].x = p1.x;	mpts[i][0].y = p1.y;
			mpts[i][1].x = p2.x;	mpts[i][1].y = p2.y;
			}
		}
	IncrementMinMaxRect(&rDims, 5);
}

void
DropLine3D::DoMark(anyOutput *o, bool mark)
{
	int i;

	if(mark) {
		if(mo) DelBitmapClass(mo);					mo = 0L;
		memcpy(&mrc, &rDims, sizeof(RECT));
		IncrementMinMaxRect(&mrc, 6 + o->un2ix(Line.width));
		mo = GetRectBitmap(&mrc, o);
		for(i = 0; i < 6; i++) {
			if(type & (1 << i)){
				InvertLine(mpts[i], 2, &Line, 0L, o, mark);
				}
			}
		o->UpdateRect(&mrc, false);
		}
	else RestoreRectBitmap(&mo, &mrc, o);
}

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

	switch (cmd) {
	case CMD_FLUSH:
		for(i = 0; i < 6; i++){
			if(ls[i]) delete(ls[i]);
			ls[i] = 0L;
			}
		if(ssRef) free(ssRef);		ssRef = 0L;
		if(name) free(name);		name = 0L;
		return true;
	case CMD_SCALE:
		Line.patlength *= ((scaleINFO*)tmpl)->sy.fy;		Line.width *= ((scaleINFO*)tmpl)->sy.fy;
		return true;
	case CMD_DL_TYPE:
		if(tmpl && *((int*)tmpl)) type = *((int*)tmpl);
		return true;
	case CMD_DL_LINE:
		if(tmpl) memcpy(&Line, tmpl, sizeof(LineDEF));
		return true;
	case CMD_SET_DATAOBJ:
		Id = GO_DROPL3D;
		data = (DataObj *)tmpl;
		return true;
	case CMD_REDRAW:
		//Note: this command is issued either by Undo (no output given) or
		//  by Plot3D::DoPlot after sorting all objects (output specified)
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(IsInRect(&rDims, mev->x, mev->y) && !CurrGO) {
				for(i = 0; i < 6; i++) {
					if(ls[i] && ls[i]->ObjThere(mev->x, mev->y)){
						o->ShowMark(this, MRK_GODRAW);
						return true;
						}
					}
				}
			break;
			}
		break;
	case CMD_UPDATE:
		if(ssRef && cssRef >2 && data) {
			data->GetValue(ssRef[0].y, ssRef[0].x, &fPos.fx);
			data->GetValue(ssRef[1].y, ssRef[1].x, &fPos.fy);
			data->GetValue(ssRef[2].y, ssRef[2].x, &fPos.fz);
			return true;
			}
		return false;
	case CMD_AUTOSCALE:
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH) {
			((Plot*)parent)->CheckBounds3D(fPos.fx, fPos.fy, fPos.fz);
			return true;
			}
		break;
	case CMD_SET_GO3D:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// define an arrow in 3D space
Arrow3D::Arrow3D(GraphObj *par, DataObj *d, fPOINT3D *p1, fPOINT3D *p2, int xc1,
		int xr1, int yc1, int yr1, int zc1, int zr1, int xc2, int xr2, int yc2, 
		int yr2, int zc2, int zr2):GraphObj(par, d)
{
	FileIO(INIT_VARS);
	Id = GO_ARROW3D;
	memcpy(&fPos1, p1, sizeof(fPOINT3D));	memcpy(&fPos2, p2, sizeof(fPOINT3D));
	if(xc1 >= 0 || xr1 >= 0 || yc1 >= 0 || yr1 >= 0 || zc1 >= 0 || zr1 >= 0 || 
		xc2 >= 0 || xr2 >= 0 || yc2 >= 0 || yr2 >= 0 || zc2 >= 0 || zr2 >= 0) {
		if(ssRef = (POINT*)malloc(sizeof(POINT)*6)) {
			ssRef[0].x = xc1;	ssRef[0].y = xr1;
			ssRef[1].x = yc1;	ssRef[1].y = yr1;
			ssRef[2].x = zc1;	ssRef[2].y = zr1;
			ssRef[3].x = xc2;	ssRef[3].y = xr2;
			ssRef[4].x = yc2;	ssRef[4].y = yr2;
			ssRef[5].x = zc2;	ssRef[5].y = zr2;
			cssRef = 6;
			}
		}
	bModified = false;
}

Arrow3D::Arrow3D(int src):GraphObj(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
	bModified = false;
}

Arrow3D::~Arrow3D()
{
	if(bModified) Undo.InvalidGO(this);
	Command(CMD_FLUSH, 0L, 0L);
}

bool
Arrow3D::SetSize(int select, double value)
{
	switch(select & 0xfff) {
	case SIZE_ARROW_LINE:
		Line.width = value;
		return true;
	case SIZE_ARROW_CAPWIDTH:
		cw = value;
		return true;
	case SIZE_ARROW_CAPLENGTH:
		cl = value;
		return true;
		}
	return false;
}

bool
Arrow3D::SetColor(int select, DWORD col)
{
	switch(select & 0xfff) {
	case COL_ARROW:
		Line.color = col;
		return true;
		}
	return false;
}

void
Arrow3D::DoPlot(anyOutput *o)
{
	double si, csi, tmp, cwr, clr, d, d1, d2;
	fPOINT3D fip1, fip2, tria[3];
	POINT3D p1, p2, cp1, cp2;
	FillDEF fill;
	int i;

	if(!parent || !o || !o->fvec2ivec(&fPos1, &fip1) ||!o->fvec2ivec(&fPos2, &fip2)) return;
	for(i = 0; i < 3; i++){
		if(ls[i]) delete(ls[i]);
		ls[i] = 0L;
		}
	p1.x = iround(fip1.fx);	p1.y = iround(fip1.fy);	p1.z = iround(fip1.fz);
	p2.x = iround(fip2.fx);	p2.y = iround(fip2.fy);	p2.z = iround(fip2.fz);
	if(p1.x == p2.x && p1.y == p2.y && p1.z == p2.z) return;	//zero length arrow
	rDims.left = rDims.right = p1.x;	rDims.top = rDims.bottom = p1.y;
	UpdateMinMaxRect(&rDims, p2.x, p2.y);
	IncrementMinMaxRect(&rDims, 5);
	if(ls[0] = new line_segment(this, data, &Line, &p1, &p2)) ls[0]->DoPlot(o);
	mpts[0][0].x = p1.x;	mpts[0][0].y = p1.y;	mpts[0][1].x = p2.x;	mpts[0][1].y = p2.y;
	if(p1.x == p2.x && p1.y == p2.y) return;			//zero length in 2D
	if((type & 0xff) == ARROW_NOCAP) return;			//no cap;
	//calculate sine and cosine for cap
	si = fip1.fy-fip2.fy;
	tmp = fip2.fx - fip1.fx;
	si = si/sqrt(si*si + tmp*tmp);
	csi = fip2.fx-fip1.fx;
	tmp = fip2.fy - fip1.fy;
	csi = csi/sqrt(csi*csi + tmp*tmp);
	//cap corners
	d1 = (d = fip2.fx - fip1.fx) * d;
	d1 += ((d = fip2.fy - fip1.fy) * d);
	d2 = d1 + ((d = fip2.fz - fip1.fz) * d);
	d1 = sqrt(d1);	d2 = sqrt(d2);	d = d1/d2;
	cwr = cw;	clr = cl*d;
	cp1.x = p2.x - o->un2ix(csi*clr + si*cwr/2.0);
	cp1.y = p2.y + o->un2iy(si*clr - csi*cwr/2.0);
	cp2.x = p2.x - o->un2ix(csi*clr - si*cwr/2.0);
	cp2.y = p2.y + o->un2iy(si*clr + csi*cwr/2.0);
	cp1.z = cp2.z = p2.z;
	mpts[1][0].x = p2.x;	mpts[1][0].y = p2.y;	mpts[1][1].x = cp1.x;	mpts[1][1].y = cp1.y;
	mpts[2][0].x = p2.x;	mpts[2][0].y = p2.y;	mpts[2][1].x = cp2.x;	mpts[2][1].y = cp2.y;
	if((type & 0xff) == ARROW_LINE) {
		if(ls[1] = new line_segment(this, data, &Line, &p2, &cp1)) ls[1]->DoPlot(o);
		if(ls[2] = new line_segment(this, data, &Line, &p2, &cp2)) ls[2]->DoPlot(o);
		}
	else if((type & 0xff) == ARROW_TRIANGLE) {
		fill.type = FILL_NONE;	fill.color = Line.color;
		fill.scale = 1.0;		fill.hatch = 0L;
		tria[0].fz = tria[1].fz = tria[2].fz = fip2.fz;
		tria[0].fx = cp1.x;		tria[0].fy = cp1.y;
		tria[1].fx = fip2.fx;	tria[1].fy = fip2.fy;
		tria[2].fx = cp2.x;		tria[2].fy = cp2.y;
		if(cap = new plane(this, data, tria, 3, &Line, &fill))cap->DoPlot(o);
	}
}

void
Arrow3D::DoMark(anyOutput *o, bool mark)
{
	int i;

	if(mark) {
		for(i = 0; i < 3; i++) {
			if(ls[i]){
				InvertLine(mpts[i], 2, &Line, 0L, o, mark);
				}
			}
		}
	else if(parent) parent->Command(CMD_REDRAW, 0L, o);
}

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

	switch (cmd) {
	case CMD_FLUSH:
		for(i = 0; i < 3; i++){
			if(ls[i]) delete(ls[i]);
			ls[i] = 0L;
			}
		if(cap) delete(cap);	cap = 0L;
		if(ssRef) free(ssRef);		ssRef = 0L;
		if(name) free(name);		name = 0L;
		return true;
	case CMD_SCALE:
		Line.patlength *= ((scaleINFO*)tmpl)->sy.fy;		Line.width *= ((scaleINFO*)tmpl)->sy.fy;
		 cw *= ((scaleINFO*)tmpl)->sy.fy;					cl *= ((scaleINFO*)tmpl)->sy.fy;
		return true;
	case CMD_SET_DATAOBJ:
		Id = GO_ARROW3D;
		data = (DataObj *)tmpl;
		return true;
	case CMD_MRK_DIRTY:			//from Undo ?
	case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(IsInRect(&rDims, mev->x, mev->y) && !CurrGO) {
				for(i = 0; i < 3; i++) {
					if(ls[i] && ls[i]->ObjThere(mev->x, mev->y)){
						o->ShowMark(this, MRK_GODRAW);
						return true;
						}
					}
				}
			break;
			}
		break;
	case CMD_ARROW_ORG3D:
		if(tmpl) memcpy(&fPos1, tmpl, sizeof(fPOINT3D));
		return true;
	case CMD_ARROW_TYPE:
		if(tmpl) type = *((int*)tmpl);
		return true;
	case CMD_UPDATE:
		if(ssRef && cssRef >5 && data) {
			data->GetValue(ssRef[0].y, ssRef[0].x, &fPos2.fx);
			data->GetValue(ssRef[1].y, ssRef[1].x, &fPos2.fy);
			data->GetValue(ssRef[2].y, ssRef[2].x, &fPos2.fz);
			data->GetValue(ssRef[3].y, ssRef[3].x, &fPos1.fx);
			data->GetValue(ssRef[4].y, ssRef[4].x, &fPos1.fy);
			data->GetValue(ssRef[5].y, ssRef[5].x, &fPos1.fz);
			return true;
			}
		return false;
	case CMD_AUTOSCALE:
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH) {
			((Plot*)parent)->CheckBounds3D(fPos1.fx, fPos1.fy, fPos1.fz);
			((Plot*)parent)->CheckBounds3D(fPos2.fx, fPos2.fy, fPos2.fz);
			return true;
			}
		break;
	case CMD_SET_GO3D:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// a data line in 3D space
Line3D::Line3D(GraphObj *par, DataObj *d, char *rx, char *ry, char *rz)
	:GraphObj(par, d)
{
	FileIO(INIT_VARS);
	if(rx && rx[0]) x_range = (char*)memdup(rx, (int)strlen(rx)+2, 0L);
	if(ry && ry[0]) y_range = (char*)memdup(ry, (int)strlen(ry)+2, 0L);
	if(rz && rz[0]) z_range = (char*)memdup(rz, (int)strlen(rz)+2, 0L);
	DoUpdate();
	Id = GO_LINE3D;
	bModified = false;
}

Line3D::Line3D(GraphObj *par, DataObj *d, fPOINT3D *pt, int n_pt, int xc1, int xr1, int yc1, int yr1,
		int zc1, int zr1, int xc2, int xr2, int yc2, int yr2, int zc2, int zr2)
	:GraphObj(par, d)
{
	FileIO(INIT_VARS);
	if(pt && n_pt) {
		values = (fPOINT3D*)memdup(pt, n_pt * sizeof(fPOINT3D), 0L);
		nPts = n_pt;
		ls = (line_segment **)calloc(nPts-1, sizeof(line_segment*));
		}
	if(xc1 >= 0 || xr1 >= 0 || yc1 >= 0 || yr1 >= 0 || zc1 >= 0 || zr1 >= 0 || 
		xc2 >= 0 || xr2 >= 0 || yc2 >= 0 || yr2 >= 0 || zc2 >= 0 || zr2 >= 0) {
		if(ssRef = (POINT*)malloc(sizeof(POINT)*6)) {
			ssRef[0].x = xc1;	ssRef[0].y = xr1;
			ssRef[1].x = yc1;	ssRef[1].y = yr1;
			ssRef[2].x = zc1;	ssRef[2].y = zr1;
			ssRef[3].x = xc2;	ssRef[3].y = xr2;
			ssRef[4].x = yc2;	ssRef[4].y = yr2;
			ssRef[5].x = zc2;	ssRef[5].y = zr2;
			cssRef = 6;
			}
		}
	Id = GO_LINE3D;
	bModified = false;
}

Line3D::Line3D(int src):GraphObj(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
	bModified = false;
}

Line3D::~Line3D()
{
	int i;

	if(bModified) Undo.InvalidGO(this);
	if(ls){
		for(i = 0; i < (nPts-1); i++) if(ls[i]) delete(ls[i]);
		free(ls);
		}
	if(pts && npts) free(pts);		pts = 0L;		npts = 0;	cssRef = 0;
	if(x_range) free(x_range);		x_range = 0L;
	if(y_range) free(y_range);		y_range = 0L;
	if(z_range) free(z_range);		z_range = 0L;
	if(values) free(values);		values = 0L;
	if(ssRef) free(ssRef);			ssRef = 0L;
	if(mo) DelBitmapClass(mo);		mo = 0L;
}

void
Line3D::DoPlot(anyOutput *o)
{
	int i, j;
	fPOINT3D fip;
	POINT3D p1, p2;
	POINT np;


	if(mo) DelBitmapClass(mo);		mo = 0L;
	if(ls) {
		if(pts && npts) free(pts);
		npts = 0;	if(!(pts = (POINT*)calloc(nPts, sizeof(POINT))))return;
		for(i = 0; i< nPts; i++) {
			if(!o->fvec2ivec(&values[i], &fip)) return;
			p2.x = iround(fip.fx);	p2.y = iround(fip.fy);	p2.z = iround(fip.fz);
			np.x = p2.x;			np.y = p2.y;
			AddToPolygon(&npts, pts, &np);
			if(i) {
				UpdateMinMaxRect(&rDims, np.x, np.y);
				j = i-1;
				if(ls[j]) delete(ls[j]);
				if(ls[j] = new line_segment(this, data, &Line, &p1, &p2)) {
					ls[j]->DoPlot(o);
					}
				}
			else {
				rDims.left = rDims.right = p2.x;
				rDims.top = rDims.bottom = p2.y;
				}
			p1.x = p2.x;	p1.y = p2.y;	p1.z = p2.z;
			}
		IncrementMinMaxRect(&rDims, 6);
		}
}

void
Line3D::DoMark(anyOutput *o, bool mark)
{
	if(mark) {
		if(mo) DelBitmapClass(mo);					mo = 0L;
		memcpy(&mrc, &rDims, sizeof(RECT));
		IncrementMinMaxRect(&mrc, 6 + o->un2ix(Line.width));
		mo = GetRectBitmap(&mrc, o);
		InvertLine(pts, npts, &Line, &rDims, o, true);
		}
	else RestoreRectBitmap(&mo, &mrc, o);
}

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

	switch (cmd) {
	case CMD_FLUSH:
		if(name) free(name);		name = 0L;
		return true;
	case CMD_SCALE:
		Line.patlength *= ((scaleINFO*)tmpl)->sy.fy;		Line.width *= ((scaleINFO*)tmpl)->sy.fy;
		return true;
	case CMD_SET_LINE:
		if(tmpl) {
			memcpy(&Line, tmpl, sizeof(LineDEF));
			return true;
			}
		return false;
	case CMD_SET_DATAOBJ:
		Id = GO_LINE3D;
		data = (DataObj *)tmpl;
		return true;
	case CMD_REDRAW:
		//Note: this command is issued  by Undo (no output given)
		if(!parent) return false;
		if(!o) return parent->Command(cmd, tmpl, o);
		//Should we ever come here ?
		return true;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(!IsInRect(&rDims, p1.x= mev->x, p1.y=mev->y)|| CurrGO || !o || nPts <2 ||
				!IsCloseToPL(p1, pts, npts))return false;
			o->ShowMark(CurrGO=this, MRK_GODRAW);
			return true;
			}
		return false;
	case CMD_UPDATE:
		if(parent && parent->Id != GO_GRID3D) {
			Undo.DataMem(this, (void**)&values, nPts * sizeof(fPOINT3D), &nPts, UNDO_CONTINUE);
			}
		if(ssRef && cssRef >5 && data && nPts == 2) {
			data->GetValue(ssRef[0].y, ssRef[0].x, &values[0].fx);
			data->GetValue(ssRef[1].y, ssRef[1].x, &values[0].fy);
			data->GetValue(ssRef[2].y, ssRef[2].x, &values[0].fz);
			data->GetValue(ssRef[3].y, ssRef[3].x, &values[1].fx);
			data->GetValue(ssRef[4].y, ssRef[4].x, &values[1].fy);
			data->GetValue(ssRef[5].y, ssRef[5].x, &values[1].fz);
			return true;
			}
		else DoUpdate();
	case CMD_AUTOSCALE:
		if(parent && parent->Id > GO_PLOT && parent->Id < GO_GRAPH){
			if(min.fx == max.fx || min.fy == max.fy){	//z's may be equal !
				min.fx = min.fy = min.fz = HUGE_VAL;
				max.fx = max.fy = max.fz = -HUGE_VAL;
				for(i = 0; i < nPts; i++) {
					if(values[i].fx < min.fx) min.fx = values[i].fx;
					if(values[i].fy < min.fy) min.fy = values[i].fy;
					if(values[i].fz < min.fz) min.fz = values[i].fz;
					if(values[i].fx > max.fx) max.fx = values[i].fx;
					if(values[i].fy > max.fy) max.fy = values[i].fy;
					if(values[i].fz > max.fz) max.fz = values[i].fz;
					}
				}
			((Plot*)parent)->CheckBounds3D(min.fx, min.fy, min.fz);
			((Plot*)parent)->CheckBounds3D(max.fx, max.fy, max.fz);
			return true;
			}
		return false;
	case CMD_SET_GO3D:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
		}
	return false;
}

void
Line3D::DoUpdate()
{
	int n1 = 0, ic = 0, i, j, k, l, m, n;
	double x, y, z;
	AccRange *rX=0L, *rY=0L, *rZ=0L;

	if(!x_range || !y_range || !z_range) return;
	if(values) free(values);	values = 0L;
	if(ls) free(ls);			ls = 0L;
	rX = new AccRange(x_range);
	rY = new AccRange(y_range);
	rZ = new AccRange(z_range);
	min.fx = min.fy = min.fz = HUGE_VAL;	max.fx = max.fy = max.fz = -HUGE_VAL;
	if(rX) n1 = rX->CountItems();
	if(n1 && rY && rZ && (values = (fPOINT3D*)malloc(n1 * sizeof(fPOINT3D)))){
		rX->GetFirst(&i, &j);		rX->GetNext(&i, &j);
		rY->GetFirst(&k, &l);		rY->GetNext(&k, &l);
		rZ->GetFirst(&m, &n);		rZ->GetNext(&m, &n);
		do {
			if(data->GetValue(j, i, &x) && data->GetValue(l, k, &y) && 
				data->GetValue(n, m, &z)){
				values[ic].fx = x;	values[ic].fy = y;	values[ic].fz = z;
				if(x < min.fx) min.fx = x;	if(x > max.fx) max.fx = x;
				if(y < min.fy) min.fy = y;	if(y > max.fy) max.fy = y;
				if(z < min.fz) min.fz = z;	if(z > max.fz) max.fz = z;
				ic++;
				}
			}while(rX->GetNext(&i, &j) && rY->GetNext(&k, &l) && rZ->GetNext(&m, &n));
		nPts = ic;
		if(ic > 1) ls = (line_segment **)calloc(ic-1, sizeof(line_segment*));
		}
	if(rX) delete(rX);	if(rY) delete(rY); if(rZ) delete(rZ);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// the text label class 
Label::Label(GraphObj *par, DataObj *d, double x, double y, TextDEF *td, DWORD flg,
	 int xc, int xr, int yc, int yr, int tc, int tr):GraphObj(par, d)
{
	int cb;

	FileIO(INIT_VARS);
	fPos.fx = x;		fPos.fy = y;		flags = flg;
	if(parent){
		fDist.fx = parent->GetSize(SIZE_LB_XDIST);
		fDist.fy = parent->GetSize(SIZE_LB_YDIST);
		}
	Id = GO_LABEL;
	if(td){
		memcpy(&TextDef, td, sizeof(TextDEF));
		if(td->text && td->text[0]) {
			cb = (int)strlen(td->text)+1;			if(cb < 20) cb = 20;
			TextDef.text = (char*)malloc(cb *sizeof(char));
			rlp_strcpy(TextDef.text, cb, td->text);
			}
		}
	if(xc >= 0 || xr >= 0 || yc >= 0 || yr >= 0 || tc >= 0 || tr >= 0) {
		if(ssRef = (POINT*)malloc(sizeof(POINT)*3)) {
			ssRef[0].x = xc;	ssRef[0].y = xr;
			ssRef[1].x = yc;	ssRef[1].y = yr;
			ssRef[2].x = tc;	ssRef[2].y = tr;
			cssRef = 3;
			}
		}
}

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

Label::~Label()
{
	HideTextCursor();
	Command(CMD_FLUSH, 0L, 0L);
	if(bModified)Undo.InvalidGO(this);
}

double
Label::GetSize(int select)
{
	switch(select){
	case SIZE_CURSORPOS:
		return (double)CursorPos;
	case SIZE_CURSOR_XMIN:
		return (double) (Cursor.right > Cursor.left ? Cursor.left : Cursor.right);
	case SIZE_CURSOR_XMAX:
		return (double) (Cursor.right > Cursor.left ? Cursor.right : Cursor.left);
	case SIZE_CURSOR_YMIN:
		return (double) (Cursor.bottom > Cursor.top ? Cursor.top : Cursor.bottom);
	case SIZE_CURSOR_YMAX:
		return (double) (Cursor.bottom > Cursor.top ? Cursor.bottom : Cursor.top);
	case SIZE_MIN_Z:	case SIZE_MAX_Z:	case SIZE_ZPOS:
		return curr_z;
		}
	return 0.0;
}

bool
Label::SetSize(int select, double value)
{
	switch(select & 0xfff) {
	case SIZE_LB_XDIST:			fDist.fx = value;		return true;
	case SIZE_LB_YDIST:			fDist.fy = value;		return true;
	case SIZE_XPOS:				fPos.fx = value;		return true;
	case SIZE_YPOS:				fPos.fy = value;		return true;
	case SIZE_ZPOS:				curr_z = value;			return is3D = true;
	case SIZE_TEXT:				TextDef.fSize = value;		return true;
		}
	return false;
}

bool
Label::SetColor(int select, DWORD col)
{
	switch(select & 0xfff) {
	case COL_TEXT:
		if(select & UNDO_STORESET) {
			Undo.ValDword(this, &TextDef.ColTxt, UNDO_CONTINUE);
			bModified = true;
			}
		TextDef.ColTxt = col;			bBGvalid = false;
		return true;
	case COL_BG:
		bgcolor = col;	bBGvalid = true;
		return true;
		}
	return false;
}

void
Label::DoPlot(anyOutput *o)
{
	if(this != CurrGO && m1 >=0 && m2 >=0) {
		m1 = m2 = -1;
		}
	if(is3D && parent && parent->Command(CMD_SET_GO3D, this, o)) return;
	DoPlotText(o);
}

void
Label::DoMark(anyOutput *o, bool mark)
{
	DWORD bgpix[16];
	int i, d, d1, ix, iy, dx, dy, n;
	RECT mrc;
	anyOutput *mo;

	if(mark) {
		//find color with maximum contrast to text
		if(!bBGvalid) {
			mrc.left = rDims.left;		mrc.right = rDims.right;
			mrc.top = rDims.top;		mrc.bottom = rDims.bottom;
			dx = mrc.right - mrc.left;	dy = mrc.bottom - mrc.top;
			if(dx <= 0 || dy <= 0) return;
			if(mo = GetRectBitmap(&mrc, o)) {
				for(i = 0,  ix = 1; ix < dx; ix += dx<<2) {
					for(iy = 1; iy < dy; dy += dy<<2) {
						if(!(mo->oGetPix(pts[ix].x, pts[iy].y, &bgpix[i]))) bgpix[i] = 0x00ffffff;
						i++;
						}
					}
				DelBitmapClass(mo);		n = i;
				}
			bgcolor = bgpix[0];
			d = ColDiff(bgcolor, TextDef.ColTxt);
			for(i = 1; i < n; i++) {
				if(d < (d1 = ColDiff(bgpix[i], TextDef.ColTxt))) {
					d = d1;		bgcolor = bgpix[i];
					}
				}
			if(!d) bgcolor = TextDef.ColTxt ^ 0x00ffffffL;
			bBGvalid = true;
			}
		//in dialogs parent has no parent
		if(parent && parent->parent) o->ShowLine(pts, 5, TextDef.ColTxt);
		ShowCursor(o);	CurrGO = this;		CurrLabel = this;
		}
	else if(m1 >= 0 && m2 >= 0) {
		m1 = m2 = -1;
		parent->Command(CMD_REDRAW, 0L, o);
		}
	else {
		HideTextCursor();				m1 = m2 = -1;
		bgLine.color = bgcolor;			o->SetLine(&bgLine);
		//in dialogs parent has no parent
		if(parent && parent->parent) o->oPolyline(pts, 5);
		IncrementMinMaxRect(&rDims, 3);
		o->UpdateRect(&rDims, false);	IncrementMinMaxRect(&rDims, -3);
		if(CurrLabel == this) CurrLabel = 0L;
		if(parent && parent->Id != GO_MLABEL && (!TextDef.text || !TextDef.text[0]))
			parent->Command(CMD_DELOBJ, (void*)this, o);
		}
}

bool
Label::Command(int cmd, void *tmpl, anyOutput *o)
{
	MouseEvent *mev;
	scaleINFO *scale;
	int i, cb;

	if(cmd != CMD_SET_DATAOBJ && !parent) return false;
	switch (cmd) {
	case CMD_SCALE:
		scale = (scaleINFO*)tmpl;
		if((flags & 0x03)!= LB_X_DATA) fPos.fx *= scale->sx.fy;
		if((flags & 0x30)!= LB_Y_DATA) fPos.fy *= scale->sy.fy;
		fDist.fx *= scale->sx.fy;				fDist.fy *= scale->sy.fy;
		TextDef.fSize *= scale->sx.fy;			TextDef.iSize = 0;
		return true;
	case CMD_HIDEMARK:
		if(m1 >=0 && m2 >=0 && m1 != m2) {
			m1 = m2 = -1;						return true;
			}
		return false;
	case CMD_FLUSH:
		if(CurrLabel == this) CurrLabel = 0L;
		if(TextDef.text) free(TextDef.text);	TextDef.text = 0L;
		if(ssRef) free(ssRef);					ssRef = 0L;
		return true;
	case CMD_POS_FIRST:		case CMD_POS_LAST:
		Undo.ValInt(this, &CursorPos, 0L);
		bModified = true;
		if(o && TextDef.text) {
			CursorPos = (cmd == CMD_POS_LAST) ? (int)strlen(TextDef.text) : 0;
			ShowCursor(o);
			return true;
			}
		return false;
	case CMD_SHIFTLEFT:		case CMD_CURRLEFT:
		if(o && CursorPos >0 && TextDef.text) {
			Undo.ValInt(this, &CursorPos, 0L);
			DrawFmtText.SetText(0L, TextDef.text, 0L, 0L);
			bModified = true;
			if(cmd == CMD_SHIFTLEFT) {
				if(CursorPos && CursorPos == m1) {
					DrawFmtText.cur_left(&CursorPos);
					m1 = CursorPos;		ShowCursor(o);
					}
				else if(CursorPos && CursorPos == m2) {
					DrawFmtText.cur_left(&CursorPos);
					m2 = CursorPos;		ShowCursor(o);
					if(parent) parent->Command(CMD_REDRAW, 0L, o); 
					}
				else if(CursorPos){
					m2 = CursorPos;
					DrawFmtText.cur_left(&CursorPos);
					m1 = CursorPos;		ShowCursor(o);
					}
				}
			else {
				if(m1 >= 0 && m2 >= 0 && parent) {
					m1 = m2 = -1;						parent->Command(CMD_REDRAW, 0L, o);
					}
				DrawFmtText.cur_left(&CursorPos);		ShowCursor(o);
				}
			if(m1 >=0 && m2 >=0 && m1 != m2) DoPlot(o);
			return true;
			}
		return false;
	case CMD_SHIFTRIGHT:	case CMD_CURRIGHT:
		if(o && TextDef.text && CursorPos < (int)strlen(TextDef.text)) {
			Undo.ValInt(this, &CursorPos, 0L);
			DrawFmtText.SetText(0L, TextDef.text, 0L, 0L);
			bModified = true;
			if(cmd == CMD_SHIFTRIGHT) {
				if(CursorPos == m1 && TextDef.text[m1]) {
					DrawFmtText.cur_right(&CursorPos);
					m2 = CursorPos;		ShowCursor(o);
					memcpy(&rm2, &Cursor, sizeof(RECT));
					if(parent) parent->Command(CMD_REDRAW, 0L, o); 
					}
				else if(CursorPos == m2 && TextDef.text[m2]) {
					DrawFmtText.cur_right(&CursorPos);
					m2 = CursorPos;		ShowCursor(o);
					memcpy(&rm2, &Cursor, sizeof(RECT));
					}
				else if(TextDef.text[CursorPos]){
					if(m1 < 0) {
						m1 = CursorPos;		ShowCursor(o);
						}
					DrawFmtText.cur_right(&CursorPos);
					m2 = CursorPos;		ShowCursor(o);
					}
				else return false;
				}
			else {
				if(m1 >= 0 && m2 >= 0 && parent) {
					m1 = m2 = -1;						parent->Command(CMD_REDRAW, 0L, o);
					}
				DrawFmtText.cur_right(&CursorPos);		ShowCursor(o);
				}
			if(m1 >=0 && m2 >=0 && m1 != m2) DoPlot(o);
			return true;
			}
		return false;
	case CMD_ADDCHAR:	case CMD_ADDCHARW:
		SetModified();
		if(tmpl && 8 != *((int*)tmpl)) return AddChar(*((int*)tmpl), o);
		//value 8 == backspace
	case CMD_BACKSP:
		SetModified();
		if(CursorPos <=0 && o) {
			if(parent && parent->Id == GO_MLABEL) {
				parent->Command(CMD_SETFOCUS, this, o);
				return parent->Command(CMD_BACKSP, tmpl, o);
				}
			RedrawEdit(o);
			return true;
			}
		DrawFmtText.SetText(0L, TextDef.text, 0L, 0L);
		DrawFmtText.cur_left(&CursorPos);					//continue as if delete
	case CMD_DELETE:
		SetModified();
		if(TextDef.text && TextDef.text[CursorPos]) {
			Undo.String(this, &TextDef.text, 0L);
			if(cmd == CMD_DELETE) Undo.ValInt(this, &CursorPos, UNDO_CONTINUE);
			if(CheckMark() && m2 < (cb = (int) strlen(TextDef.text))) {
				Undo.ValInt(this, &m1, UNDO_CONTINUE);
				Undo.ValInt(this, &m2, UNDO_CONTINUE);
				rlp_strcpy(TextDef.text + m1, cb, TextDef.text + m2);
				CursorPos = m1;			m1 = m2 = -1;
				}
			else {
				DrawFmtText.SetText(0L, TextDef.text, 0L, 0L);
				cb = CursorPos;		DrawFmtText.cur_right(&cb);
				cb -= CursorPos;	if(cb < 1) cb = 1;
				rlp_strcpy(TextDef.text + CursorPos, TMP_TXT_SIZE, TextDef.text + CursorPos + cb);
				}
			if(o) {
				RedrawEdit(o);		ShowCursor(o);
				}
			}
		else if(TextDef.text && parent->Id == GO_MLABEL) {
			parent->Command(CMD_SETFOCUS, this, o);
			return parent->Command(CMD_DELETE, tmpl, o);
			}
		else o->HideMark();
		break;
	case CMD_GETTEXT:
		if(TextDef.text && TextDef.text[0] && tmpl) {
			rlp_strcpy((char*)tmpl, TMP_TXT_SIZE, TextDef.text);
			return true;
			}
		return false;
	case CMD_SETTEXT:
		if(TextDef.text) free(TextDef.text);		TextDef.text = 0L;
		if(tmpl && *((char*)tmpl)) {
			TextDef.text = (char*)memdup(tmpl, (int)strlen((char*)tmpl)+1, 0);
			}
		return true;
	case CMD_GETTEXTDEF:
		if(!tmpl) return false;
		memcpy(tmpl, &TextDef, sizeof(TextDEF));
		return true;
	case CMD_SETTEXTDEF:
		if(!tmpl)return false;
		memcpy(&TextDef, tmpl, sizeof(TextDEF)-sizeof(char*));
		if(((TextDEF*)tmpl)->text) Command(CMD_SETTEXT, tmpl, o);
		return true;
	case CMD_SET_DATAOBJ:
		Id = GO_LABEL;
		data = (DataObj*)tmpl;
		return true;
	case CMD_UPDATE:
		if(ssRef && cssRef >2 && data) {
			data->GetValue(ssRef[0].y, ssRef[0].x, &fPos.fx);
			data->GetValue(ssRef[1].y, ssRef[1].x, &fPos.fy);
			if(data->GetText(ssRef[2].y, ssRef[2].x, TmpTxt, TMP_TXT_SIZE)) {
				Undo.String(this, &TextDef.text, UNDO_CONTINUE);
				TextDef.text = (char*)realloc(TextDef.text, cb = (int)strlen(TmpTxt)+2);
				if(TmpTxt[0]) rlp_strcpy(TextDef.text, cb, TmpTxt);
				else TextDef.text[0] = 0;
				}
			return true;
			}
		return false;
	case CMD_SELECT:
		if(!o || !tmpl) return false;
		CalcCursorPos(((POINT*)tmpl)->x, ((POINT*)tmpl)->y, o);
		o->ShowMark(this, MRK_GODRAW);
		return true;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_MOVE:
			if((mev->StateFlags & 0x01) && ObjThere(mev->x, mev->y)) {
				i = CursorPos;				CalcCursorPos(mev->x, mev->y, o);
				if(CurrLabel && CurrLabel != this) {
					CurrLabel->Command(CMD_HIDEMARK, tmpl, o);
					}
				CurrGO = CurrLabel = this;
				if(i == CursorPos) return true;
				if(CursorPos > m1 && CursorPos < m2) {
					if(m1 < 0) m1 = CursorPos;
					else if(m2 != CursorPos)m2 = CursorPos;
					parent->Command(CMD_REDRAW, 0L, o);
					}
				else {
					if(m1 < 0) m1 = CursorPos;
					else if(m2 != CursorPos)m2 = CursorPos;
					if(m1 >=0 && m2 >=0 && m1 != m2) DoPlot(o);
					}
				return true;
				}
			break;
		case MOUSE_LBUP:
			if(ObjThere(mev->x, mev->y)) {
				if(parent && parent->Id == GO_MLABEL) parent->Command(CMD_SETFOCUS, this, o);
				CalcCursorPos(mev->x, mev->y, o);		
				if(o->MrkRect && (void*)o->MrkRect == (void*)this) o->MrkMode = MRK_NONE;
				if(m1 < 0) m1 = CursorPos;			ShowCursor(o);
				o->ShowMark(this, MRK_GODRAW);
				}
			else if(m1 >= 0 && m2 >= 0) {
				m1 = m2 = -1;		DoPlot(o);
				}
			break;
			}
		break;
	case CMD_TEXTTHERE:
		if(ObjThere(((MouseEvent *)tmpl)->x, ((MouseEvent *)tmpl)->y)) {
			CalcCursorPos(((MouseEvent *)tmpl)->x, ((MouseEvent *)tmpl)->y, o);
			CalcRect(o);
			m1 = m2 = CursorPos;						CurrGO = this;								
			return true;
			}
		m1 = m2 = -1;
		return false;
	case CMD_AUTOSCALE:
		if(parent->Id >= GO_PLOT && parent->Id < GO_GRAPH
			&& (flags & LB_X_DATA) && (flags & LB_Y_DATA)) {
			((Plot*)parent)->CheckBounds(fPos.fx, fPos.fy);
			return true;
			}
		break;
	case CMD_REDRAW:
		if(is3D && o) {
			DoPlotText(o);
			is3D = false;										//enable edit
			}
		else if(CurrGO == this) {
			if(parent && parent->parent) RedrawEdit(defDisp);	//not a dialog
			else ShowCursor(defDisp);							//dialog !
			}
		else return parent->Command(cmd, tmpl, o);
		return true;
	case CMD_MOVE:
		if(parent && (parent->Id == GO_MLABEL || parent->Id == GO_LEGITEM))
			return parent->Command(cmd, tmpl, o);
		Undo.MoveObj(this, (lfPOINT*)tmpl, 0L);
	case CMD_UNDO_MOVE:
		if(!(flags & 0x03)) fPos.fx += ((lfPOINT*)tmpl)[0].fx;
		if(!(flags & 0x30)) fPos.fy += ((lfPOINT*)tmpl)[0].fy;
		if(o){
			o->StartPage();		parent->DoPlot(o);		o->EndPage();
			}
		return bModified = true;
		}
	return false;
}

void *
Label::ObjThere(int x, int y)
{
	POINT p1;

	if(IsInRect(&rDims, p1.x = x, p1.y = y) && IsInPolygon(&p1, pts, 5))
		return this;
	return 0L;
}

void
Label::Track(POINT *p, anyOutput *o)
{
	POINT *tpts;
	RECT old_rc;
	int i;

	if(!parent || !TextDef.text || !TextDef.text[0]) return;
	m1 = m2 = -1;
	if(parent->Id == GO_MLABEL || parent->Id == GO_LEGITEM){
		parent->Track(p, o);
		return;
		}
	if(o && (tpts = (POINT*)malloc(5*sizeof(POINT)))){
		memcpy(&old_rc, &rDims, sizeof(rDims));
		defs.UpdRect(o, rDims.left, rDims.top, rDims.right, rDims.bottom);
		for(i = 0; i < 5; i++) {
			tpts[i].x = pts[i].x+p->x;	tpts[i].y = pts[i].y+p->y;
			defs.UpdAdd(o, tpts[i].x, tpts[i].y);
			}
		o->ShowLine(tpts, 5, TextDef.ColTxt);
		free(tpts);
		}
}

bool
Label::CalcRect(anyOutput *o)
{
	int rx1, rx, ry;
	fRECT rc, rcc;

	if(parent && parent->Id != GO_MLABEL) o->SetTextSpec(&TextDef);
	DrawFmtText.SetText(0L, TextDef.text, &ix, &iy);
	if(TextDef.text && TextDef.text[0]) {
		if(!(DrawFmtText.oGetTextExtent(o, &rx, &ry, 0))) return false;
		rx++;
		}
	else {
		if(!(o->oGetTextExtent("A", 1, &rx, &ry))) return false;
		rx = 1;
		}
	rx += 4;	rc.Xmin = -2.0f;	rc.Ymin = 0.0f;		rc.Xmax = rx;		rc.Ymax = ry;
	si = sin(TextDef.RotBL *0.01745329252);	csi = cos(TextDef.RotBL *0.01745329252);
	if(TextDef.Align & TXA_HCENTER) {
		rc.Xmin -= rx/2.0-1.0;		rc.Xmax -= rx/2.0-1.0;
		}
	else if(TextDef.Align & TXA_HRIGHT) {
		rc.Xmin -= rx-2.0;			rc.Xmax -= rx-2.0;
		}
	if(TextDef.Align & TXA_VCENTER) {
		rc.Ymin -= ry/2.0;			rc.Ymax -= ry/2.0;
		}
	else if(TextDef.Align & TXA_VBOTTOM) {
		rc.Ymin -= ry;				rc.Ymax -= ry;
		}
	if(DrawFmtText.oGetTextExtent(o, &rx1, &ry, CursorPos)){
		rx = CursorPos ? (int)rx1 : 0;
		}
	else rx = 0;
	rcc.Xmax = rc.Xmin + (double)rx+2.0;	rcc.Ymin = rc.Ymin+2.0;
	rcc.Xmin = rc.Xmin;						rcc.Ymax = rc.Ymax-2.0;
	pts[0].x = iround(rc.Xmin*csi + rc.Ymin*si)+ix;
	pts[0].y = iround(rc.Ymin*csi - rc.Xmin*si)+iy;
	pts[1].x = iround(rc.Xmax*csi + rc.Ymin*si)+ix;
	pts[1].y = iround(rc.Ymin*csi - rc.Xmax*si)+iy;
	pts[2].x = iround(rc.Xmax*csi + rc.Ymax*si)+ix;
	pts[2].y = iround(rc.Ymax*csi - rc.Xmax*si)+iy;
	pts[3].x = iround(rc.Xmin*csi + rc.Ymax*si)+ix;
	pts[3].y = iround(rc.Ymax*csi - rc.Xmin*si)+iy;
	pts[4].x = pts[0].x;	pts[4].y = pts[0].y;
	Cursor.left = iround(rcc.Xmax*csi + rcc.Ymin*si)+ix;
	Cursor.top = iround(rcc.Ymin*csi - rcc.Xmax*si)+iy;
	Cursor.right = iround(rcc.Xmax*csi + rcc.Ymax*si)+ix;
	Cursor.bottom = iround(rcc.Ymax*csi - rcc.Xmax*si)+iy;
	SetMinMaxRect(&rDims, pts[0].x, pts[0].y, pts[1].x, pts[1].y);
	UpdateMinMaxRect(&rDims, pts[2].x, pts[2].y);
	UpdateMinMaxRect(&rDims, pts[3].x, pts[3].y);
	UpdateMinMaxRect(&rDims, pts[4].x, pts[4].y);
	return true;
}

void 
Label::RedrawEdit(anyOutput *o)
{
	FillDEF bgFill = {FILL_NONE, bgcolor, 1.0, 0L, bgcolor};

	if(!o || !parent) return;
	bgLine.color = bgcolor;		o->SetLine(&bgLine);	o->SetFill(&bgFill);
	o->oPolygon(pts, 5);		IncrementMinMaxRect(&rDims, 3);		
	o->UpdateRect(&rDims, false);
	CalcRect(o);			bgLine.color ^= 0x00ffffffL;
	o->SetLine(&bgLine);		o->oPolygon(pts, 5);
	if(parent->Id == GO_MLABEL) {
		if(parent->parent && parent->parent->Id == GO_LEGITEM && parent->parent->parent)
			parent->parent->parent->DoPlot(o);
		else parent->DoPlot(o);
		}
	else if(parent->Id == GO_LEGITEM && parent->parent) parent->parent->DoPlot(o);
	else DoPlot(o);
	o->UpdateRect(&rDims, false);
	DoMark(o, true);			ShowCursor(o);
}

void
Label::SetModified()
{
	AxisDEF *adef;

	bModified = true;
	if(parent && parent->Id==GO_TICK && parent->parent && parent->parent->Id==GO_AXIS){
	adef = ((Axis*)(parent->parent))->GetAxis();
	adef->flags &= ~AXIS_AUTOSCALE;
	}
}

void
Label::DoPlotText(anyOutput *o)
{
	static LineDEF yLine = {0.0, 1.0, 0x0000ffff, 0x0};
	static FillDEF yFill = {0, 0x0000ffff, 1.0, 0L, 0x0000ffff};
	POINT mpts[5];
	int i;

	if(!parent || !o) return;
	if(m1 >= 0 && m2 >= 0 && m1 != m2 && CurrGO == this) {
		i = CursorPos;							CursorPos = m1;
		CalcRect(o);							memcpy(&rm1, &Cursor, sizeof(RECT));
		CursorPos = m2;							CalcRect(o);
		memcpy(&rm2, &Cursor, sizeof(RECT));	CursorPos = i;
		if(CurrGO == this) ShowCursor(o);
		else CalcRect(o);
		if(m2 > m1) {
			mpts[0].x = mpts[4].x = rm1.left;	mpts[1].x = rm1.right;
			mpts[0].y = mpts[4].y = rm1.top;	mpts[1].y = rm1.bottom;
			mpts[2].x = rm2.right;				mpts[2].y = rm2.bottom;
			mpts[3].x = rm2.left;				mpts[3].y = rm2.top;
			}
		else {
			mpts[0].x = mpts[4].x = rm2.left;	mpts[1].x = rm2.right;
			mpts[0].y = mpts[4].y = rm2.top;	mpts[1].y = rm2.bottom;
			mpts[2].x = rm1.right;				mpts[2].y = rm1.bottom;
			mpts[3].x = rm1.left;				mpts[3].y = rm1.top;
			}
		o->SetLine(&yLine);						o->SetFill(&yFill);
		o->oPolygon(mpts, 5, 0L);
		}
	else {
		m1 = m2 = -1;
		if(CurrGO == this) ShowCursor(o);
		}
	defDisp = o;
	if(parent && parent->Id == GO_MLABEL) parent->Command(CMD_SETFOCUS, this, o);
	switch(flags & 0x03) {
	case LB_X_DATA:	ix = o->fx2ix(fPos.fx);		break;
	case LB_X_PARENT: 
		ix = iround(parent->GetSize(SIZE_LB_XPOS));
		break;
	default:
		ix = o->co2ix(fPos.fx + parent->GetSize(SIZE_GRECT_LEFT));
		break;
		}
	switch(flags & 0x30) {
	case LB_Y_DATA:	iy = o->fy2iy(fPos.fy);		break;
	case LB_Y_PARENT: 
		iy = iround(parent->GetSize(SIZE_LB_YPOS));
		break;
	default:
		iy = o->co2iy(fPos.fy +parent->GetSize(SIZE_GRECT_TOP));
		break;
		}
	ix += o->un2ix(fDist.fx);		iy += o->un2iy(fDist.fy);
	TextDef.iSize = o->un2iy(TextDef.fSize);
	o->SetTextSpec(&TextDef);
	if(TextDef.text && TextDef.text[0]){
		DrawFmtText.SetText(o, TextDef.text, &ix, &iy);
		}
	if(!(CalcRect(o))) return;
	if(m1 >= 0 && m2 >= 0 && m1 != m2 && CurrGO == this && fabs(TextDef.RotBL) < 0.01) {
		o->CopyBitmap(mpts[0].x, mpts[0].y, o, mpts[0].x, mpts[0].y,
			mpts[2].x - mpts[0].x, mpts[2].y - mpts[0].y, true);
		}
	if(m1 >= 0 && m2 >= 0) o->UpdateRect(&rDims, false);
}

bool
Label::CheckMark()
{
	int m;

	if(m1 < 0 || m2 < 0 || m1 == m2) return false;
	if(m1 < m2) return true;
	//come here on right to left mark: swap m1 and m2
	m = m1;		m1 = m2;	m2 = m;
	return	true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// a multiline label consists of several Label objects
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
mLabel::mLabel(GraphObj *par, DataObj *d, double x, double y, TextDEF *td, char *txt, 
	int cp, DWORD flg):GraphObj(par, d)
{
	int i;

	memcpy(&TextDef, td, sizeof(TextDEF));
	TextDef.text = 0L;		fPos.fx = x;		fPos.fy = y;
	Lines = 0L;				flags = flg;		lspc = 1.0;
	fDist.fx = 0.0;			fDist.fy = 0.0;
	CurrGO = CurrLabel = 0L;
	if(txt && (Lines = (Label**)calloc(2, sizeof(Label*)))) {
		for(i = 0; i < cp && txt[i]; i ++) TmpTxt[i] = txt[i];
		TmpTxt[i] = 0;
		if(Lines[0] = new 	Label(this, d, x, y, &TextDef, LB_X_PARENT | LB_Y_PARENT))
			Lines[0]->Command(CMD_SETTEXT, TmpTxt, 0L);
		if(Lines[1] = new 	Label(this, d, x, y, &TextDef, LB_X_PARENT | LB_Y_PARENT)){
			Lines[1]->Command(CMD_SETTEXT, txt+cp, 0L);
			CurrGO = CurrLabel = Lines[1];
			}
		nLines = 2;		cli = 1;
		}
	Id = GO_MLABEL;
}

mLabel::mLabel(GraphObj *par, DataObj *d, double x, double y, TextDEF *td, char *txt)
	:GraphObj(par, d)
{
	int i, nll;
	char **llist;

	memcpy(&TextDef, td, sizeof(TextDEF));
	TextDef.text = 0L;		fPos.fx = x;		fPos.fy = y;
	Lines = 0L;				flags = 0L;			Id = GO_MLABEL;
	fDist.fx = 0.0;			fDist.fy = 0.0;		lspc = 1.0;
	CurrGO = CurrLabel = 0L;
	if(txt){
		if((llist=split(txt,'\n',&nll)) && nll && (Lines=(Label**)calloc(nll, sizeof(Label*)))){
			for(i = 0; i < nll; i++) {
				if(llist[i]){
					Lines[i] = new 	Label(this, d, x, y, &TextDef, LB_X_PARENT | LB_Y_PARENT);
					Lines[i]->Command(CMD_SETTEXT, llist[i], 0L);
					free(llist[i]);
					}
				}
			free(llist);	nLines = nll;		cli = 0;
			}
		}
}

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

mLabel::~mLabel()
{
	int i;
	
	Undo.InvalidGO(this);
	if(Lines){
		for(i = 0; i < nLines; i++) if(Lines[i]) DeleteGO(Lines[i]);
		free(Lines);
		}
}

double
mLabel::GetSize(int select)
{
	switch(select){
	case SIZE_LB_XPOS:	return cPos1.fx;
	case SIZE_LB_YPOS:	return cPos1.fy;
	case SIZE_GRECT_TOP:	
		if (parent) return parent->GetSize(select);
		break;
	case SIZE_MIN_Z:	case SIZE_MAX_Z:	case SIZE_ZPOS:
		return curr_z;
	case SIZE_LSPC:
		return lspc;
		}
	return 0.0;
}

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

	switch(select & 0xfff) {
	case SIZE_LB_XDIST:
		undo_flags = CheckNewFloat(&fDist.fx, fDist.fx, value, this, undo_flags);
		return true;
	case SIZE_LB_YDIST:
		undo_flags = CheckNewFloat(&fDist.fy, fDist.fy, value, this, undo_flags);
		return true;
	case SIZE_XPOS:
		undo_flags = CheckNewFloat(&fPos.fx, fPos.fx, value, this, undo_flags);
		return true;
	case SIZE_YPOS:
		undo_flags = CheckNewFloat(&fPos.fy, fPos.fy, value, this, undo_flags);
		return true;
	case SIZE_ZPOS:
		curr_z = value;
		if(Lines) for(i = 0; i < nLines; i++) if(Lines[i]){
			Lines[i]->SetSize(select, value);
			}
		return is3D = true;
	case SIZE_LSPC:
		undo_flags = CheckNewFloat(&lspc, lspc, value, this, undo_flags);
		return true;
		}
	return false;
}

void
mLabel::DoPlot(anyOutput *o)
{
	int i;
	double dh, dx, dy;

	if(!o || !Lines) return;
	undo_flags = 0L;
	if(parent){		//if this object is part of a dialog we dont have a parent
		dx = parent->GetSize(SIZE_GRECT_LEFT);		dy = parent->GetSize(SIZE_GRECT_TOP);
		}
	else dx = dy = 0.0;
	cPos.fx = cPos.fy = 0.0;
	TextDef.iSize = o->un2iy(TextDef.fSize);
	switch(flags & 0x03) {
	case LB_X_DATA:		cPos.fx = o->fx2fix(fPos.fx);				break;
	case LB_X_PARENT:	if(parent) cPos.fx = parent->GetSize(SIZE_LB_XPOS);	break;
	default:
		//if no parent its a dialog
		cPos.fx = parent ? o->co2fix(fPos.fx + dx) : fPos.fx;
		break;
		}
	switch(flags & 0x30) {
	case LB_Y_DATA:		cPos.fy = o->fy2fiy(fPos.fy);				break;
	case LB_Y_PARENT:	if(parent) cPos.fy = parent->GetSize(SIZE_LB_YPOS);	break;
	default:	
		//if no parent its a dialog
		cPos.fy = parent ? o->co2fiy(fPos.fy + dy) : fPos.fy;
		break;
		}
	si = sin(TextDef.RotBL *0.01745329252f);	csi = cos(TextDef.RotBL *0.01745329252f);
	if(TextDef.Align & TXA_VBOTTOM) dh = (double)(nLines-1);
	else if(TextDef.Align & TXA_VCENTER) dh = ((double)(nLines-1))/2.0;
	else dh = 0.0;						dh *= TextDef.fSize;
	cPos.fx -= o->un2fix(dh*si);		cPos.fy -= o->un2fiy(dh*csi);
	memcpy(&cPos1, &cPos, sizeof(lfPOINT));
	if(lspc < 0.5 || lspc > 5) lspc = 1.0;
#ifdef _WINDOWS
	dist.fx = floor(o->un2fix(TextDef.fSize * si * lspc));
	dist.fy = floor(o->un2fiy(TextDef.fSize * csi * lspc));
#else
	dist.fx = floor(o->un2fix(TextDef.fSize * si * 1.2 * lspc));
	dist.fy = floor(o->un2fiy(TextDef.fSize * csi * 1.2 * lspc));
#endif
	o->SetTextSpec(&TextDef);
	rDims.left = rDims.top = rDims.right = rDims.bottom = 0;
	for(i = 0; i < nLines; i++)	if(Lines[i]){
		Lines[i]->Command(CMD_SETTEXTDEF, &TextDef, o);
		Lines[i]->SetSize(SIZE_LB_XDIST, fDist.fx);	Lines[i]->SetSize(SIZE_LB_YDIST, fDist.fy);
		Lines[i]->SetSize(SIZE_XPOS, fPos.fx);		Lines[i]->SetSize(SIZE_YPOS, fPos.fy);
		Lines[i]->moveable = moveable;				Lines[i]->DoPlot(o);
		UpdateMinMaxRect(&rDims, Lines[i]->rDims.left, Lines[i]->rDims.top);
		UpdateMinMaxRect(&rDims, Lines[i]->rDims.right, Lines[i]->rDims.bottom);
		}
	if(CurrLabel && o && Command(CMD_SETFOCUS, CurrLabel, o))
		Lines[cli]->ShowCursor(o);
}

bool
mLabel::Command(int cmd, void *tmpl, anyOutput *o)
{
	int i, j, k;
	fRECT t_cur;
	MouseEvent mev;
	scaleINFO *scale;

	switch (cmd) {
	case CMD_MOUSE_EVENT:		case CMD_TEXTTHERE:
		if(Lines && tmpl && IsInRect(&rDims, ((MouseEvent*)tmpl)->x, ((MouseEvent*)tmpl)->y)) 
			for(i = 0; i<nLines; i++) if(Lines[i] && Lines[i]->Command(cmd, tmpl, o)) return true;
		break;
	case CMD_ADDCHAR:
		if(!tmpl || 13 != *((int*)tmpl)) return false;
		if(!Lines[cli] || !Lines[cli]->Command(CMD_GETTEXT, &TmpTxt, o)) return false;
		k = iround(Lines[cli]->GetSize(SIZE_CURSORPOS));
		if(parent)Undo.ObjConf(this, 0L);
		if(tmpl && Lines && (Lines = (Label**)realloc(Lines, (nLines+1) * sizeof(Label*)))) {
			for(i = nLines-1, j = nLines; i >= cli; i--, j-- ) {
				Lines[j] = Lines[i];
				}
			i++, j++;
			if(Lines[j]) DeleteGO(Lines[j]);	Lines[i] = Lines[j] = 0L;	nLines++;
			if(Lines[j] = new 	Label(this, data, fPos.fx, fPos.fy, &TextDef, LB_X_PARENT | LB_Y_PARENT)){
				Lines[j]->Command(CMD_SETTEXT, TmpTxt+k, o);
				Lines[j]->Command(CMD_POS_FIRST, 0L, o);
				CurrGO = CurrLabel = Lines[j];
				TmpTxt[k] = 0;
				}
			if(Lines[i] = new 	Label(this, data, fPos.fx, fPos.fy, &TextDef, LB_X_PARENT | LB_Y_PARENT))
				Lines[i]->Command(CMD_SETTEXT, TmpTxt, 0L);
			Command(CMD_SETFOCUS, CurrGO, o);
			if(parent) return parent->Command(CMD_REDRAW, 0L, o);
			else if(o) DoPlot(o);
			}
		break;
	case CMD_SETFOCUS:
		for(i = 0; i< nLines; i++) if(Lines[i] == (Label*)tmpl) {
			cli = i;
			cPos1.fx = cPos.fx + dist.fx*i;
			cPos1.fy = cPos.fy + dist.fy*i;
			return true;
			}
		break;
	case CMD_GETTEXT:
		if(tmpl && Lines && nLines && Lines[0]) return Lines[0]->Command(CMD_GETTEXT, tmpl, o);
		break;
	case CMD_ALLTEXT:							//used e.g from dialog text boxes
		if(tmpl && Lines && nLines){
			for(i = j = 0; i < nLines; i++) {
				if(Lines[i] && Lines[i]->Command(CMD_GETTEXT, TmpTxt+500, o) && TmpTxt[500]){
					j += rlp_strcpy((char*)tmpl+j, 500-j, TmpTxt+500);
					((char*)tmpl)[j++] = '\n';
					}
				((char*)tmpl)[j] = 0;
				}
			if(j >2) return true;
			}
		return false;
	case CMD_DELETE:
		cli++;
		//fall through
	case CMD_BACKSP:
		if(cli > 0 && cli < nLines && Lines && Lines[cli] && Lines[cli-1]) {
			Lines[cli-1]->Command(CMD_POS_LAST, 0L, o);
			TmpTxt[0] = 0;
			Lines[cli-1]->Command(CMD_GETTEXT, TmpTxt, o);
			Lines[cli]->Command(CMD_GETTEXT, TmpTxt+strlen(TmpTxt), o);
			Lines[cli-1]->Command(CMD_SETTEXT, TmpTxt, o);
			DeleteGO(Lines[cli]);	Lines[cli] = 0L;
			for(i = cli+1; i < nLines; i++){
				Lines[i-1] = Lines[i], Lines[i]= 0L;
				}
			nLines--;
			CurrGO = CurrLabel = Lines[cli-1];
			if(parent) parent->Command(CMD_REDRAW, 0L, o);
			if(CurrLabel) CurrLabel->RedrawEdit(o);
			return true;
			}
		return false;
	case CMD_GETTEXTDEF:
		if(!tmpl) return false;
		memcpy(tmpl, &TextDef, sizeof(TextDEF));
		return true;
	case CMD_SETTEXTDEF:
		if(!tmpl)return false;
		if(parent)Undo.TextDef(this, &TextDef, undo_flags);
		undo_flags |= UNDO_CONTINUE;
		memcpy(&TextDef, tmpl, sizeof(TextDEF));
		TextDef.text = 0L;
		return true;
	case CMD_SET_DATAOBJ:
		if(Lines) {
			for(i = 0; i< nLines; i++) if(Lines[i]) Lines[i]->Command(cmd, tmpl, o);
			}
		Id = GO_MLABEL;
		data = (DataObj*)tmpl;
		return true;
	case CMD_AUTOSCALE:
		if(parent && parent->Id >= GO_PLOT && parent->Id < GO_GRAPH
			&& (flags & LB_X_DATA) && (flags & LB_Y_DATA)) {
			((Plot*)parent)->CheckBounds(fPos.fx, fPos.fy);
			return true;
			}
		break;
	case CMD_CURRUP:		case CMD_CURRDOWN:
		if(!o) return false;
		o->SetTextSpec(&TextDef);
		Command(CMD_SETFOCUS, CurrGO, o);
		if(cli >= 0 && cli < nLines && Lines && Lines[cli]) {
			t_cur.Xmin = Lines[cli]->GetSize(SIZE_CURSOR_XMIN);
			t_cur.Xmax = Lines[cli]->GetSize(SIZE_CURSOR_XMAX);
			t_cur.Ymin = Lines[cli]->GetSize(SIZE_CURSOR_YMIN);
			t_cur.Ymax = Lines[cli]->GetSize(SIZE_CURSOR_YMAX);
			mev.StateFlags = 0;		mev.Action = MOUSE_LBUP;
			mev.x = iround((t_cur.Xmax+t_cur.Xmin)/2.0);
			mev.y = iround((t_cur.Ymax+t_cur.Ymin)/2.0);
			i = o->un2ix(TextDef.fSize*si);		j = o->un2iy(TextDef.fSize*csi);
			if(cmd == CMD_CURRUP && cli > 0 && Lines[cli-1]) {
				Lines[cli-1]->CalcCursorPos(mev.x -= i, mev.y -= j, o);
				o->ShowMark(CurrGO = CurrLabel = Lines[cli-=1], MRK_GODRAW);
				return Command(CMD_SETFOCUS, Lines[cli], o);
				}
			if(cmd == CMD_CURRDOWN && cli < (nLines-1) && Lines[cli+1]) {
				Lines[cli+1]->CalcCursorPos(mev.x += i, mev.y += j, o);
				o->ShowMark(CurrGO = CurrLabel = Lines[cli+=1], MRK_GODRAW);
				return Command(CMD_SETFOCUS, Lines[cli], o);
				}
			}
		else return false;
		break;
	case CMD_SCALE:
		scale = (scaleINFO*)tmpl;
		if((flags & 0x03)!= LB_X_DATA) fPos.fx *= scale->sx.fy;
		if((flags & 0x30)!= LB_Y_DATA) fPos.fy *= scale->sy.fy;
		fDist.fx *= scale->sx.fy;				fDist.fy *= scale->sy.fy;
		TextDef.fSize *= scale->sx.fy;			TextDef.iSize = 0;
		return true;
	case CMD_SELECT:
		if(o && tmpl) for(i = 0; i < nLines; i++){
			o->SetTextSpec(&TextDef);
			if(Lines[i] && ((POINT*)tmpl)->y > Lines[i]->rDims.top && ((POINT*)tmpl)->y < 
				Lines[i]->rDims.bottom) return Lines[i]->Command(cmd, tmpl, o);
			}
		break;
	case CMD_DELOBJ:
		if(parent && Lines) for(i = 0; i< nLines; i++) 
			if(Lines[i] && Lines[i] == (Label*)tmpl) return parent->Command(cmd, this, o);
		break;
	case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		else if(o) DoPlot(o);
		break;
	case CMD_MOVE:
		if(parent && parent->Id == GO_LEGITEM) return parent->Command(cmd, tmpl, o);
		Undo.MoveObj(this, (lfPOINT*)tmpl, 0L);
	case CMD_UNDO_MOVE:
		if(!(flags & 0x03)) fPos.fx += ((lfPOINT*)tmpl)[0].fx;
		if(!(flags & 0x30)) fPos.fy += ((lfPOINT*)tmpl)[0].fy;
		if(o && parent){
			o->StartPage();		parent->DoPlot(o);		o->EndPage();
			}
		return true;
		}
	return false;
}

void
mLabel::Track(POINT *p, anyOutput *o)
{
	int i;

	if(!parent) return;
	if(parent->Id == GO_LEGITEM){
		parent->Track(p, o);
		return;
		}
	for(i = 0; i < nLines; i++)	if(Lines[i]){
		if(!i) memcpy(&rDims, &Lines[i]->rDims, sizeof(RECT));
		else {
			UpdateMinMaxRect(&rDims, Lines[i]->rDims.left, Lines[i]->rDims.top);
			UpdateMinMaxRect(&rDims, Lines[i]->rDims.right, Lines[i]->rDims.bottom);
			}
		}
	defs.UpdRect(o, rDims.left, rDims.top, rDims.right, rDims.bottom);
	Id = 0L;			//disable reentrance from Label objects
	for(i = 0; i < nLines; i++)	if(Lines[i]) Lines[i]->Track(p, o);
	Id = GO_MLABEL;		//enable entrance from Label objects
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// a rectangular range to accept any text
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
TextFrame::TextFrame(GraphObj *parent, DataObj *data, lfPOINT *p1, lfPOINT *p2, char *txt)
	:GraphObj(parent, data)
{
	FileIO(INIT_VARS);					lspc = 1.0;
	pos1.fx = p1->fx;	pos1.fy = p1->fy;	pos2.fx = p2->fx;	pos2.fy = p2->fy;
	if(txt && txt[0]) {
		text = (unsigned char*)memdup(txt, (int)strlen(txt)+1, 0);
		}
	else if(lines = (unsigned char**)malloc(sizeof(char*))){
		if(lines[0] = (unsigned char*)malloc(TF_MAXLINE))lines[0][0] = 0;
		nlines = 1;
		}
	Id = GO_TEXTFRAME;
}

TextFrame::TextFrame(int src):GraphObj(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
	moveable = 1;				bModified = false;
}

TextFrame::~TextFrame()
{
	int i;

	if(text)free(text);			text = 0L;
	if(drc) delete(drc);		drc = 0L;
	if(tm_rec) free(tm_rec);	tm_rec = 0L;
	if(lines) {
		for(i = 0; i < nlines; i++)	if(lines[i]) free(lines[i]);
		free(lines);			lines = 0L;
		}
	HideTextCursor();
}
double
TextFrame::GetSize(int select)
{
	switch(select) {
	case SIZE_XPOS:		return pos1.fx;
	case SIZE_XPOS+1:	return pos2.fx;
	case SIZE_YPOS:		return pos1.fy;
	case SIZE_YPOS+1:	return pos2.fy;
	case SIZE_GRECT_LEFT:	case SIZE_GRECT_TOP:
	case SIZE_GRECT_RIGHT:	case SIZE_GRECT_BOTTOM:
		if(parent) return parent->GetSize(select);
		break;
		}
	return 0.0;
}

bool
TextFrame::SetSize(int select, double value)
{
	switch(select & 0xfff) {
	case SIZE_XPOS:				pos1.fx = value;			return bResize = true;
	case SIZE_XPOS+1:			pos2.fx = value;			return bResize = true;
	case SIZE_YPOS:				pos1.fy = value;			return bResize = true;
	case SIZE_YPOS+1:			pos2.fy = value;			return bResize = true;
		}
	return false;
}

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

	if(has_m1 && has_m2) TextMark(o, 3);
	has_m1 = has_m2 = false;
	if(!drc) drc = new dragRect(this, 0);
	memcpy(&upd, &rDims, sizeof(RECT));
	if(mark){
		if(drc) drc->DoPlot(o);								ShowCursor(o);
		}
	else if(parent)	parent->DoPlot(o);
	IncrementMinMaxRect(&upd, 6);
	o->UpdateRect(&upd, false);
}

void
TextFrame::DoPlot(anyOutput *o)
{
	int i, j, x1, y1, x2, y2;
	double tmp, dx, dy;

	if(!o) return;
	if(parent){		//if this object is part of a dialog we dont have a parent
		dx = parent->GetSize(SIZE_GRECT_LEFT);			dy = parent->GetSize(SIZE_GRECT_TOP);
		x1 = o->co2ix(pos1.fx+dx);						y1 = o->co2iy(pos1.fy+dy);
		x2 = o->co2ix(pos2.fx+dx);						y2 = o->co2iy(pos2.fy+dy);
		ipad.left = o->un2ix(pad.Xmin);					ipad.right = o->un2ix(pad.Xmax);
		ipad.top = o->un2iy(pad.Ymin);					ipad.bottom = o->un2iy(pad.Ymax);
		}
	else {
		dx = dy = 0.0;
		x1 = (int)pos1.fx;		y1 = (int)pos1.fy;		x2 = (int)pos2.fx;		y2 = (int)pos2.fy;
		ipad.left = ipad.right = ipad.top = ipad.bottom = 4;
		fmt_txt.EditMode(true);
		}
	if(pos1.fx > pos2.fx) {
		tmp = pos2.fx;	pos2.fx = pos1.fx;	pos1.fx = tmp;
		}
	if(pos1.fy > pos2.fy) {
		tmp = pos2.fy;	pos2.fy = pos1.fy;	pos1.fy = tmp;
		}
	o->SetLine(&Line);						o->SetFill(&Fill);
	o->oRectangle(x1, y1, x2, y2, name);
	SetMinMaxRect(&rDims, x1, y1, x2, y2);
	x1 += ipad.left;						y1 += ipad.top;
	TextDef.iSize = o->un2iy(TextDef.fSize);
#ifdef _WINDOWS
	linc = o->un2iy(TextDef.fSize*lspc);
#else
	linc = o->un2iy(TextDef.fSize*lspc*1.2);
#endif
	o->SetTextSpec(&TextDef);				y1 += linc;
	if(text && text[0] && !(lines)) text2lines(o);
	else if(bResize && lines) {
		c_char = lines[cur_pos.y][cur_pos.x];			lines[cur_pos.y][cur_pos.x] = 0x01;
		if(!c_char) lines[cur_pos.y][cur_pos.x+1] = 0x00; 
		lines2text();				text2lines(o);		bResize = false;
		o->ShowMark(this, MRK_GODRAW);
		return;
		}
	if(has_m1 && has_m2) TextMark(o, 1);
	for(i = 0; i < nlines; i++) {
		if(lines[i] && lines[i][0]){
			j = (int)strlen((char*)lines[i]);
			if(lines[i][j-1] == '\n') {
				lines[i][j-1] = 0;
				fmt_txt.SetText(o, (char*)lines[i], &x1, &y1);
				lines[i][j-1] = '\n';
				}
			else fmt_txt.SetText(o, (char*)lines[i], &x1, &y1);
			}
		y1 += linc;
		}
	if(has_m1 && has_m2) TextMark(o, 2);
	bModified = bResize = false;
}

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

	switch (cmd) {
	case CMD_SELECT:
		if(!o || !tmpl) return false;
		CalcCursorPos(((POINT*)tmpl)->x, ((POINT*)tmpl)->y, o);
		if(parent)o->ShowMark(this, MRK_GODRAW);	ShowCursor(o);
		return true;
	case CMD_COPY:
		return CopyText(o, false);
	case CMD_SAVEPOS:
		bModified = true;
		Undo.SaveLFP(this, &pos1, 0L);
		Undo.SaveLFP(this, &pos2, UNDO_CONTINUE);
		return true;
	case CMD_SCALE:
		if(tmpl) {
			pos1.fx = ((scaleINFO*)tmpl)->sx.fx + pos1.fx * ((scaleINFO*)tmpl)->sx.fy;
			pos1.fy = ((scaleINFO*)tmpl)->sy.fx + pos1.fy * ((scaleINFO*)tmpl)->sy.fy;
			pos2.fx = ((scaleINFO*)tmpl)->sx.fx + pos2.fx * ((scaleINFO*)tmpl)->sx.fy;
			pos2.fy = ((scaleINFO*)tmpl)->sy.fx + pos2.fy * ((scaleINFO*)tmpl)->sy.fy;
			TextDef.fSize *= ((scaleINFO*)tmpl)->sy.fy;		TextDef.iSize = 0;
			pad.Xmax *= ((scaleINFO*)tmpl)->sx.fy;			pad.Xmin *= ((scaleINFO*)tmpl)->sx.fy;
			pad.Ymax *= ((scaleINFO*)tmpl)->sy.fy;			pad.Ymin *= ((scaleINFO*)tmpl)->sy.fy;
			Line.width *= ((scaleINFO*)tmpl)->sy.fy;		Line.patlength *= ((scaleINFO*)tmpl)->sy.fy;
			FillLine.width *= ((scaleINFO*)tmpl)->sy.fy;	FillLine.patlength *= ((scaleINFO*)tmpl)->sy.fy;
			Fill.scale *= ((scaleINFO*)tmpl)->sy.fy;
			}
		return true;
	case CMD_GETTEXT:	case CMD_ALLTEXT:
		if(lines && lines[0] && lines[0][0] && nlines) {
			lines2text();	i = rlp_strcpy((char*)tmpl, TMP_TXT_SIZE-2, (char*)text);
			while(i && ((char*)tmpl)[i-1] == '\n') i--; 
			((char*)tmpl)[i++] = '\n';						((char*)tmpl)[i] = 0;
			return true;
			}
		return false;
	case CMD_ADDCHARW:	case CMD_ADDCHAR:
		if(tmpl && o) AddChar(o, *((int *)tmpl));
		return true;
	case CMD_DELETE:
		if(o) DelChar(o);
		return true;
	case CMD_SHIFTLEFT:
		if(!has_m1) {
			m1_pos.x = cur_pos.x;		m1_pos.y = cur_pos.y;
			}
		has_m1 = has_m2 = true;
	case CMD_CURRLEFT:
		if(!(lines[cur_pos.y]))return false;
		if(cmd == CMD_CURRLEFT && has_m1 && has_m2) TextMark(o, 3);
		if(cur_pos.x > 0) {
			i = 0;					fmt_txt.SetText(0L,(char*)lines[cur_pos.y], &i, &i);
			i = cur_pos.x;			fmt_txt.cur_left(&i);
			cur_pos.x = i;
			}
		else if(cur_pos.y) {
			cur_pos.y--;			cur_pos.x = (int)strlen((char*)lines[cur_pos.y]);
			}
		if(cmd == CMD_SHIFTLEFT){
			m2_pos.x = cur_pos.x;		m2_pos.y = cur_pos.y;
			DoPlot(o);					o->UpdateRect(&rDims, false);
			}
		ShowCursor(o);
		return false;
	case CMD_SHIFTRIGHT:
		if(!has_m1) {
			m1_pos.x = cur_pos.x;		m1_pos.y = cur_pos.y;
			}
		has_m1 = has_m2 = true;
	case CMD_CURRIGHT:
		if(!(lines[cur_pos.y]))return false;
		if(cmd == CMD_CURRIGHT && has_m1 && has_m2) TextMark(o, 3);
		if(cur_pos.x >= (int)strlen((char*)lines[cur_pos.y])) {
			if(cur_pos.y < (nlines-1)) {
				cur_pos.y++;		cur_pos.x = 0;
				}
			else if(cur_pos.y == (nlines-1) && lines[cur_pos.y][0]) {
				if(!(lines = (unsigned char**)realloc(lines, (nlines+1)*sizeof(char*))))return false;
				if(!(lines[cur_pos.y+1] = (unsigned char*)malloc(TF_MAXLINE))) return false;
				cur_pos.y++;		cur_pos.x = 0;		nlines++;
				lines[cur_pos.y][0] = 0;
				}
			}
		else {
			i = 0;						fmt_txt.SetText(0L,(char*)lines[cur_pos.y], &i, &i);
			i = cur_pos.x;				fmt_txt.cur_right(&i);
			cur_pos.x = i;
			}
		if(cmd == CMD_SHIFTRIGHT){
			m2_pos.x = cur_pos.x;		m2_pos.y = cur_pos.y;
			DoPlot(o);					o->UpdateRect(&rDims, false);
			}
		ShowCursor(o);
		return false;
	case CMD_SHIFTUP:
		if(!has_m1) {
			m1_pos.x = cur_pos.x;		m1_pos.y = cur_pos.y;
			}
		has_m1 = has_m2 = true;
	case CMD_CURRUP:
		if(cmd == CMD_CURRUP && has_m1 && has_m2) TextMark(o, 3);
		if(cur_pos.y && o) {
			i = ((Cursor.bottom + Cursor.top)>>1)-linc;
			CalcCursorPos(Cursor.left, i, o);
			if(cmd == CMD_SHIFTUP){
				m2_pos.x = cur_pos.x;	m2_pos.y = cur_pos.y;
				DoPlot(o);				o->UpdateRect(&rDims, false);
				}
			ShowCursor(o);
			return true;
			}
		return false;
	case CMD_SHIFTDOWN:
		if(!has_m1) {
			m1_pos.x = cur_pos.x;		m1_pos.y = cur_pos.y;
			}
		has_m1 = has_m2 = true;
	case CMD_CURRDOWN:
		if(cmd == CMD_CURRDOWN && has_m1 && has_m2) TextMark(o, 3);
		if(cur_pos.y < (nlines-1) && o && lines[cur_pos.y][0]) {
			i = ((Cursor.bottom + Cursor.top)>>1)+linc;
			if(i >= (rDims.bottom-ipad.bottom)) return false;
			CalcCursorPos(Cursor.left, i, o);
			if(cmd == CMD_SHIFTDOWN){
				m2_pos.x = cur_pos.x;	m2_pos.y = cur_pos.y;
				DoPlot(o);				o->UpdateRect(&rDims, false);
				}
			ShowCursor(o);
			return true;
			}
		return false;
	case CMD_POS_FIRST:
		if(has_m1 && has_m2) TextMark(o, 3);
		cur_pos.x = 0;									ShowCursor(o);
		return true;
	case CMD_POS_LAST:
		if(has_m1 && has_m2) TextMark(o, 3);
		cur_pos.x = (int)strlen((char*)lines[cur_pos.y]);
		ShowCursor(o);
		return true;
	case CMD_TEXTTHERE:
		if(IsInRect(&rDims, ((MouseEvent *)tmpl)->x, ((MouseEvent *)tmpl)->y)) return true;
		return false;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(IsInRect(&rDims, mev->x, mev->y) && (!(CurrGO) || !has_m2) && o){
				CalcCursorPos(mev->x, mev->y, o);
				if(has_m1 && has_m2) {
					if(o)DoPlot(o);						o->UpdateRect(&rDims, false);
					CurrGO = this;						ShowCursor(o);
					return true;
					}
				return o->ShowMark(this, MRK_GODRAW);
				}
			else if(CurrGO == this && o){
				ShowCursor(o);
				return IsInRect(&rDims, mev->x, mev->y);
				}
			break;
		case MOUSE_LBDOWN:
			if(has_m1 && has_m2) {
				has_m1 = has_m2 = false;
				if(o)DoPlot(o);							o->UpdateRect(&rDims, false);
				}
			has_m1 = has_m2 = false;
		case MOUSE_MOVE:
			if(!(mev->StateFlags & 0x1)) return false;
			if(!IsInRect(&rDims, mev->x, mev->y))return false;
			if(!CurrGO) CurrGO = this;
			CalcCursorPos(mev->x, mev->y, o);
			if(!has_m1) {
				m1_pos.x = m2_pos.x = cur_pos.x;		m1_pos.y = m2_pos.y = cur_pos.y;
				return has_m1 = true;
				}
			else if(cur_pos.x != m2_pos.x || cur_pos.y != m2_pos.y) {
				m2_pos.x = cur_pos.x;					m2_pos.y = cur_pos.y;
				has_m2 = true;
				if(o)DoPlot(o);							o->UpdateRect(&rDims, false);
				return true;
				}
			return true;
			}
		return false;
	case CMD_SET_DATAOBJ:
		Id = GO_TEXTFRAME;
		return true;
	case CMD_PASTE:
		return DoPaste(o);
	case CMD_MOVE:
		bModified = true;
		Undo.MoveObj(this, (lfPOINT*)tmpl, 0L);
	case CMD_UNDO_MOVE:
		pos1.fx += ((lfPOINT*)tmpl)[0].fx;	pos1.fy += ((lfPOINT*)tmpl)[0].fy;
		pos2.fx += ((lfPOINT*)tmpl)[0].fx;	pos2.fy += ((lfPOINT*)tmpl)[0].fy;
		CurrGO = this;
	case CMD_REDRAW:
		if(parent){
			if(o && cmd == CMD_REDRAW) DoPlot(o);		//trickle down ?
			if(!o && cmd == CMD_REDRAW) {
				//coming from Undo
				bModified = true;
				if(lines) {
					for(i = 0; i < nlines; i++) if(lines[i]) free(lines[i]);
					free(lines);			lines = 0L;
					}
				HideTextCursor();			cur_pos.x = cur_pos.y = 0;
				parent->Command(CMD_REDRAW, tmpl, o);
				}
			else parent->Command(CMD_REDRAW, tmpl, o);
			}
		return true;
	case CMD_SETSCROLL:
		if(parent) return parent->Command(cmd, tmpl, o);
	case CMD_GETTEXTDEF:
		if(!tmpl) return false;
		memcpy(tmpl, &TextDef, sizeof(TextDEF));
		return true;
	case CMD_SETTEXTDEF:
		if(!tmpl)return false;
		memcpy(&TextDef, tmpl, sizeof(TextDEF));
		TextDef.text = 0L;
		return true;
	case CMD_SETTEXT:
		if(lines) {
			for(i = 0; i < nlines; i++) if(lines[i]) free(lines[i]);
			free(lines);			lines = 0L;
			}
		if(text) free(text);		text = 0L;
		if(tmpl && *((char*)tmpl)) text = (unsigned char*)memdup(tmpl, (int)strlen(((char*)tmpl))+1, 0);
		return true;
		}
	return false;
}

void
TextFrame::Track(POINT *p, anyOutput *o)
{
	POINT tpts[5];
	RECT old_rc;
	double dx, dy;

	if(o && parent){
		dx = parent->GetSize(SIZE_GRECT_LEFT);		dy = parent->GetSize(SIZE_GRECT_TOP);
		memcpy(&old_rc, &rDims, sizeof(rDims));
		o->UpdateRect(&rDims, false);
		tpts[0].x = tpts[1].x = tpts[4].x = o->co2ix(pos1.fx+dx)+p->x;		
		tpts[0].y = tpts[3].y = tpts[4].y = o->co2iy(pos1.fy+dy)+p->y;
		tpts[1].y = tpts[2].y = o->co2iy(pos2.fy+dy)+p->y;
		tpts[2].x = tpts[3].x = o->co2ix(pos2.fx+dx)+p->x;
		UpdateMinMaxRect(&rDims, tpts[0].x, tpts[0].y);
		UpdateMinMaxRect(&rDims, tpts[2].x, tpts[2].y);	
		if(old_rc.left != rDims.left || old_rc.right != rDims.right || old_rc.top !=
			rDims.top || old_rc.bottom != rDims.bottom)IncrementMinMaxRect(&rDims, 3);
		o->ShowLine(tpts, 5, Line.color);
		}
}

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

void
TextFrame::text2lines(anyOutput *o)
{
	int i, j, w, h, cl, maxlines, maxw;
	char tmp_line[TF_MAXLINE];
	bool hasMark = false;

	if(lines) {
		for(i = 0; i < nlines; i++) if(lines[i]) free(lines[i]);
		free(lines);			lines = 0L;
		}
	has_m1 = has_m2 = false;
	nlines = 0;
	if(!text || !text[0]) return;
	maxlines = (rDims.bottom -rDims.top)/linc +1;
	maxw = rDims.right - rDims.left - ipad.left - ipad.right;
	lines = (unsigned char**)calloc(maxlines, sizeof(char*));
	for(cl = cpos = w = h = 0; cl < maxlines && text[cpos]; cl++) {
		if(!(lines[cl] = (unsigned char*)malloc(TF_MAXLINE))) return;
		for(i = 0; text[cpos] && i < TF_MAXLINE; i++) {
			tmp_line[i] = text[cpos++];				tmp_line[i+1] = 0;
			fmt_txt.SetText(0L, tmp_line, &w, &h);
			if(!(fmt_txt.oGetTextExtent(o, &w, &h, i))) break;
			if(tmp_line[i] == '\n'){
				break;													//new line character found
				}
			if(tmp_line[i] < ' ') switch(tmp_line[i]) {
				case 0x01:
					if(c_char == 0 && text[cpos]) c_char = text[cpos++];	//cursor at end of line
					hasMark = true;				break;
				case 0x02:	case 0x03:
					hasMark = true;				break;
				}
			else if(w >= maxw){
				for(j = i; j > (i>>1); j--) {
					if(tmp_line[j] == ' ' || tmp_line[j] == '-' || tmp_line[j] < ' ') break;
					}
				if(j == (i>>1)) {
					cpos--;							tmp_line[i] = 0;
					}
				else {
					for(tmp_line[j+1] = 0; j < i; j++, cpos--);
					}
				break;
				}
			}
		if(i || tmp_line[i] == '\n') rlp_strcpy((char*)lines[cl], TF_MAXLINE, tmp_line);
		else lines[cl][0] = 0;
		}
	nlines = cl;
	if(hasMark)procTokens();
}

void
TextFrame::lines2text()
{
	int i;

	if(text) free(text);		cpos = 0;
	text = (unsigned char*)malloc(csize = 1000);
	for(i = 0; i < nlines; i++) {
		if(lines[i] && lines[i][0]) add_to_buff((char**)&text, &cpos, &csize, (char*)lines[i], 0);
		}
}

void
TextFrame::AddChar(anyOutput *o, int c)
{
	int i, j, h, w, maxw;
	bool brd;
	char *txt1;

	if(cur_pos.y >= nlines) return;
	if(!lines || !lines[cur_pos.y]) return;
	if(c == '\r') c = '\n';
	if(has_m1 && has_m2){
		TmpTxt[0] = c;		TmpTxt[1] = 0;
		if(c == 8) ReplMark(o, "");
		else if(c >= 32 || c == '\n') ReplMark(o, TmpTxt);
		return;
		}
	else if(!text) {
		lines2text();
		Undo.TextBuffer(this, &csize, &cpos, &text, 0L, o);
		}
	maxw = rDims.right - rDims.left - ipad.left - ipad.right;
	i = j = (int)strlen((char*)lines[cur_pos.y])+1;
	has_m1 = has_m2 = false;
	if(c >= 32 || c == '\n') {
		if(c > 254 && (txt1 = (char*)malloc(10))) {
#ifdef USE_WIN_SECURE
			w = sprintf_s(txt1, 10, "&#%d;", c);
#else
			w = sprintf(txt1, "&#%d;", c);
#endif
			for(j = j+w; j>0; j--) {
				lines[cur_pos.y][j] = lines[cur_pos.y][j-w];
				if((j-w) == cur_pos.x){
					for(i = 0; i < w; i++) lines[cur_pos.y][j-w+i] = txt1[i];
					j = 0;					cur_pos.x += w;
					}
				}
			free(txt1);
			}
		else while(j) {
			lines[cur_pos.y][j] = lines[cur_pos.y][j-1];	j--;
			if(j == cur_pos.x){
				lines[cur_pos.y][j] = c;					j = 0;
				cur_pos.x++;
				}
			}
		fmt_txt.SetText(0L, (char*)lines[cur_pos.y], &j, &j);
		fmt_txt.oGetTextExtent(o, &w, &h, i);				brd = false;
		if(cur_pos.x > 2 && (c == '>' || c == ';')) {
			if(c == '>' && fmt_txt.leftTag((char*)lines[cur_pos.y],cur_pos.x-1) >= 0) brd =true;
			if(c == ';' && fmt_txt.ucLeft((char*)lines[cur_pos.y],cur_pos.x-1, 0L, 0L) > 0) brd =true;
			}
		if(brd || w >= maxw || c == '\n' || (c == ' ' && w >=(maxw>>1))) {
			c_char = lines[cur_pos.y][cur_pos.x];			lines[cur_pos.y][cur_pos.x] = 0x01;
			if(!c_char) lines[cur_pos.y][cur_pos.x+1] = 0x00; 
			lines2text();	Undo.TextBuffer(this, &csize, &cpos, &text, 0L, o);		text2lines(o);
			}
		}
	else if(c == 8 && (cur_pos.x || cur_pos.y)) {			//Backspace
		if(!cur_pos.x) Command(CMD_CURRLEFT, 0L, o);
		Command(CMD_CURRLEFT, 0L, o);						DelChar(o);
		return;
		}
	else return;
	DoPlot(o);		o->UpdateRect(&rDims, false);			ShowCursor(o);
}

void
TextFrame::DelChar(anyOutput *o)
{
	int i, cb, x;

	if(has_m1 && has_m2){
		ReplMark(o, "");	return;
		}
	if(lines[cur_pos.y][cur_pos.x]) {
		lines2text();			Undo.TextBuffer(this, &csize, &cpos, &text, 0L, o);
		cb = x = cur_pos.x;			fmt_txt.cur_right(&x);
		cb = x - cb;	if(cb < 1) cb = 1;
		for(i = cur_pos.x; lines[cur_pos.y][i]; i++) {
			if(!(lines[cur_pos.y][i] = lines[cur_pos.y][i+cb])) break;
			}
		c_char = lines[cur_pos.y][cur_pos.x];			lines[cur_pos.y][cur_pos.x] = 0x01;
		if(!c_char) lines[cur_pos.y][cur_pos.x+1] = 0x00; 
		lines2text();			text2lines(o);
		DoPlot(o);				o->UpdateRect(&rDims, false);
		ShowCursor(o);			return;
		}
	else if(cur_pos.y < (nlines-1)){
		cur_pos.y++;			cur_pos.x = 0;
		DelChar(o);				return;
		}
}

void
TextFrame::ReplMark(anyOutput *o, char *ntext)
{
	int i, j;

	if(!has_m1 || !has_m2 || !o || !ntext) return;
	lines2text();
	Undo.TextBuffer(this, &csize, &cpos, &text, 0L, o);
	for(i = cpos = 0; i < nlines && i < m1_cpos.y; i++) {
		if(lines[i] && lines[i][0]) add_to_buff((char**)&text, &cpos, &csize, (char*)lines[i], 0);
		}
	if(m1_cpos.x)add_to_buff((char**)&text, &cpos, &csize, (char*)lines[i], m1_cpos.x);
	add_to_buff((char**)&text, &cpos, &csize, ntext, 0);			j = cpos;
	if(m1_cpos.y == m2_cpos.y)add_to_buff((char**)&text, &cpos, &csize, (char*)(lines[i]+m2_cpos.x), 0);
	for( ; i < nlines && i < m2_cpos.y; i++);
	if(i == m2_cpos.y && m2_cpos.y > m1_cpos.y)add_to_buff((char**)&text, &cpos, &csize, (char*)(lines[i]+m2_cpos.x), 0);
	for(i++; i < nlines; i++) {
		if(lines[i] && lines[i][0]) add_to_buff((char**)&text, &cpos, &csize, (char*)lines[i], 0);
		}
	if(tm_rec)free(tm_rec);			tm_rec = 0L;		has_m1 = has_m2 = false;
	if(text[j]) {
		c_char = text[j];						text[j] = 0x01;
		}
	else if(i < (nlines-1) && lines[i+1] && lines[i+1][0]) {
		c_char = lines[i+1][0];					lines[i+1][0] = 0x01;
		}
	else if(j == cpos){
		c_char = 0;		text[cpos++] = 0x01;		text[cpos] = 0;
		}
	else cur_pos.x = cur_pos.y = 0;
	text2lines(o);		DoPlot(o);	o->UpdateRect(&rDims, false);
	ShowCursor(o);
}

void
TextFrame::procTokens()
{
	int i, j;

	for(i = 0; i < nlines; i++) {
		if(lines[i] && lines[i][0]){
			for(j = 0; lines[i][j]; j++) switch(lines[i][j]) {
				case 0x01:
					cur_pos.y = i;				cur_pos.x = j;
					lines[i][j] = c_char;		c_char = '?';
					break;
				case 0x02:
					m1_pos.y = i;				m1_pos.x = j;
					if(m1_char == 0x01) {
						lines[i][j] = c_char;	c_char = '?';
						cur_pos.y = i;			cur_pos.x = j;
						}
					else lines[i][j] = m1_char;
					has_m1 = true;				m1_char = '?';
					break;
				case 0x03:
					m2_pos.y = i;				m2_pos.x = j;
					if(m2_char == 0x01) {
						lines[i][j] = c_char;	c_char = '?';
						cur_pos.y = i;			cur_pos.x = j;
						}
					else lines[i][j] = m2_char;
					has_m2 = true;				m2_char = '?';
					break;
					}
			}
		}
}

bool
TextFrame::DoPaste(anyOutput *o)
{
	int i, j, k;
	char *ntxt;
	unsigned char *ptxt;

	if((ptxt = PasteText()) && ptxt[0]) {
		lines2text();	Undo.TextBuffer(this, &csize, &cpos, &text, 0L, o);
		if(!(ntxt = (char*)malloc(strlen((char*)ptxt)+1))) return false;
		for(i = j = cpos = 0; ptxt[i]; i++) {
			if(ptxt[i] >= ' ' || ptxt[i] == '\n') ntxt[j++] = ptxt[i];
			else if(ptxt[i] == 9)ntxt[j++] = ' ';		//convert tab->space 
			}
		ntxt[j] = 0;
		if(!ntxt[0]) {
			free(ntxt);				return false;
			}
		if(has_m1 && has_m2) {
			ReplMark(o, ntxt);		free(ntxt);
			return true;
			}
		m1_char = ntxt[0];			ntxt[0] = 0x02;
		ntxt[j] = 0;				if(text) free(text);
		if(!(text = (unsigned char*)malloc(csize = 1000)))return false;
		for(i = k = 0, text[0] = 0; i < nlines; i++) {
			if(lines[i] && lines[i][0]){
				if(i == cur_pos.y) {
					if(cur_pos.x) add_to_buff((char**)&text, &cpos, &csize, (char*)lines[i], cur_pos.x);
					add_to_buff((char**)&text, &cpos, &csize, ntxt, 0);	k = cpos;
					add_to_buff((char**)&text, &cpos, &csize, (char*)lines[i]+cur_pos.x, 0);
					free(ntxt);		ntxt = 0L;
					}
				else add_to_buff((char**)&text, &cpos, &csize, (char*)lines[i], 0);
				}
			}
		if(ntxt) {
			add_to_buff((char**)&text, &cpos, &csize, ntxt, 0);			k = cpos;
			}
		m2_char = 0x01;
		if(text[k]) {
			c_char = text[k];						text[k] = 0x03;
			}
		else if(i < (nlines-1) && lines[i+1] && lines[i+1][0]) {
			c_char = lines[i+1][0];					lines[i+1][0] = 0x03;
			}
		else if(k == cpos){
			c_char = 0;		text[cpos++] = 0x03;	text[cpos] = 0;
			}
		text2lines(o);						DoPlot(o);
		o->UpdateRect(&rDims, false);
		ShowCursor(o);						if(ntxt) free(ntxt);
		}
	return false;
}
void
TextFrame::TextMark(anyOutput *o, int mode)
{
	int i, j, w, h;
	LineDEF ld;
	FillDEF fd;

	if(m1_pos.y > m2_pos.y) {
		m1_cpos.y = m2_pos.y;				m1_cpos.x = m2_pos.x;
		m2_cpos.y = m1_pos.y;				m2_cpos.x = m1_pos.x;
		}
	else if(m1_pos.y == m2_pos.y  && m1_pos.x > m2_pos.x) {
		m1_cpos.x = m2_pos.x;				m2_cpos.x = m1_pos.x;
		m1_cpos.y = m2_cpos.y = m1_pos.y;
		}
	else {
		m1_cpos.y = m1_pos.y;				m1_cpos.x = m1_pos.x;
		m2_cpos.y = m2_pos.y;				m2_cpos.x = m2_pos.x;
		}
	if(!has_m1 || !has_m2 || !o) return;
	if(m1_pos.y == m2_pos.y && m1_pos.x == m2_pos.x) return;
	if(mode == 1){							//create background mark
		if(tm_rec)free(tm_rec);				tm_rec = 0L;
		if((tm_c = m2_cpos.y - m1_cpos.y +1)<1) return;
		if(!(tm_rec = (RECT*)malloc(tm_c * sizeof(RECT))))return;
		for(i = w = 0, j = m1_cpos.y; j <= m2_cpos.y; i++, j++) {
			h = TextDef.iSize;
			if(j == m1_cpos.y) {
				fmt_txt.SetText(0L, (char*)lines[j], 0L, 0L);
				if(m1_cpos.x) fmt_txt.oGetTextExtent(o, &w, &h, m1_cpos.x);
				else w = 0;
				tm_rec[0].left = w + rDims.left + ipad.left + 1;			
				fmt_txt.SetText(0L, (char*)(lines[j]+m1_cpos.x), 0L, 0L);
				fmt_txt.oGetTextExtent(o, &w, &h, 0);
				tm_rec[0].right = tm_rec[0].left + w;
				}
			if(j == m2_cpos.y) {
				fmt_txt.SetText(0L, (char*)lines[j], 0L, 0L);
				if(m2_cpos.x) fmt_txt.oGetTextExtent(o, &w, &h, m2_cpos.x);
				else w = 0;
				tm_rec[i].right = w + rDims.left + ipad.left - 1;			
				if(m2_cpos.y > m1_cpos.y) {
					tm_rec[i].left = rDims.left + ipad.left;
					}
				}
			else if(j < m2_cpos.y && j > m1_cpos.y) {
				tm_rec[i].left = rDims.left + ipad.left;
				tm_rec[i].top = j * linc + rDims.top + ipad.top;
				fmt_txt.SetText(0L, (char*)lines[j], 0L, 0L);
				fmt_txt.oGetTextExtent(o, &w, &h, 0);
				tm_rec[i].right = w + rDims.left + ipad.left;			
				}
			tm_rec[i].top = j * linc + rDims.top + ipad.top;
			tm_rec[i].bottom = tm_rec[i].top + linc;
			}
		ld.color = 0x0000ffff;				ld.patlength = 1.0;
		ld.pattern = 0x0L;					ld.width = 0;
		fd.color = fd.color2 = ld.color;	fd.hatch = 0L;
		fd.scale = 1.0;						fd.type = 0;
		o->SetLine(&ld);					o->SetFill(&fd);
		for(i = 0; i < tm_c; i++) {
			o->oRectangle(tm_rec[i].left, tm_rec[i].top, tm_rec[i].right, tm_rec[i].bottom, 0L);
			}
		}
	if(mode == 2){							//invert rectangles
		if(tm_rec) for(i = 0; i < tm_c; i++) {
			o->CopyBitmap(tm_rec[i].left, tm_rec[i].top, o, tm_rec[i].left, tm_rec[i].top,
				tm_rec[i].right - tm_rec[i].left, tm_rec[i].bottom - tm_rec[i].top, true);
			}
		}
	if(mode == 3){							//clear mark
		if(tm_rec)free(tm_rec);			tm_rec = 0L;
		tm_c = 0;						has_m1 = has_m2 = false;
		DoPlot(o);						o->UpdateRect(&rDims, false);
		}
}

bool
TextFrame::CopyText(anyOutput *o, bool b_cut)
{
	int i, csize, pos = 0;
	char *ntxt;

	if(!lines || !lines[0][0] || !o) return false;
	if(!has_m1 || !has_m2) {
		m1_pos.x = m1_pos.y = 0;		m2_pos.y = nlines-1;
		if(lines[nlines-1]) m2_pos.x = (int)strlen((char*)lines[nlines-1]);
		else m2_pos.x = 0;				has_m1 = has_m2 = true;
		DoPlot(o);						o->UpdateRect(&rDims, false);
		return CopyText(o, false);
		}
	if(!(ntxt = (char*)malloc(csize = 1000))) return false;
	if(m1_cpos.y == m2_cpos.y) {
		add_to_buff(&ntxt, &pos, &csize, (char*)(lines[m1_cpos.y]) + m1_cpos.x, m2_cpos.x - m1_cpos.x);
		::CopyText(ntxt, pos);			free(ntxt);
		}
	else if(m1_cpos.y < m2_cpos.y){
		add_to_buff(&ntxt, &pos, &csize, (char*)(lines[m1_cpos.y]) + m1_cpos.x, 0);
		for(i = m1_cpos.y; i < m2_cpos.y; i++) {
			add_to_buff(&ntxt, &pos, &csize, (char*)(lines[i]), 0);
			}
		add_to_buff(&ntxt, &pos, &csize, (char*)(lines[i]), m2_pos.x);
		::CopyText(ntxt, pos);			free(ntxt);
		}
	else {
		free(ntxt);						return false;
		}
	if(b_cut) ReplMark(o, "");
	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// The segment object is either a pie slice or a ring segment
//
segment::segment(GraphObj *par, DataObj *d, lfPOINT *c, double r1, double r2,
				 double a1, double a2):GraphObj(par, d)
{
	FileIO(INIT_VARS);
	segFill.hatch = &segFillLine;
	segFill.color = 0x00c0c0c0L;
	fCent.fx = c->fx;		fCent.fy = c->fy;
	radius1 = r1;			radius2 = r2;
	angle1 = a1;			angle2 = a2;
	Id = GO_SEGMENT;
	bModified = false;
}

segment::segment(int src):GraphObj(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
	bModified = false;
}

segment::~segment()
{
	if(pts && nPts) free(pts);			pts = 0L;
	if(bModified) Undo.InvalidGO(this);
	if(mo) DelBitmapClass(mo);			mo = 0L;
}
	
bool 
segment::SetSize(int select, double value)
{
	switch(select & 0xfff) {
	case SIZE_XPOS:		fCent.fx = value;		break;
	case SIZE_YPOS:		fCent.fy = value;		break;
	case SIZE_RADIUS1:	radius1 = value;		break;
	case SIZE_RADIUS2:	radius2 = value;		break;
	case SIZE_ANGLE1:	angle1 = value;			break;
	case SIZE_ANGLE2:	angle2 = value;			break;
	default:			return false;
		}
	return true;
}

void
segment::DoPlot(anyOutput *o)
{
	double dsize, dpt, frx1, frx2, dtmp, da, dda, sia, csia;
	int i, n, npt = 12;
	POINT np, of, cp,  *tmppts;

	if(!o || angle1 == angle2) return;
	if(mo) DelBitmapClass(mo);		mo = 0L;
	dsize = angle1 > angle2 ? angle1 -angle2 : (angle1+360.0)-angle2;
	dpt = dsize*0.01745329252;		//degrees to rad
	frx1 = (double)o->un2fix(radius1);
	frx2 = (double)o->un2fix(radius2);
	dtmp = frx1*dpt;
	npt += (int)(dtmp < dsize ? dtmp : dsize);
	dtmp = frx2*dpt;
	npt += (int)(dtmp < dsize ? dtmp : dsize);
	if(!(pts = (POINT*)malloc(npt*sizeof(POINT))))return;
	nPts = 0;
	n = (dtmp < dsize) ? (int)dtmp : (int)dsize;
	while (n<2) n++;
	da = angle1*0.01745329252;
	dda = (dsize*0.01745329252)/(double)n;
	sia = sin(0.5*((angle2 < angle1 ? angle1 : angle1 +360) + angle2) * 0.01745329252);
	csia = cos(0.5*((angle2 < angle1 ? angle1 : angle1 +360) + angle2) * 0.01745329252);
	of.x = o->un2ix(shift * csia);
	of.y = - (o->un2iy(shift * sia));
	cp.x = o->co2ix(fCent.fx + (parent ? parent->GetSize(SIZE_GRECT_LEFT): 0.0));
	cp.y = o->co2iy(fCent.fy + (parent ? parent->GetSize(SIZE_GRECT_TOP): 0.0));
	for(i = 0; i < n; i++) {
		sia = sin(da);					csia = cos(da);
		np.x = of.x + cp.x;				np.y = of.y + cp.y;
		np.x += o->un2ix(csia*radius2);	np.y -= o->un2iy(sia*radius2);
		AddToPolygon(&nPts, pts, &np);
		da -= dda;
		}
	sia = sin(angle2 *0.01745329252);
	csia = cos(angle2 *0.01745329252);
	np.x = of.x + cp.x;					np.y = of.y + cp.y;
	np.x += o->un2ix(csia*radius2);		np.y -= o->un2iy(sia*radius2);
	AddToPolygon(&nPts, pts, &np);
	dtmp = frx1*dpt;
	n = dtmp < dsize ? (int)dtmp : (int)dsize;
	da = angle2*0.01745329252;
	if(n>1)dda = (dsize*0.01745329252)/(double)n;
	else dda = 0.0;
	for(i = 0; i < n; i++) {
		sia = sin(da);					csia = cos(da);
		np.x = of.x + cp.x;				np.y = of.y + cp.y;
		np.x += o->un2ix(csia*radius1);	np.y -= o->un2iy(sia*radius1);
		AddToPolygon(&nPts, pts, &np);
		da += dda;
		}
	sia = sin(angle1 *0.01745329252);	csia = cos(angle1 *0.01745329252);
	np.x = of.x + cp.x;					np.y = of.y + cp.y;
	np.x += o->un2ix(csia*radius1);		np.y -= o->un2iy(sia*radius1);
	AddToPolygon(&nPts, pts, &np);
	if(nPts <3) return;
	AddToPolygon(&nPts, pts, &pts[0]);	//close polygon
	o->SetLine(&segLine);				o->SetFill(&segFill);
	o->oPolygon(pts, nPts);
	if(tmppts = (POINT*)realloc(pts, nPts *sizeof(POINT))) pts = tmppts;
	SetMinMaxRect(&rDims, pts[0].x, pts[0].y, pts[1].x, pts[1].y);
	for(i = 2; i < nPts; i++) UpdateMinMaxRect(&rDims, pts[i].x, pts[i].y);
	i = 3*o->un2ix(segLine.width);		//increase size of rectangle for marks
	IncrementMinMaxRect(&rDims, i+6);
}

void
segment::DoMark(anyOutput *o, bool mark)
{
	if(mark){
		if(mo) DelBitmapClass(mo);			mo = 0L;
		memcpy(&mrc, &rDims, sizeof(RECT));
		mo = GetRectBitmap(&mrc, o);
		InvertLine(pts, nPts, &segLine, &rDims, o, mark);
		}
	else if(mo) RestoreRectBitmap(&mo, &mrc, o);
}

bool
segment::Command(int cmd, void *tmpl, anyOutput *o)
{
	FillDEF *TmpFill;
	LegItem *leg;

	switch (cmd) {
	case CMD_SCALE:
		fCent.fx *= ((scaleINFO*)tmpl)->sx.fy;			fCent.fy *= ((scaleINFO*)tmpl)->sx.fy;
		radius1 *= ((scaleINFO*)tmpl)->sx.fy;			radius2 *= ((scaleINFO*)tmpl)->sx.fy;
		segLine.width *= ((scaleINFO*)tmpl)->sx.fy;		segLine.patlength *= ((scaleINFO*)tmpl)->sx.fy;
		segFillLine.width *= ((scaleINFO*)tmpl)->sx.fy;	segFillLine.patlength *= ((scaleINFO*)tmpl)->sx.fy;
		segFill.scale *= ((scaleINFO*)tmpl)->sx.fy;		shift *= ((scaleINFO*)tmpl)->sx.fy;
		return true;
	case CMD_LEGEND:
		if(tmpl) {
			leg = new LegItem(this, data, 0L, &segLine, &segFill, 0L);
			if(!((Legend*)tmpl)->Command(CMD_DROP_OBJECT, leg, o)) DeleteGO(leg);
			}
		return true;
	case CMD_MOUSE_EVENT:
		switch (((MouseEvent*)tmpl)->Action) {
		case MOUSE_LBUP:
			if(ObjThere(((MouseEvent*)tmpl)->x, ((MouseEvent*)tmpl)->y)) 
				return o->ShowMark(CurrGO=this, MRK_GODRAW);
			}
		return false;
	case CMD_SET_DATAOBJ:
		Id = GO_SEGMENT;
		return true;
	case CMD_REDRAW:
		return parent ? parent->Command(cmd, tmpl, o) : false;
	case CMD_SEG_FILL:
		TmpFill = (FillDEF *)tmpl;
		if(TmpFill) {
			segFill.type = TmpFill->type;			segFill.color = TmpFill->color;
			segFill.scale = TmpFill->scale;
			if(TmpFill->hatch) memcpy(&segFillLine, TmpFill->hatch, sizeof(LineDEF));
			}
		return true;
	case CMD_SEG_LINE:
		if(tmpl) memcpy(&segLine, tmpl, sizeof(LineDEF));
		return true;
	case CMD_SEG_MOVEABLE:
		if(tmpl) moveable = *((int*)tmpl);
		return true;
	case CMD_SHIFT_OUT:
		if(tmpl) shift = *((double*)tmpl);
		return true;
	case CMD_MOVE:
		bModified = true;
		Undo.MoveObj(this, (lfPOINT*)tmpl, 0L);
	case CMD_UNDO_MOVE:
		fCent.fx += ((lfPOINT*)tmpl)[0].fx;		fCent.fy += ((lfPOINT*)tmpl)[0].fy;
		if(parent)parent->Command(CMD_REDRAW, 0L, o);
		return true;
		}
	return false;
}

void *
segment::ObjThere(int x, int y)
{
	bool bFound = false;
	POINT p1;

	if(IsInRect(&rDims, p1.x = x, p1.y = y)) {
		bFound = IsInPolygon(&p1, pts, nPts);
		if(bFound || IsCloseToPL(p1, pts, nPts)) return this;
		}
	return 0L;
}

void
segment::Track(POINT *p, anyOutput *o)
{
	POINT *tpts;
	RECT old_rc;
	int i;

	if(o && (tpts = (POINT*)malloc(nPts*sizeof(POINT)))){
		memcpy(&old_rc, &rDims, sizeof(rDims));
		o->UpdateRect(&rDims, false);
		for(i = 0; i < nPts; i++) {
			tpts[i].x = pts[i].x+p->x;			tpts[i].y = pts[i].y+p->y;
			UpdateMinMaxRect(&rDims, tpts[i].x, tpts[i].y);
			}
		o->ShowLine(tpts, nPts, segLine.color);
		if(old_rc.left != rDims.left || old_rc.right != rDims.right || old_rc.top !=
			rDims.top || old_rc.bottom != rDims.bottom)IncrementMinMaxRect(&rDims, 3);
		free(tpts);
		}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// the polyline object
//
polyline::polyline(GraphObj *par, DataObj *d, lfPOINT *fpts, int cpts):
	GraphObj(par, d)
{
	double dx = 0.0, dy = 0.0;
	int i;

	FileIO(INIT_VARS);
	if(parent){
		dx = parent->GetSize(SIZE_GRECT_LEFT);	dy = parent->GetSize(SIZE_GRECT_TOP);
		}
	if(cpts && (Values = (lfPOINT*)malloc((nPoints = cpts)* sizeof(lfPOINT)))){
		memcpy(Values, fpts, cpts*sizeof(lfPOINT));
		for(i = 0; i < cpts; i++) {
			Values[i].fx -= dx;		Values[i].fy -= dy;
			}
		}
	Id = GO_POLYLINE;
	bModified = false;
}

polyline::polyline(int src):GraphObj(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
	bModified = false;
}

polyline::~polyline()
{
	Command(CMD_FLUSH, 0L, 0L);
	if(this == CurrGO) CurrGO = 0L;
	if(bModified) Undo.InvalidGO(this);
}

double
polyline::GetSize(int select)
{
	int i;

	if(select >= SIZE_XPOS && select <=  SIZE_XPOS_LAST){
		if((i = select-SIZE_XPOS) >=0 && i <= 200)
			return i < nPoints ? Values[i].fx : Values[nPoints-2].fx;
		}
	if(select >= SIZE_YPOS && select <= SIZE_YPOS_LAST){
		if((i = select-SIZE_YPOS) >=0 && i <= 200)
			return i < nPoints ? Values[i].fy : Values[nPoints-2].fy;
		}
	return parent ? parent->GetSize(select) : 0.0;
}

DWORD
polyline::GetColor(int select)
{
	return pgLine.color;
}

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

	if((select & 0xfff) >= SIZE_XPOS && (select & 0xfff) <=  SIZE_XPOS_LAST){
		if((i = select-SIZE_XPOS) >=0 && i <= 200 && i < (int)nPoints){
			Values[i].fx = value;
			return true;
			}
		}
	if((select & 0xfff) >= SIZE_YPOS && (select & 0xfff) <= SIZE_YPOS_LAST){
		if((i = select-SIZE_YPOS) >=0 && i <= 200 && i < (int)nPoints){
			Values[i].fy = value;
			return true;
			}
		}
	return false;
}

void
polyline::DoPlot(anyOutput *o)
{
	POINT np, *tmppts;
	double dx, dy;
	int i;

	if(!Values || !nPoints || !o || !parent) return;
	if(pts) free(pts);
	dx = parent->GetSize(SIZE_GRECT_LEFT);	dy = parent->GetSize(SIZE_GRECT_TOP);
	if(!(pts = (POINT*)malloc((nPoints+2)*sizeof(POINT))))return;
	for(i = nPts = 0; i < nPoints; i++){
		np.x = o->co2ix(Values[i].fx + dx);		np.y = o->co2iy(Values[i].fy + dy);
		AddToPolygon(&nPts, pts, &np);
		}
	if(type == 1) AddToPolygon(&nPts, pts, &pts[0]);	//close polygon
	if(tmppts = (POINT*)realloc(pts, nPts *sizeof(POINT))) pts = tmppts;
	SetMinMaxRect(&rDims, pts[0].x, pts[0].y, pts[1].x, pts[1].y);
	for(i = 2; i < nPts; i++) UpdateMinMaxRect(&rDims, pts[i].x, pts[i].y);
	i = 3*o->un2ix(pgLine.width)+3;		//increase size of rectangle for marks
	IncrementMinMaxRect(&rDims, i);
	if(this == CurrGO) o->ShowMark(this, MRK_GODRAW);
	else switch(type) {
	case 0:				//line
		o->SetLine(&pgLine);							o->oPolyline(pts, nPts);
		break;
	case 1:				//polygon
		o->SetLine(&pgLine);	o->SetFill(&pgFill);	o->oPolygon(pts, nPts);
		break;
		}
}

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

	memcpy(&upd, &rDims, sizeof(RECT));
	IncrementMinMaxRect(&upd, 6 + o->un2ix(pgLine.width)*4);
	if(mark) {
		o->SetLine(&pgLine);
		if(nPoints < 200){
			switch(type) {
			case 0:				//line
				o->oPolyline(pts, nPts);
			break;
			case 1:				//polygon
				o->SetFill(&pgFill);		o->oPolygon(pts, nPts);
				break;
				}
			ShowPoints(o);
			}
		else InvertLine(pts, nPts, &pgLine, &upd, o, true);;
		o->UpdateRect(&upd, false);
 		}
	else {
		if(parent)	parent->DoPlot(o);
		}
}

bool
polyline::Command(int cmd, void *tmpl, anyOutput *o)
{
	MouseEvent *mev;
	POINT p1;
	bool bFound = false;
	int i;

	switch (cmd) {
	case CMD_MRK_DIRTY:			//issued by Undo
		CurrGO = this;
		bModified = true;
	case CMD_FLUSH:
		if(pHandles) {
			for(i = 0; i < nPoints; i++) if(pHandles[i]) delete(pHandles[i]);
			free(pHandles);		pHandles = 0L;
			}
		if(cmd == CMD_FLUSH && Values && nPoints){
			free(Values);
			Values = 0L;	nPoints = 0;
			}
		if(pts && nPts) free(pts);		pts = 0L;		nPts = 0;
		return true;
	case CMD_SCALE:
		if(Values) for(i = 0; i < nPoints; i++){
			Values[i].fx = ((scaleINFO*)tmpl)->sx.fx + Values[i].fx * ((scaleINFO*)tmpl)->sx.fy;
			Values[i].fy = ((scaleINFO*)tmpl)->sy.fx + Values[i].fy * ((scaleINFO*)tmpl)->sy.fy;
			}
		pgLine.patlength *= ((scaleINFO*)tmpl)->sy.fy;		pgLine.width *= ((scaleINFO*)tmpl)->sy.fy;
		pgFillLine.patlength *= ((scaleINFO*)tmpl)->sy.fy;	pgFillLine.width *= ((scaleINFO*)tmpl)->sy.fy;
		pgFill.scale *= ((scaleINFO*)tmpl)->sy.fy;
		return true;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(!ObjThere(p1.x= mev->x, p1.y=mev->y)|| CurrGO || !o || nPoints <2)return false;
			return o->ShowMark(CurrGO=this, MRK_GODRAW);
			}
		return false;
	case CMD_DELOBJ:
		if(pHandles && tmpl && tmpl == (void*)CurrHandle) {
			for(i = 0; i < nPoints; i++) if(pHandles[i] == CurrHandle) {
				Undo.DataMem(this, (void**)&Values, nPoints * sizeof(lfPOINT), &nPoints, 0L);
				for( ; i < nPoints-1; i++) {
					Values[i].fx = Values[i+1].fx;	Values[i].fy = Values[i+1].fy;
					}
				nPoints--;
				if(pHandles[nPoints])delete(pHandles[nPoints]);
				pHandles[nPoints] = 0L;				CurrHandle = 0L;
				CurrGO = this;						bModified = true;
				return true;
				}
			}
		return false;
	case CMD_SAVEPOS:
		if(tmpl && Values) {
			bModified = true;
			i = *(int*)tmpl;
			if(i >= 0 && i < nPoints) Undo.SaveLFP(this, Values + i, 0L);
			}
		return true;
	case CMD_SET_DATAOBJ:
		Id = type == 1 ? GO_POLYGON : GO_POLYLINE;
		return true;
	case CMD_SETSCROLL:		case CMD_REDRAW:
		if(parent) return parent->Command(cmd, tmpl, o);
		return false;
	case CMD_MOVE:
		bModified = true;
		Undo.MoveObj(this, (lfPOINT*)tmpl, 0L);
	case CMD_UNDO_MOVE:
		for(i = 0; i < nPoints; i++) {
			Values[i].fx += ((lfPOINT*)tmpl)[0].fx;
			Values[i].fy += ((lfPOINT*)tmpl)[0].fy;
			}
		if(o) {
			o->StartPage();		parent->DoPlot(o);		o->EndPage();
			}
		return true;
		}
	return false;
}

void * 
polyline::ObjThere(int x, int y)
{
	bool bFound = false;
	POINT p1;
	int i;
	void *ret;

	if(IsInRect(&rDims, p1.x = x, p1.y = y)) {
		if(CurrGO == this && pHandles) for(i = nPoints-1; i >= 0; i--) 
			if((pHandles[i]) && (ret = pHandles[i]->ObjThere(x, y))) return ret;
		if(type == 1) bFound = IsInPolygon(&p1, pts, nPts);
		if(bFound || IsCloseToPL(p1,pts,nPts)) return this;
		}
	return 0L;
}

void
polyline::Track(POINT *p, anyOutput *o)
{
	POINT *tpts;
	RECT old_rc;
	int i;

	if(o && (tpts = (POINT*)malloc(nPts*sizeof(POINT)))){
		memcpy(&old_rc, &rDims, sizeof(rDims));
		o->UpdateRect(&rDims, false);
		for(i = 0; i < nPts; i++) {
			tpts[i].x = pts[i].x+p->x;
			tpts[i].y = pts[i].y+p->y;
			UpdateMinMaxRect(&rDims, tpts[i].x, tpts[i].y);
			}
		o->ShowLine(tpts, nPts, pgLine.color);
		if(old_rc.left != rDims.left || old_rc.right != rDims.right || old_rc.top !=
			rDims.top || old_rc.bottom != rDims.bottom)IncrementMinMaxRect(&rDims, 3);
		free(tpts);
		}
}

void
polyline::ShowPoints(anyOutput *o)
{
	int i;
	double dx, dy;
	POINT hpts[3];
	LineDEF gl = {0.0, 1.0, 0x00c0c0c0, 0};

	if(nPoints >= 200 || !o) return;
	if(!pHandles && (pHandles = (dragHandle**)calloc(nPoints+4, sizeof(dragHandle*)))){
		for(i = 0; i < nPoints; i++) pHandles[i] = new dragHandle(this, DH_DATA+i);
		}
	if(!pHandles) return;
	if(Id == GO_BEZIER && parent && nPoints > 3) {
		dx = parent->GetSize(SIZE_GRECT_LEFT);			dy = parent->GetSize(SIZE_GRECT_TOP);
		o->SetLine(&gl);
		hpts[0].x = o->co2ix(Values[0].fx+dx);			hpts[0].y = o->co2iy(Values[0].fy+dy);
		hpts[1].x = o->co2ix(Values[1].fx+dx);			hpts[1].y = o->co2iy(Values[1].fy+dy);
		o->oPolyline(hpts, 2);
		for(i = 3; i < (nPoints-2); i += 3) {
			hpts[0].x = o->co2ix(Values[i-1].fx+dx);	hpts[0].y = o->co2iy(Values[i-1].fy+dy);
			hpts[1].x = o->co2ix(Values[i].fx+dx);		hpts[1].y = o->co2iy(Values[i].fy+dy);
			hpts[2].x = o->co2ix(Values[i+1].fx+dx);	hpts[2].y = o->co2iy(Values[i+1].fy+dy);
			o->oPolyline(hpts, 3);
			}
		hpts[0].x = o->co2ix(Values[i-1].fx+dx);		hpts[0].y = o->co2iy(Values[i-1].fy+dy);
		hpts[1].x = o->co2ix(Values[i].fx+dx);			hpts[1].y = o->co2iy(Values[i].fy+dy);
		o->oPolyline(hpts, 2);
		}
	for(i = 0; i < nPoints; i++) if(pHandles[i]) pHandles[i]->DoPlot(o);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Beziers are based on the polyline object
Bezier::Bezier(GraphObj *par, DataObj *d, lfPOINT *fpts, int cpts, int mode, double res):
	polyline(par, d, 0L, 0)
{
	double dx, dy, merr;
	int i;

	type = mode;
	Id = GO_BEZIER;

	if(!parent) return;
	dx = parent->GetSize(SIZE_GRECT_LEFT);	dy = parent->GetSize(SIZE_GRECT_TOP);
	if(type == 0 && (Values = (lfPOINT*)malloc(4 * cpts * sizeof(lfPOINT)))) {
		merr = 0.01 * res / Units[defs.cUnits].convert;
		FitCurve(fpts, cpts, merr);
		Values[nPoints].fx = fpts[cpts-1].fx;	Values[nPoints].fy = fpts[cpts-1].fy;
		for(i = 0; i < nPoints; i++) {
			Values[i].fx -= dx;		Values[i].fy -= dy;
			}
		nPoints ++;
		}
	return;
}

Bezier::Bezier(int src):polyline(0L, 0L, 0L, 0)
{
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
}

void
Bezier::DoPlot(anyOutput *o)
{
	POINT *tmppts;
	double dx, dy;
	int i;

	if(!Values || !nPoints || !o || !parent) return;
	if(pts) free(pts);		pts = 0L;
	dx = parent->GetSize(SIZE_GRECT_LEFT);	dy = parent->GetSize(SIZE_GRECT_TOP);
	if(!(tmppts = (POINT*)malloc((nPoints+2)*sizeof(POINT))))return;
	for(i = 0; i < nPoints; i++){
		tmppts[i].x = o->co2ix(Values[i].fx + dx);	tmppts[i].y = o->co2iy(Values[i].fy + dy);
		}
	rDims.left = rDims.right = tmppts[0].x;		rDims.top = rDims.bottom = tmppts[0].y;
	for(i = 1; i < nPoints; i++) {
		if(tmppts[i].x < rDims.left) rDims.left = tmppts[i].x;
		else if(tmppts[i].x > rDims.right) rDims.right = tmppts[i].x;
		if(tmppts[i].y < rDims.top) rDims.top = tmppts[i].y;
		else if(tmppts[i].y > rDims.bottom) rDims.bottom = tmppts[i].y;
		}
	//DrawBezier returns not more than 2^MAXDEPTH points
	pts = (POINT*)malloc(nPoints * 64 * sizeof(POINT));
	for(i= nPts = 0; i< (nPoints-2); i += 3) {
		DrawBezier(&nPts, pts, tmppts[i], tmppts[i+1], tmppts[i+2], tmppts[i+3], 0);
		}
	IncrementMinMaxRect(&rDims, 3);
	o->ShowLine(pts, nPts, 0x00c0c0c0L);
	if(this == CurrGO) o->ShowMark(this, MRK_GODRAW);
	else {
		o->SetLine(&pgLine);			o->oPolyline(pts, nPts);
		}
}

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

	switch (cmd) {
	case CMD_MRK_DIRTY:			//issued by Undo
		CurrGO = this;
		bModified = true;
	case CMD_FLUSH:
		if(pHandles) {
			for(i = 0; i < nPoints; i++) if(pHandles[i]) delete(pHandles[i]);
			free(pHandles);		pHandles = 0L;
			}
		if(cmd == CMD_FLUSH && Values && nPoints){
			free(Values);
			Values = 0L;	nPoints = 0;
			}
		if(pts && nPts) free(pts);		pts = 0L;		nPts = 0;
		return true;
	case CMD_DELOBJ:
		if(pHandles && tmpl && tmpl == (void*)CurrHandle) {
			i = CurrHandle->type - DH_DATA;
			if (i >= 0 && i < nPoints) {
				i = CurrHandle->type - DH_DATA;
				Undo.DataMem(this, (void**)&Values, nPoints * sizeof(lfPOINT), &nPoints, 0L);
				if (i < 2) {
					i1 = 0;					i2 = 3;
					}
				else if (i > (nPoints-3)) {
					i1 = nPoints -3;		i2 = nPoints;
					}
				else {
					i -= (((i-2) % 3)-1);	i1 = i -1;		i2 = i +2;
					RemovePoint(Values, i1+1);
					Values[i2].fx = Values[i1].fx;	Values[i2].fy = Values[i1].fy;
					}
				for(i = 0; i < nPoints; i++) if(pHandles[i]) delete(pHandles[i]);
				free(pHandles);		pHandles = 0L;		CurrHandle = 0L;
				i = i2 - i1;
				for( ; i2 < nPoints; i1++, i2++) {
					Values[i1].fx = Values[i2].fx;	Values[i1].fy = Values[i2].fy;
					}
				nPoints -= i;	CurrGO = this;		bModified = true;
				return true;
				}
			}
		return false;
	case CMD_SET_DATAOBJ:
		Id = GO_BEZIER;
		return true;
		}
	return polyline::Command(cmd, tmpl, o);
}

void
Bezier::AddPoints(int n, lfPOINT *p)
{
	int i;

	for(i = 0; i< n; i++) {
		Values[nPoints].fx = p[i].fx;	Values[nPoints].fy = p[i].fy;
		nPoints ++;
		}
}

//Fitting of a Bezier curve to digitized points is based on:
//   P.J. Schneider (1990) An Algorithm for Automatically Fitting Digitized
//   Curves. In: Graphics Gems (Ed. A. Glassner, Academic Press Inc., 
//   ISBN 0-12-286165-5) pp. 612ff
void
Bezier::FitCurve(lfPOINT *d, int npt, double error)
{
	double len;
	lfPOINT tHat1, tHat2;

	tHat1.fx = d[1].fx - d[0].fx;		tHat1.fy = d[1].fy - d[0].fy;
	if(0.0 != (len = sqrt((tHat1.fx * tHat1.fx) + (tHat1.fy * tHat1.fy)))) {
		tHat1.fx /= len;					tHat1.fy /= len;
		}
	tHat2.fx = d[npt-2].fx - d[npt-1].fx;		tHat2.fy = d[npt-2].fy - d[npt-1].fy;
	if(0.0 != (len = sqrt((tHat2.fx * tHat2.fx) + (tHat2.fy * tHat2.fy)))) {
		tHat2.fx /= len;					tHat2.fy /= len;
		}
	FitCubic(d, 0, npt -1, tHat1, tHat2, error);
}

void
Bezier::RemovePoint(lfPOINT *d, int sel)
{
	lfPOINT tHat1, tHat2;

	tHat1.fx = d[sel-2].fx - d[sel-3].fx;	tHat1.fy = d[sel-2].fy - d[sel-3].fy;
	tHat2.fx = d[sel+2].fx - d[sel+3].fx;	tHat2.fy = d[sel+2].fy - d[sel+3].fy;
	d[sel] = d[sel+3];
	IpolBez(d+sel-3, tHat1, tHat2);
}

//heuristic interpolation: other methods failed
void
Bezier::IpolBez(lfPOINT *d, lfPOINT tHat1, lfPOINT tHat2)
{
	double b0, b1, b2;						//temp variables

	b1 = d[3].fx - d[0].fx;					b2 = d[3].fy - d[0].fy;
	b0 = sqrt(b1 * b1 + b2 * b2)/3.0;
	b1 = b0/sqrt(tHat1.fx * tHat1.fx + tHat1.fy * tHat1.fy);
	b2 = b0/sqrt(tHat2.fx * tHat2.fx + tHat2.fy * tHat2.fy);
	d[1].fx = d[0].fx + tHat1.fx * b1;		d[1].fy = d[0].fy + tHat1.fy * b1;
	d[2].fx = d[3].fx + tHat2.fx * b2;		d[2].fy = d[3].fy + tHat2.fy * b2;
}

//Fit a Bezier curve to a (sub)set of digitized points
void
Bezier::FitCubic(lfPOINT *d, int first, int last, lfPOINT tHat1, lfPOINT tHat2, double error)
{
	lfPOINT bezCurve[4];
	double *u, *uPrime, maxError, iterationError, len;
	int i, splitPoint, npt;
	lfPOINT tHatCenter;
	int maxIterations = 8;

	iterationError = error * error;
	npt = last - first +1;
	if(npt == 2) {
		bezCurve[0] = d[first];					bezCurve[3] = d[last];
		IpolBez(bezCurve, tHat1, tHat2);
		AddPoints(3, bezCurve);
		return;
		}
	u = ChordLengthParameterize(d, first, last);
	GenerateBezier(d, first, last, u, tHat1, tHat2, bezCurve);
	maxError = ComputeMaxError(d, first, last, bezCurve, u, &splitPoint);
	if(maxError < error) {
		if(maxError < 0.0) {					//Failure fitting curve
			IpolBez(bezCurve, tHat1, tHat2);
			}
		AddPoints(3, bezCurve);					return;
		}
	if(maxError < iterationError) {
		for (i = 0; i < maxIterations; i++) {
			uPrime = Reparameterize(d, first, last, u, bezCurve);
			GenerateBezier(d, first, last, uPrime, tHat1, tHat2, bezCurve);
			maxError = ComputeMaxError(d, first, last, bezCurve, uPrime, &splitPoint);
			if (maxError < error) {
				if(maxError < 0.0) IpolBez(bezCurve, tHat1, tHat2);
				AddPoints(3, bezCurve);			return;
				}
			free(u);							u = uPrime;
			}
		}
	//Fitting failed: split at max error point and recurse
	tHatCenter.fx = d[splitPoint-1].fx - d[splitPoint+1].fx;
	tHatCenter.fy = d[splitPoint-1].fy - d[splitPoint+1].fy;
	if(0.0 != (len = sqrt((tHatCenter.fx * tHatCenter.fx) + (tHatCenter.fy * tHatCenter.fy)))) {
		tHatCenter.fx /= len;					tHatCenter.fy /= len;
		}
	FitCubic(d, first, splitPoint, tHat1, tHatCenter, error * _SQRT2);
	tHatCenter.fx = -tHatCenter.fx;				tHatCenter.fy = -tHatCenter.fy;
	FitCubic(d, splitPoint, last, tHatCenter, tHat2, error * _SQRT2);
}

//Use least-squares method to find Bezier control points for region.
void
Bezier::GenerateBezier(lfPOINT *d, int first, int last, double *uPrime, 
	lfPOINT tHat1, lfPOINT tHat2, lfPOINT *bezCurve)
{
	int i, npt;
	lfPOINT *A0, *A1, tmp, v1, v2;
	double C[2][2], X[2];
	double det_C0_C1, det_C0_X, det_X_C1, alpha_l, alpha_r;
	double b0, b1, b2, b3;		//temp variables

	npt = last - first +1;
	A0 = (lfPOINT*) malloc(npt * sizeof(lfPOINT));
	A1 = (lfPOINT*) malloc(npt * sizeof(lfPOINT));
	for (i = 0; i < npt; i++) {
		v1 = tHat1;								v2 = tHat2;
		b2 = 1.0 - uPrime[i];
		b1 = 3.0 * uPrime[i] * b2 * b2;			b2 = 3.0 * uPrime[i] * uPrime[i] * b2;
		if(0.0 != (b0 = sqrt((v1.fx * v1.fx) + (v1.fy * v1.fy)))) {
			v1.fx *= b1/b0;						v1.fy *= b1/b0;
			}
		if(0.0 != (b0 = sqrt((v2.fx * v2.fx) + (v2.fy * v2.fy)))) {
			v2.fx *= b2/b0;						v2.fy *= b2/b0;
			}
		A0[i] = v1;								A1[i] = v2;
		}
	C[0][0] = C[0][1] = C[1][0] = C[1][1] = X[0] = X[1] = 0.0;
	for (i = 0; i < npt; i++) {
		C[0][0] += ((A0[i].fx * A0[i].fx) + (A0[i].fy * A0[i].fy));
		C[0][1] += ((A0[i].fx * A1[i].fx) + (A0[i].fy * A1[i].fy));
		C[1][0] = C[0][1];
		C[1][1] += ((A1[i].fx * A1[i].fx) + (A1[i].fy * A1[i].fy));
		b2 = 1.0 - uPrime[i];					b0 = b2 * b2 * b2;
		b1 = 3.0 * uPrime[i] * b2 * b2;			b2 = 3.0 * uPrime[i] * uPrime[i] * b2;
		b3 = uPrime[i] * uPrime[i] * uPrime[i];
		tmp.fx = d[last].fx * b2 + d[last].fx * b3;
		tmp.fy = d[last].fy * b2 + d[last].fy * b3;
		tmp.fx += d[first].fx * b1;				tmp.fy += d[first].fy * b1;
		tmp.fx += d[first].fx * b0;				tmp.fy += d[first].fy * b0;
		tmp.fx = d[first+i].fx - tmp.fx;		tmp.fy = d[first+i].fy - tmp.fy;
		X[0] += (A0[i].fx * tmp.fx + A0[i].fy * tmp.fy);
		X[1] += (A1[i].fx * tmp.fx + A1[i].fy * tmp.fy);
		}
	det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
	det_C0_X = C[0][0] * X[1] - C[0][1] * X[0];
	det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
	if(det_C0_C1 == 0.0) det_C0_C1 = (C[0][0] * C[1][1]) * 10e-12;
	alpha_l = det_X_C1 / det_C0_C1;				alpha_r = det_C0_X / det_C0_C1;
	bezCurve[0] = d[first];						bezCurve[3] = d[last];
	if(alpha_l < 0.0 || alpha_r < 0.0) {
		//use Wu/Barsky heuristic
		b1 = d[last].fx - d[first].fx;			b2 = d[last].fy - d[first].fy;
		b0 = sqrt(b1 * b1 + b2 * b2)/3.0;
		b1 = b0/sqrt(tHat1.fx * tHat1.fx + tHat1.fy * tHat1.fy);
		b2 = b0/sqrt(tHat2.fx * tHat2.fx + tHat2.fy * tHat2.fy);
		}
	else {
		b1 = alpha_l/sqrt(tHat1.fx * tHat1.fx + tHat1.fy * tHat1.fy);
		b2 = alpha_r/sqrt(tHat2.fx * tHat2.fx + tHat2.fy * tHat2.fy);
		}
	bezCurve[1].fx = bezCurve[0].fx + tHat1.fx * b1;
	bezCurve[1].fy = bezCurve[0].fy + tHat1.fy * b1;
	bezCurve[2].fx = bezCurve[3].fx + tHat2.fx * b2;
	bezCurve[2].fy = bezCurve[3].fy + tHat2.fy * b2;
	free(A0);									free(A1);
}

//Given set of points and their parameterization, try to find
//  a better parameterization.
double *
Bezier::Reparameterize(lfPOINT *d, int first, int last, double *u, lfPOINT *bezCurve)
{
	int i, j, k, npt = last-first+1;
	double *uPrime, num, den, *pl, *ph, *pq;
	lfPOINT Q1[3], Q2[2], Q_u, Q1_u, Q2_u;

	uPrime = (double*)malloc(npt * sizeof(double));
	//Use Newton-Raphson iteration to find better root
	for (i = first, j = 0; i <= last; i++, j++) {
		for(pl=(double*)bezCurve, ph=pl+2, pq=(double*)Q1, k=0; k <= 4; pl++, ph++, pq++, k++) {
			*pq = (*ph - *pl ) * 3.0;
			}
		for(pl=(double*)Q1, ph=pl+2, pq=(double*)Q2, k=0; k <= 2; pl++, ph++, pq++, k++) {
			*pq = (*ph - *pl ) * 2.0;
			}
		Q_u = fBezier(3, bezCurve, u[j]);
		Q1_u = fBezier(2, Q1, u[j]);	
		Q2_u = fBezier(1, Q2, u[j]);
		num = (Q_u.fx - d[i].fx) * (Q1_u.fx) + (Q_u.fy - d[i].fy) * (Q1_u.fy);
		den = (Q1_u.fx) * (Q1_u.fx) + (Q1_u.fy) * (Q1_u.fy) + 
			(Q_u.fx - d[i].fx) * (Q2_u.fx) + (Q_u.fy - d[i].fy) * (Q2_u.fy);
		uPrime[j] = u[j] - (num/den);
		}
	return uPrime;
}

//evaluate a Bezier curve at a particular parameter value
lfPOINT
Bezier::fBezier(int degree, lfPOINT *V, double t)
{
	int i, j;
	lfPOINT Q;
	lfPOINT *Vtemp;

	Vtemp = (lfPOINT *)malloc((degree+1) * sizeof(lfPOINT));
	for (i = 0; i <= degree; i++) {
		Vtemp[i] = V[i];
		}
	for (i = 1; i <= degree; i++) {
		for (j = 0; j <= degree-i; j++) {
			Vtemp[j].fx = (1.0 -t) * Vtemp[j].fx + t * Vtemp[j+1].fx;
			Vtemp[j].fy = (1.0 -t) * Vtemp[j].fy + t * Vtemp[j+1].fy;
			}
		}
	Q = Vtemp[0];
	free(Vtemp);
	return Q;
}

double *
Bezier::ChordLengthParameterize(lfPOINT *d, int first, int last)
{ 
	int i;
	double tmp, *u;

	u = (double*)malloc((last-first+1) * sizeof(double));
	u[0] = 0.0;
	for(i = first+1; i <= last; i++) {
		u[i-first] = u[i-first-1] + 
			sqrt(((tmp=(d[i].fx - d[i-1].fx))*tmp) + ((tmp=(d[i].fy - d[i-1].fy))*tmp));
		}
	for(i = first +1; i <= last; i++) {
		u[i-first] = u[i-first] / u[last-first];
		}
	return u;
}

double
Bezier::ComputeMaxError(lfPOINT *d, int first, int last, lfPOINT *bezCurve, double *u, int *splitPoint)
{
	int i;
	double maxDist, dist;
	lfPOINT P, v;

	*splitPoint = (last - first + 1)>>1;
	maxDist = -1.0;
	for(i = first +1; i < last; i++) {
		P = fBezier(3, bezCurve, u[i-first]);
		v.fx = P.fx - d[i].fx;			v.fy = P.fy - d[i].fy;
		dist = v.fx * v.fx + v.fy * v.fy;
		if(dist >= maxDist) {
			maxDist = dist;		*splitPoint = i;
			}
		}
	return maxDist;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// polygons are based on the polyline object
polygon::polygon(GraphObj *par, DataObj *d, lfPOINT *fpts, int cpts):
	polyline(par, d, fpts, cpts)
{
	Id = GO_POLYGON;
	memcpy(&pgLine, defs.pgLineDEF(0L), sizeof(LineDEF));
	type = 1;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// rectangle with absolute coordinates
rectangle::rectangle(GraphObj *par, DataObj *d, lfPOINT *p1, lfPOINT *p2):
	GraphObj(par, d)
{
	double dx = 0.0, dy = 0.0;

	FileIO(INIT_VARS);
	if(parent){
		dx = parent->GetSize(SIZE_GRECT_LEFT);	dy = parent->GetSize(SIZE_GRECT_TOP);
		}
	memcpy(&fp1, p1, sizeof(lfPOINT));	memcpy(&fp2, p2, sizeof(lfPOINT));
	fp1.fx -= dx;	fp1.fy -= dy;		fp2.fx -= dx;	fp2.fy -= dy;
	type = 0;
	Id = GO_RECTANGLE;
	bModified = false;
}

rectangle::rectangle(int src):GraphObj(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
	type = 0;
	bModified = false;
}

rectangle::~rectangle()
{
	Command(CMD_FLUSH, 0L, 0L);
	if(bModified) Undo.InvalidGO(this);
	if(drc) delete(drc);					drc = 0L;
}

double
rectangle::GetSize(int select)
{
	if(parent && parent->Id== GO_GROUP){
	switch(select) {
		case SIZE_XPOS:		return fp1.fx + parent->GetSize(SIZE_XPOS);
		case SIZE_XPOS+1:	return fp2.fx + parent->GetSize(SIZE_XPOS);
		case SIZE_YPOS:		return fp1.fy + parent->GetSize(SIZE_YPOS);
		case SIZE_YPOS+1:	return fp2.fy + parent->GetSize(SIZE_YPOS);
		case SIZE_GRECT_LEFT:	case SIZE_GRECT_TOP:
		case SIZE_GRECT_RIGHT:	case SIZE_GRECT_BOTTOM:
			return parent->GetSize(select);
			}
		return 0.0;
		}
	switch(select) {
	case SIZE_XPOS:		return fp1.fx;
	case SIZE_XPOS+1:	return fp2.fx;
	case SIZE_YPOS:		return fp1.fy;
	case SIZE_YPOS+1:	return fp2.fy;
	case SIZE_GRECT_LEFT:	case SIZE_GRECT_TOP:
	case SIZE_GRECT_RIGHT:	case SIZE_GRECT_BOTTOM:
		if(parent) return parent->GetSize(select);
		break;
		}
	return 0.0;
}

bool
rectangle::SetSize(int select, double value)
{
	switch(select & 0xfff) {
	case SIZE_XPOS:				fp1.fx = value;			return true;
	case SIZE_XPOS+1:			fp2.fx = value;			return true;
	case SIZE_YPOS:				fp1.fy = value;			return true;
	case SIZE_YPOS+1:			fp2.fy = value;			return true;
		}
	return false;
}

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

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

void
rectangle::DoPlot(anyOutput *o)
{
	int x1, y1, x2, y2;
	double tmp, dx, dy;

	if(!parent || !o) return;
	dx = parent->GetSize(SIZE_GRECT_LEFT);		dy = parent->GetSize(SIZE_GRECT_TOP);
	if(fp1.fx > fp2.fx) {
		tmp = fp2.fx;	fp2.fx = fp1.fx;	fp1.fx = tmp;
		}
	if(fp1.fy > fp2.fy) {
		tmp = fp2.fy;	fp2.fy = fp1.fy;	fp1.fy = tmp;
		}
	if(type == 2) PlotRoundRect(o);
	else {
		x1 = o->co2ix(fp1.fx+dx);			y1 = o->co2iy(fp1.fy+dy);
		x2 = o->co2ix(fp2.fx+dx);			y2 = o->co2iy(fp2.fy+dy);
		o->SetLine(&Line);				o->SetFill(&Fill);
		if(type == 1) o->oCircle(x1, y1, x2, y2, name);
		else o->oRectangle(x1, y1, x2, y2, name);
		SetMinMaxRect(&rDims, x1, y1, x2, y2);
		}
	o->MrkMode = MRK_NONE;
	if(CurrGO == this) o->ShowMark(this, MRK_GODRAW);
}

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

	switch (cmd) {
	case CMD_FLUSH:
		if(pts) free(pts);		pts = 0L;
		if(name) free(name);	name = 0L;
		return true;
	case CMD_SCALE:
		fp1.fx = ((scaleINFO*)tmpl)->sx.fx + fp1.fx * ((scaleINFO*)tmpl)->sx.fy;
		fp2.fx = ((scaleINFO*)tmpl)->sx.fx + fp2.fx * ((scaleINFO*)tmpl)->sx.fy;
		fp1.fy = ((scaleINFO*)tmpl)->sy.fx + fp1.fy * ((scaleINFO*)tmpl)->sy.fy;
		fp2.fy = ((scaleINFO*)tmpl)->sy.fx + fp2.fy * ((scaleINFO*)tmpl)->sy.fy;
		Line.patlength *= ((scaleINFO*)tmpl)->sy.fy;		Line.width *= ((scaleINFO*)tmpl)->sy.fy;
		FillLine.patlength *= ((scaleINFO*)tmpl)->sy.fy;	FillLine.width *= ((scaleINFO*)tmpl)->sy.fy;
		Fill.scale *= ((scaleINFO*)tmpl)->sy.fy;			rad *= ((scaleINFO*)tmpl)->sy.fy;
		return true;
	case CMD_SAVEPOS:
		bModified = true;
		Undo.SaveLFP(this, &fp1, 0L);
		Undo.SaveLFP(this, &fp2, UNDO_CONTINUE);
		return true;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *) tmpl;
		switch (mev->Action) {
		case MOUSE_LBUP:
			if(IsInRect(&rDims, mev->x, mev->y) && !(CurrGO) && (o)){
				return o->ShowMark(this, MRK_GODRAW);
				}
			}
		return false;
	case CMD_SET_DATAOBJ:
		switch (type) {
		case 1:		Id = GO_ELLIPSE;		break;
		case 2:		Id = GO_ROUNDREC;		break;
		default:	Id = GO_RECTANGLE;		break;
			}
		return true;
	case CMD_MOVE:
		bModified = true;
		Undo.MoveObj(this, (lfPOINT*)tmpl, 0L);
	case CMD_UNDO_MOVE:
		fp1.fx += ((lfPOINT*)tmpl)[0].fx;	fp1.fy += ((lfPOINT*)tmpl)[0].fy;
		fp2.fx += ((lfPOINT*)tmpl)[0].fx;	fp2.fy += ((lfPOINT*)tmpl)[0].fy;
		CurrGO = this;
	case CMD_REDRAW:
		if(parent && cmd != CMD_UNDO_MOVE){
			parent->Command(CMD_REDRAW, tmpl, o);
			}
		return true;
	case CMD_SETSCROLL:
		if(parent) return parent->Command(cmd, tmpl, o);
		}
	return false;
}

void
rectangle::Track(POINT *p, anyOutput *o)
{
	POINT tpts[5];
	RECT old_rc;
	double dx, dy;

	if(o && parent){
		dx = parent->GetSize(SIZE_GRECT_LEFT);		dy = parent->GetSize(SIZE_GRECT_TOP);
		memcpy(&old_rc, &rDims, sizeof(rDims));
		o->UpdateRect(&rDims, false);
		tpts[0].x = tpts[1].x = tpts[4].x = o->co2ix(fp1.fx+dx)+p->x;		
		tpts[0].y = tpts[3].y = tpts[4].y = o->co2iy(fp1.fy+dy)+p->y;
		tpts[1].y = tpts[2].y = o->co2iy(fp2.fy+dy)+p->y;
		tpts[2].x = tpts[3].x = o->co2ix(fp2.fx+dx)+p->x;
		UpdateMinMaxRect(&rDims, tpts[0].x, tpts[0].y);
		UpdateMinMaxRect(&rDims, tpts[2].x, tpts[2].y);	
		if(old_rc.left != rDims.left || old_rc.right != rDims.right || old_rc.top !=
			rDims.top || old_rc.bottom != rDims.bottom)IncrementMinMaxRect(&rDims, 3);
		o->ShowLine(tpts, 5, Line.color);
		if(type == 1) o->ShowEllipse(tpts[0], tpts[2], Line.color);
		}
}

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

//use circular Bresenham's algorithm to draw rounded rectangles
//Ref: C. Montani, R. Scopigno (1990) "Speres-To-Voxel Conversion", in:
//   Graphic Gems (A.S. Glassner ed.) Academic Press, Inc.; 
//   ISBN 0-12-288165-5 
void
rectangle::PlotRoundRect(anyOutput *o)
{
	int i, m, x, y, di, de, lim, ir, x1, x2, y1, y2;
	double dx, dy;
	POINT np;

	dx = parent->GetSize(SIZE_GRECT_LEFT);		dy = parent->GetSize(SIZE_GRECT_TOP);
	ir = o->un2ix(rad);
	x1 = o->co2ix(fp1.fx+dx);			y1 = o->co2iy(fp1.fy+dy);
	x2 = o->co2ix(fp2.fx+dx);			y2 = o->co2iy(fp2.fy+dy);
	if (x1 > x2) Swap(x1, x2);		if(y1 > y2) Swap(y1, y2);
	if(pts) free(pts);				nPts = 0;
	m = ir*4+10;
	if(!(pts = (POINT*)malloc(m*sizeof(POINT))))return;
	for(i = 0; i < 4; i++) {
		x = lim = 0;	y = ir;	di = 2*(1-ir);
		while (y >= lim){
			if(di < 0) {
				de = 2*di + 2*y -1;
				if(de > 0) {
					x++;	y--;	di += (2*x -2*y +2);
					}
				else {
					x++;	di += (2*x +1);
					}
				}
			else {
				de = 2*di -2*x -1;
				if(de > 0) {
					y--;	di += (-2*y +1);
					}
				else {
					x++;	y--;	di += (2*x -2*y +2);
					}
				}
			switch(i) {
			case 0:
				np.x = x2-ir+x;		np.y = y2-ir+y;
				break;
			case 1:
				np.x = x2-ir+y;		np.y = y1+ir-x;
				break;
			case 2: 
				np.x = x1+ir-x;		np.y = y1+ir-y;
				break;
			case 3:
				np.x = x1+ir-y;		np.y = y2-ir+x;
				break;
				}
			AddToPolygon(&nPts, pts, &np);
			}
		}
	AddToPolygon(&nPts, pts, pts);	//close polygon
	o->SetLine(&Line);				o->SetFill(&Fill);
	o->oPolygon(pts, nPts, name);
	SetMinMaxRect(&rDims, x1, y1, x2, y2);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ellipse with absolute coordinates
ellipse::ellipse(GraphObj *par, DataObj *d, lfPOINT *p1, lfPOINT *p2)
	:rectangle(par, d, p1, p2)
{
	type = 1;
	Id = GO_ELLIPSE;
}

ellipse::ellipse(int src)
	:rectangle(src)
{
	type = 1;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// rounded rectangle
roundrec::roundrec(GraphObj *par, DataObj *d, lfPOINT *p1, lfPOINT *p2)
	:rectangle(par, d, p1, p2)
{
	type = 2;
	Id = GO_ROUNDREC;
}

roundrec::roundrec(int src)
	:rectangle(src)
{
	type = 2;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Add a legend to the graph
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
LegItem::LegItem(GraphObj *par, DataObj *d, LineDEF *ld, LineDEF *lf, FillDEF *fd, char *desc)
	:GraphObj(par, d)
{
	FileIO(INIT_VARS);
	if(!ld && !fd && lf) ld = lf;
	if(ld) {
		memcpy(&DataLine, ld, sizeof(LineDEF));
		flags |= 0x01;
		}
	if(lf) memcpy(&OutLine, lf, sizeof(LineDEF));
	if(fd) {
		if(fd->hatch) memcpy(&HatchLine, fd->hatch, sizeof(LineDEF));
		memcpy(&Fill, fd, sizeof(FillDEF));
		Fill.hatch = &HatchLine;
		flags |= 0x02;
		}
	DefDesc(desc);		Id = GO_LEGITEM;		moveable = 1;
}

LegItem::LegItem(GraphObj *par, DataObj *d, LineDEF *ld, Symbol *sy)
	:GraphObj(par, d)
{
	FileIO(INIT_VARS);
	if(ld) {
		memcpy(&DataLine, ld, sizeof(LineDEF));		flags |= 0x01;
		}
	if(sy) {
		Sym = sy;		Sym->parent = this;			flags |= 0x04;
		}
	DefDesc(sy ? sy->name : 0L);		Id = GO_LEGITEM;		moveable = 1;
}

LegItem::LegItem(GraphObj *par, DataObj *d, LineDEF *ld, int err, char *desc)
	:GraphObj(par, d)
{
	FileIO(INIT_VARS);		flags |= (0x80 | (err & 0x7f));
	if(ld) {
		memcpy(&OutLine, ld, sizeof(LineDEF));
		}
	DefDesc(desc);		Id = GO_LEGITEM;		moveable = 1;
}

LegItem::LegItem(int src):GraphObj(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
	moveable = 1;
}

LegItem::~LegItem()
{
	if(Sym) DeleteGO(Sym);		Sym = 0L;
	if(Desc) DeleteGO(Desc);	Desc = 0L;
}

double
LegItem::GetSize(int select)
{
	switch(select) {
	case SIZE_XCENTER:
		return (parent->GetSize((flags & 0x01) ? SIZE_XPOS+2 : SIZE_XPOS) + 
			parent->GetSize((flags & 0x01) ? SIZE_XPOS+3 : SIZE_XPOS+1))/2.0;
	case SIZE_YCENTER:
		return (parent->GetSize(SIZE_YPOS) + parent->GetSize(SIZE_YPOS+1))/2.0;
	case SIZE_LB_XPOS:		case SIZE_LB_YPOS:
	case SIZE_GRECT_TOP:	case SIZE_GRECT_LEFT:
	default:
		if(parent) return parent->GetSize(select);
		break;
		}
	return 0.0;
}

void
LegItem::DoPlot(anyOutput *o)
{
	POINT pts[3];
	int ie, cy;

	if(!parent || !o) return;
	hcr.top = iround(parent->GetSize(SIZE_YPOS));
	hcr.bottom = iround(parent->GetSize(SIZE_YPOS+1));
	if(flags & 0x80) {
		hcr.left = iround(parent->GetSize((flags & 0x40) ? SIZE_XPOS+2 : SIZE_XPOS));
		hcr.right = iround(parent->GetSize((flags & 0x40) ? SIZE_XPOS+3 :SIZE_XPOS+1));
		ie = o->un2ix(DefSize(SIZE_ERRBAR)/2.0);
		o->SetLine(&OutLine);						cy = (hcr.top + hcr.bottom)>>1;
		if((flags & 0x3f) == 0x01) {
			pts[0].x = pts[1].x = (hcr.right + hcr.left)>>1;
			pts[0].y = hcr.top;		pts[1].y = hcr.bottom;
			o->oPolyline(pts, 2, 0L);		
			pts[0].x -= (ie-1);		pts[1].x += ie;		pts[0].y = pts[1].y = hcr.top;
			o->oPolyline(pts, 2, 0L);					pts[0].y = pts[1].y = hcr.bottom;
			o->oPolyline(pts, 2, 0L);
			}
		else if((flags & 0x3f) == 0x02) {
			pts[0].x = pts[1].x = ((hcr.right + hcr.left)>>1);
			pts[0].y = pts[1].y = cy;			cy = ((hcr.bottom - hcr.top)>>1);
			pts[0].x -= cy;						pts[2].x = (pts[1].x += cy);
			o->oPolyline(pts, 2, 0L);			pts[1].x = pts[0].x;
			pts[0].y -= ie;						pts[1].y += ie;
			o->oPolyline(pts, 2, 0L);
			pts[0].x = pts[1].x = pts[2].x;		o->oPolyline(pts, 2, 0L);
			}
		else if((flags & 0x3f) == 0x03) {
			pts[0].x = pts[1].x = (hcr.right + hcr.left)>>1;
			pts[0].y = hcr.top;		pts[1].y = hcr.bottom;
			o->oPolyline(pts, 2, 0L);			pts[0].x -= (ie-1);
			pts[0].y = pts[1].y = hcr.top;		o->oPolyline(pts, 2, 0L);
			pts[0].y = pts[1].y = hcr.bottom;	pts[0].x += (ie + ie -1);
			o->oPolyline(pts, 2, 0L);
			}
		else if((flags & 0x3f) == 0x04) {
			pts[0].x = pts[1].x = (hcr.right + hcr.left)>>1;
			pts[0].y = hcr.top;		pts[1].y = hcr.bottom;
			o->oPolyline(pts, 2, 0L);			pts[0].x += ie;
			pts[0].y = pts[1].y = hcr.top;		o->oPolyline(pts, 2, 0L);
			pts[0].y = pts[1].y = hcr.bottom;	pts[0].x -= (ie + ie -1);
			o->oPolyline(pts, 2, 0L);
			}
		else if((flags & 0x3f) == 0x05) {
			pts[0].x = pts[1].x = (hcr.right + hcr.left)>>1;
			pts[0].y = hcr.top;		pts[1].y = hcr.bottom;
			o->oPolyline(pts, 2, 0L);
			}
		}
	else {
		hcr.left = iround(parent->GetSize((flags & 0x01) ? SIZE_XPOS+2 : SIZE_XPOS));
		hcr.right = iround(parent->GetSize((flags & 0x01) ? SIZE_XPOS+3 :SIZE_XPOS+1));
		if(flags & 0x02){
			o->SetLine(&OutLine);	o->SetFill(&Fill);
			o->oRectangle(hcr.left, hcr.top, hcr.right, hcr.bottom, name);
			}
		if(flags & 0x01){
			pts[0].x = hcr.left;	pts[1].x = hcr.right;
			pts[0].y = pts[1].y = iround(GetSize(SIZE_YCENTER))+1;
			o->SetLine(&DataLine);	o->oPolyline(pts, 2, 0L);
			}
		if(flags & 0x04){
			if(Sym) Sym->DoPlot(o);
			}
		}
	SetMinMaxRect(&rDims, hcr.left, hcr.top, hcr.right, hcr.bottom);
	if(Desc) {
		Desc->moveable = 1;			Desc->DoPlot(o);
		if(Desc->rDims.bottom > rDims.bottom){
			parent->SetSize(SIZE_YPOS+1, (double)Desc->rDims.bottom);
			}
		UpdateMinMaxRect(&rDims, Desc->rDims.left, Desc->rDims.top);
		UpdateMinMaxRect(&rDims, Desc->rDims.right, Desc->rDims.bottom);
		}
}

void
LegItem::DoMark(anyOutput *o, bool mark)
{
	RECT cr;
	LineDEF ld = {0.0, 1.0, 0x00000000L, 0x00000000L};
	POINT pts[5];

	if(!parent || !o) return;
	cr.left = hcr.left-5;			cr.right = hcr.right+3;
	cr.top = hcr.top-3;				cr.bottom = hcr.bottom+1;
	ld.color = mark ? 0x00000000L : 0x00ffffffL;	o->SetLine(&ld);
	pts[0].x = pts[3].x = pts[4].x = cr.left;	pts[0].y = pts[1].y = pts[4].y = cr.top;
	pts[1].x = pts[2].x = cr.right;				pts[2].y = pts[3].y = cr.bottom;
	o->oPolyline(pts, 5, name);					IncrementMinMaxRect(&cr, 3);
	o->UpdateRect(&cr, false);
}

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

	switch(cmd){
	case CMD_TEXTTHERE:
		if(Desc && Desc->Command(cmd, tmpl, o)) return true;
		return false;
	case CMD_MOUSE_EVENT:
		if(tmpl && IsInRect(&rDims, ((MouseEvent*)tmpl)->x, ((MouseEvent*)tmpl)->y) && o) {
			if(Desc && Desc->Command(cmd, tmpl, o)) return true;
			if(!CurrGO) o->ShowMark(CurrGO=this, MRK_GODRAW);
			}
		break;
	case CMD_SCALE:
		DataLine.patlength *= ((scaleINFO*)tmpl)->sy.fy;		DataLine.width *= ((scaleINFO*)tmpl)->sy.fy;
		OutLine.patlength *= ((scaleINFO*)tmpl)->sy.fy;			OutLine.width *= ((scaleINFO*)tmpl)->sy.fy;
		HatchLine.patlength *= ((scaleINFO*)tmpl)->sy.fy;		HatchLine.width *= ((scaleINFO*)tmpl)->sy.fy;
		Fill.scale *= ((scaleINFO*)tmpl)->sy.fy;	
		if(Sym) Sym->Command(cmd, tmpl, o);
		if(Desc) Desc->Command(cmd, tmpl, o);
		break;
	case CMD_REDRAW:	case CMD_MOVE:
		if(parent) return parent->Command(cmd, tmpl, o);
		break;
	case CMD_MUTATE:
		if(!parent || !(tmpPlots = (GraphObj **)tmpl) || !tmpPlots[0] || !tmpPlots[1]) return false;
		if(Desc == tmpPlots[0]) {
			Undo.MutateGO((GraphObj**)&Desc, tmpPlots[1], 0L, o);
			return true;
			}
		break;
	case CMD_SET_DATAOBJ:
		if(Desc) Desc->Command(cmd, tmpl, o);
		Id = GO_LEGITEM;
		data = (DataObj *)tmpl;
		return true;
		}
	return false;
}

void
LegItem::Track(POINT *p, anyOutput *o)
{
	if(parent) parent->Track(p, o);
}

bool
LegItem::HasFill(LineDEF *ld, FillDEF *fd, char *desc)
{
	if(ld && cmpLineDEF(ld, &OutLine)) return false;
	if(fd && cmpFillDEF(fd, &Fill)) return false;
	if(fd && fd->hatch && cmpLineDEF(fd->hatch, &HatchLine)) return false;
	return true;
}

bool
LegItem::HasSym(LineDEF *ld, GraphObj *sy)
{
	if(sy && !Sym) return false;
	if(sy->Id == GO_SYMBOL && (sy->type & 0xfff) != (Sym->type & 0xfff)) return false;
	if(sy && Sym) {
		if(Sym->GetSize(SIZE_SYMBOL) != sy->GetSize(SIZE_SYMBOL)) return false;
		if(Sym->GetSize(SIZE_SYM_LINE) != sy->GetSize(SIZE_SYM_LINE)) return false;
		if(Sym->GetColor(COL_SYM_LINE) != sy->GetColor(COL_SYM_LINE)) return false;
		if(Sym->GetColor(COL_SYM_FILL) != sy->GetColor(COL_SYM_FILL)) return false;
		}
	if(ld && cmpLineDEF(ld, &DataLine)) return false;
	return true;
}

bool
LegItem::HasErr(LineDEF *ld, int err)
{
	if((((DWORD)err & 0x1f) | 0x80) != (flags & 0x9f)) return false;
	if(ld && cmpLineDEF(ld, &OutLine)) return false;
	return true;
}

void
LegItem::DefDesc(char *txt)
{
	TextDEF td;
	int cb;

	cb = txt && *txt ? (int)strlen(txt) : 20;
	if(cb < 20) cb = 20;
	td.ColTxt = 0x00000000L;		td.ColBg = 0x00ffffffL;
	td.fSize = DefSize(SIZE_TICK_LABELS);
	td.RotBL = td.RotCHAR = 0.0;
	td.iSize = 0;					td.Align = TXA_VTOP | TXA_HLEFT;
	td.Mode = TXM_TRANSPARENT;		td.Style = TXS_NORMAL;
	td.Font = FONT_HELVETICA;		td.text = (char*)malloc(cb+2);
	rlp_strcpy(td.text, cb+2, txt ? txt : (char*)"text");
	Desc = new Label(this, data, 0, 0, &td, LB_X_PARENT | LB_Y_PARENT);
}

Legend::Legend(GraphObj *par, DataObj *d):GraphObj(par, d)
{
	FileIO(INIT_VARS);		Id = GO_LEGEND;		moveable = true;
}

Legend::Legend(int src):GraphObj(0L, 0L)
{
	FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		}
	moveable = true;
}

Legend::~Legend()
{
	int i;

	if(Items) {
		for(i = 0; i< nItems; i++) if(Items[i]) DeleteGO(Items[i]);
		free(Items);	Items = 0L;
		}
	if(to) DelBitmapClass(to);		to = 0L;
}
double
Legend::GetSize(int select)
{
	switch(select) {
	case SIZE_XPOS:		return C_Rect.Xmin;
	case SIZE_XPOS+1:	return C_Rect.Xmax;
	case SIZE_XPOS+2:	return E_Rect.Xmin;
	case SIZE_XPOS+3:	return E_Rect.Xmax;
	case SIZE_YPOS:		return C_Rect.Ymin;
	case SIZE_YPOS+1:	return C_Rect.Ymax;
	case SIZE_LB_XPOS:	return lb_pos.fx;
	case SIZE_LB_YPOS:	return lb_pos.fy;
	case SIZE_GRECT_TOP:	case SIZE_GRECT_LEFT:
		if(parent) return parent->GetSize(select);
		break;
		}
	return 0.0;
}

bool
Legend::SetSize(int select, double value)
{
	double tmp;

	switch (select & 0xfff){
	case SIZE_XPOS:		pos.fx = value;		return true;
	case SIZE_YPOS:		pos.fy = value;		return true;
	case SIZE_YPOS+1:
		tmp = value - C_Rect.Ymax;
		C_Rect.Ymin += tmp;		C_Rect.Ymax += tmp;		lb_pos.fy +=tmp;
		}
	return false;
}

void
Legend::DoPlot(anyOutput *o)
{
	int i;
	double y_inc, dx, dy;

	if(!o || !Items) return;
	if(to) DelBitmapClass(to);		to = 0L;
	dx = parent->GetSize(SIZE_GRECT_LEFT);		dy = parent->GetSize(SIZE_GRECT_TOP);
	C_Rect.Xmin = o->co2fix(pos.fx + B_Rect.Xmin + D_Rect.Xmin + dx);
	C_Rect.Ymin = o->co2fiy(pos.fy + B_Rect.Ymin + D_Rect.Ymin + dy);
	C_Rect.Xmax = C_Rect.Xmin + o->un2fix(D_Rect.Xmax - D_Rect.Xmin);
	C_Rect.Ymax = C_Rect.Ymin + o->un2fix(D_Rect.Ymax - D_Rect.Ymin);
	E_Rect.Ymin = C_Rect.Ymin;	E_Rect.Ymax = C_Rect.Ymax;
	E_Rect.Xmin = o->co2fix(pos.fx + B_Rect.Xmin + F_Rect.Xmin + dx);
	E_Rect.Xmax = E_Rect.Xmin + o->un2fix(F_Rect.Xmax - F_Rect.Xmin);
	y_inc = floor(0.5+o->un2fiy(B_Rect.Ymax - B_Rect.Ymin));
	rDims.top = iround(C_Rect.Ymin); rDims.bottom = iround(C_Rect.Ymax);
	rDims.left = rDims.right = iround(C_Rect.Xmin);	
	lb_pos.fx = o->co2fix(pos.fx + B_Rect.Xmax + dx);	lb_pos.fy = C_Rect.Ymin;
	//draw all items
	for(i = 0; i < nItems; i++) {
		if(Items[i]){
			if((Items[i]->flags & 0x11) == 0x01) hasLine = true;
			Items[i]->DoPlot(o);
			UpdateMinMaxRect(&rDims, Items[i]->rDims.left, Items[i]->rDims.top);
			UpdateMinMaxRect(&rDims, Items[i]->rDims.right, Items[i]->rDims.bottom);
			C_Rect.Ymin += y_inc;		C_Rect.Ymax += y_inc;
			lb_pos.fy += y_inc;
			}
		}
	IncrementMinMaxRect(&rDims, 6);
}

void
Legend::DoMark(anyOutput *o, bool mark)
{
	RECT cr;
	LineDEF ld = {0.0, 1.0, 0x00c0c0c0L, 0x00000000L};
	POINT pts[5];

	if(!parent || !o) return;
	cr.left = rDims.left;			cr.right = rDims.right;
	cr.top = rDims.top;				cr.bottom = rDims.bottom;
	ld.color = mark ? 0x00c0c0c0L : 0x00ffffffL;	o->SetLine(&ld);
	pts[0].x = pts[3].x = pts[4].x = cr.left;	pts[0].y = pts[1].y = pts[4].y = cr.top;
	pts[1].x = pts[2].x = cr.right;				pts[2].y = pts[3].y = cr.bottom;
	o->oPolyline(pts, 5, name);					IncrementMinMaxRect(&cr, 3);
	o->UpdateRect(&cr, false);
}

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

	switch(cmd){
	case CMD_TEXTTHERE:
		if(Items) for(i = 0; i< nItems; i++) 
			if(Items[i] && Items[i]->Command(cmd, tmpl, o)) return true;
		return false;
	case CMD_MOUSE_EVENT:
		if(o && tmpl && IsInRect(&rDims, ((MouseEvent*)tmpl)->x, ((MouseEvent*)tmpl)->y)) {
			if(Items) for(i = 0; i< nItems; i++) 
				if(Items[i] && Items[i]->Command(cmd, tmpl, o)) return true;
			if(!CurrGO) o->ShowMark(CurrGO = this, MRK_GODRAW);
			}
		break;
	case CMD_SET_DATAOBJ:
		if(Items) for(i = 0; i < nItems; i++) if(Items[i]) Items[i]->Command(cmd, tmpl, o);
		Id = GO_LEGEND;
		data = (DataObj *)tmpl;
		return true;
	case CMD_SCALE:
		pos.fx *= ((scaleINFO*)tmpl)->sx.fy;			pos.fy *= ((scaleINFO*)tmpl)->sy.fy;
		lb_pos.fx *= ((scaleINFO*)tmpl)->sx.fy;			lb_pos.fy *= ((scaleINFO*)tmpl)->sy.fy;
		B_Rect.Xmax *= ((scaleINFO*)tmpl)->sx.fy;		B_Rect.Xmin *= ((scaleINFO*)tmpl)->sx.fy;
		B_Rect.Ymax *= ((scaleINFO*)tmpl)->sy.fy;		B_Rect.Ymin *= ((scaleINFO*)tmpl)->sy.fy;
		C_Rect.Xmax *= ((scaleINFO*)tmpl)->sx.fy;		C_Rect.Xmin *= ((scaleINFO*)tmpl)->sx.fy;
		C_Rect.Ymax *= ((scaleINFO*)tmpl)->sy.fy;		C_Rect.Ymin *= ((scaleINFO*)tmpl)->sy.fy;
		D_Rect.Xmax *= ((scaleINFO*)tmpl)->sx.fy;		D_Rect.Xmin *= ((scaleINFO*)tmpl)->sx.fy;
		D_Rect.Ymax *= ((scaleINFO*)tmpl)->sy.fy;		D_Rect.Ymin *= ((scaleINFO*)tmpl)->sy.fy;
		E_Rect.Xmax *= ((scaleINFO*)tmpl)->sx.fy;		E_Rect.Xmin *= ((scaleINFO*)tmpl)->sx.fy;
		E_Rect.Ymax *= ((scaleINFO*)tmpl)->sy.fy;		E_Rect.Ymin *= ((scaleINFO*)tmpl)->sy.fy;
		F_Rect.Xmax *= ((scaleINFO*)tmpl)->sx.fy;		F_Rect.Xmin *= ((scaleINFO*)tmpl)->sx.fy;
		F_Rect.Ymax *= ((scaleINFO*)tmpl)->sy.fy;		F_Rect.Ymin *= ((scaleINFO*)tmpl)->sy.fy;
		if(Items) for(i = 0; i < nItems; i++) if(Items[i]) Items[i]->Command(cmd, tmpl, o);
		return true;
	case CMD_DELOBJ:
		o->HideMark();
		if(Items && parent) for(i = 0; i < nItems; i++) {
			if(Items[i] && tmpl == (void *)Items[i]) {
				Undo.DeleteGO((GraphObj**)(&Items[i]), 0L, o);
				parent->Command(CMD_REDRAW, NULL, o);
				return true;
				}
			}
		break;
	case CMD_MOVE:
		Undo.MoveObj(this, (lfPOINT*)tmpl, 0L);
	case CMD_UNDO_MOVE:
		pos.fx += ((lfPOINT*)tmpl)[0].fx;	pos.fy += ((lfPOINT*)tmpl)[0].fy;
		CurrGO = this;
	case CMD_REDRAW:
		if(parent && cmd != CMD_UNDO_MOVE){
			parent->Command(CMD_REDRAW, tmpl, o);
			}
		return true;
	case CMD_DROP_OBJECT:
		if(!tmpl) return false;
		if(!(Items = (LegItem**)realloc(Items, (2+nItems)*sizeof(LegItem*))))return false;
		Items[nItems++] = (LegItem*)tmpl;
		Items[nItems-1]->parent = this;
		return true;
		}
	return false;
}

void
Legend::Track(POINT *p, anyOutput *o)
{
	POINT pts[5];
	LineDEF tld = {0.0, 1.0, 0x00c0c0c0, 0x0L};

	if(!p || !o) return;
	if(to) {
		o->CopyBitmap(trc.left, trc.top, to, 0, 0, trc.right - trc.left, 
			trc.bottom - trc.top, false);
		DelBitmapClass(to);		to = 0L;
		o->UpdateRect(&trc, false);
		}
	trc.left = pts[0].x = pts[1].x = pts[4].x = rDims.left + p->x;		
	trc.top = pts[0].y = pts[3].y = pts[4].y = rDims.top + p->y;
	trc.bottom = pts[1].y = pts[2].y = rDims.bottom + p->y;
	trc.right = pts[2].x = pts[3].x = rDims.right + p->x;
	IncrementMinMaxRect(&trc, 3);	to = GetRectBitmap(&trc, o);
	o->SetLine(&tld);				o->oPolyline(pts, 5, 0L);
	o->UpdateRect(&trc, false);
} 

bool
Legend::HasFill(LineDEF *ld, FillDEF *fd, char *desc)
{
	int i;
	LegItem *li;

	if(Items) for(i = 0; i < nItems; i++) {
		if(Items[i] && Items[i]->HasFill(ld, fd, desc)) return true;
		}
	if(li = new LegItem(this, data, 0L, ld, fd, desc)){
		if(!(Command(CMD_DROP_OBJECT, li, 0L))) DeleteGO(li);
		}
	return false;
}

bool
Legend::HasSym(LineDEF *ld, GraphObj *sy, char *desc)
{
	int i, sym;
	Symbol *ns;
	LegItem *li;

	if(!parent || !sy) return true;
	if(ld) hasLine = true;
	if(Items) for(i = 0; i < nItems; i++) {
		if(Items[i] && Items[i]->HasSym(ld, sy)) return true;
		}
	sym = sy->Id == GO_SYMBOL ? (sy->type & 0xff) : 0;
	if(!(ns = new Symbol(this, data, 0.0, 0.0, sym | SYM_POS_PARENT))) return true;
	ns->SetSize(SIZE_SYMBOL, sy->GetSize(SIZE_SYMBOL));
	ns->SetSize(SIZE_SYM_LINE, sy->GetSize(SIZE_SYM_LINE));
	ns->SetColor(COL_SYM_LINE, sy->GetColor(COL_SYM_LINE));
	ns->SetColor(COL_SYM_FILL, sy->GetColor(COL_SYM_FILL));
	if(desc && desc[0]) {
		ns->name = (char*)memdup(desc,(int)strlen(desc)+1, 0); 
		}
	else if(sy->name && sy->name[0]) {
		ns->name = (char*)memdup(sy->name,(int)strlen(sy->name)+1, 0); 
		}
	else if(sy->parent && sy->parent->Id < GO_GRAPH && sy->parent->Id >= GO_PLOT) {
		if(((Plot*)(sy->parent))->data_desc) ns->name = (char*)memdup(((Plot*)(sy->parent))->data_desc,
			(int)strlen(((Plot*)(sy->parent))->data_desc)+1, 0);
		}
	else if(sy->parent && sy->parent->name && sy->parent->name[0]) {
		ns->name = (char*)memdup(sy->parent->name,(int)strlen(sy->parent->name)+1, 0); 
		}
	if(li = new LegItem(this, data, ld, ns)){
		if(!(Command(CMD_DROP_OBJECT, li, 0L))) DeleteGO(li);
		}
	return false;
}

bool
Legend::HasErr(LineDEF *ld, int err, char *desc)
{
	int i;
	LegItem *li;

	if(Items) for(i = 0; i < nItems; i++) {
		if(Items[i] && Items[i]->HasErr(ld, err)) return true;
		}
	if(li = new LegItem(this, data, ld, err | (hasLine ? 0x40 : 0), desc)){
		if(!(Command(CMD_DROP_OBJECT, li, 0L))) DeleteGO(li);
		}
	return false;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Graphs are graphic objects containing plots, axes, and drawn objects
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Graph::Graph(GraphObj *par, DataObj *d, anyOutput *o, int style):GraphObj(par, d)
{
	Graph::FileIO(INIT_VARS);
	Disp = o;		Id = GO_GRAPH;		cGraphs++;	bModified = true;
	if(style & 0x10) y_axis.flags |= AXIS_INVERT;
	if (!d && parent) parent->Command(CMD_DELOBJ, this, NULL);
}

Graph::Graph(int src):GraphObj(0L, 0L)
{
	int i;

	Graph::FileIO(INIT_VARS);
	if(src == FILE_READ) {
		FileIO(FILE_READ);
		x_axis.owner = y_axis.owner = (void *)this;
		//do all axes
		for(i = 0; Axes && i< NumAxes; i++) if(Axes[i]) Axes[i]->parent = this;
		//do all plots
		for(i = 0; Plots && i< NumPlots; i++) if(Plots[i]) Plots[i]->parent = this;
		if(x_axis.max > x_axis.min && y_axis.max > y_axis.min &&
			Bounds.Xmax > Bounds.Xmin && Bounds.Ymax > Bounds.Ymin) dirty = false;
		}
	cGraphs++;		bModified = false;
}

Graph::~Graph()
{
	int i;

	if(!parent) return;			parent = 0L;
	Undo.InvalidGO(this);		DoZoom("reset");
	if(CurrGraph == this) CurrGraph = 0L;
	if(Plots) {
		for(i = 0; i< NumPlots; i++) if(Plots[i]) DeleteGO(Plots[i]);
		free(Plots);		Plots = 0L;		NumPlots = 0;
		}
	if(Axes) {
		for(i = 0; i< NumAxes; i++) if(Axes[i]) DeleteGO(Axes[i]);
		free(Axes);			Axes = 0L;		NumAxes = 0;
		}
	if(OwnDisp && Disp) DelDispClass(Disp);		OwnDisp = false;	Disp = 0L;
	if(frm_g) DeleteGO(frm_g);		if(frm_d) DeleteGO(frm_d);
	if(x_axis.breaks && x_axis.owner == this) free(x_axis.breaks);
	if(y_axis.breaks && y_axis.owner == this) free(y_axis.breaks);
	if(tl_pts) free(tl_pts);
	if(nscp > 0 && nscp <= NumPlots && Sc_Plots) free(Sc_Plots);
	nscp = 0;			Sc_Plots = 0L;
	if(name) free(name);		name = 0L;
	if(filename) free(filename);	filename= 0L;
}

double
Graph::GetSize(int select)
{
	return Graph::DefSize(select);
}

bool
Graph::SetSize(int select, double val)
{
	switch(select & 0xfff) {
	case SIZE_GRECT_TOP:	GRect.Ymin = val;	return true;
	case SIZE_GRECT_BOTTOM:	GRect.Ymax = val;	return true;
	case SIZE_GRECT_LEFT:	GRect.Xmin = val;	return true;
	case SIZE_GRECT_RIGHT:	GRect.Xmax = val;	return true;
	case SIZE_DRECT_TOP:	DRect.Ymin = val;	return true;
	case SIZE_DRECT_BOTTOM:	DRect.Ymax = val;	return true;
	case SIZE_DRECT_LEFT:	DRect.Xmin = val;	return true;
	case SIZE_DRECT_RIGHT:	DRect.Xmax = val;	return true;
	default: return false;
		}
}

DWORD
Graph::GetColor(int select)
{
	switch(select & 0xfff) {
	case COL_AXIS:		return ColAX;
	case COL_BG:		return ColDR;
	case COL_DRECT:		return ColDR;
	case COL_GRECT:		return ColGR;
		}
	if(parent) return parent->GetColor(select);
	else return defs.Color(select);
}

bool
Graph::SetColor(int select, DWORD col)
{
	switch(select) {
	case COL_DRECT:
		ColDR = col;		return true;
	case COL_GRECT:
		ColGR = col;		return true;
		}
	return false;
}

void
Graph::DoPlot(anyOutput *target)
{
	int i, cb;
	AxisDEF *ax;

	if(nscp > 0 && nscp <= NumPlots && Sc_Plots) free(Sc_Plots);
	nscp = 0;		Sc_Plots = 0L;
	if(target && parent && parent->Id == GO_SPREADDATA) target->OC_type &= ~OC_TRANSPARENT;
	rc_mrk.left = rc_mrk.right = rc_mrk.top = rc_mrk.bottom = -1;
	CurrAxes = Axes;						defs.Idle(CMD_FLUSH);
	if(data) do_formula(data, 0L);			//init mfcalc
	//every graph needs axes!
	if(type == GT_STANDARD && !NumAxes) CreateAxes(AxisTempl);
	//verify ownership of axes
	if(Axes) for (i = 0; i < NumAxes; i++){
		if(Axes[i] && (ax = Axes[i]->GetAxis()))
			if(ax == &x_axis || ax == &y_axis) ax->owner = this;
		}
	if(!name){
#ifdef USE_WIN_SECURE
		cb = sprintf_s(TmpTxt, TMP_TXT_SIZE, "Graph %d", cGraphs) + 2;
#else
		cb = sprintf(TmpTxt, "Graph %d", cGraphs) + 2;
#endif
		name = (char*)realloc(name, cb);		rlp_strcpy(name, cb, TmpTxt);
		}
	if(!target && !Disp) {
		Disp = NewDispClass(this);
		Disp->SetMenu(MENU_GRAPH);
		if(name) Disp->Caption(name, false);
		Disp->VPorg.fy = (double)Disp->MenuHeight;
		Disp->CheckMenu(ToolMode, true);
		OwnDisp = true;						defs.SetDisp(Disp);
		bModified = false;			//first graph is not modified!
		Undo.SetDisp(Disp);
		}
	//the first output class is the display class
	if(!Disp && (!(Disp = target))) return;
	Disp->ActualSize(&defs.clipRC);
	if(OwnDisp) Disp->Erase(Disp->dFillCol = defs.Color(COL_BG));
	CurrDisp = target ? target : Disp;
	CurrDisp->MrkMode = MRK_NONE;
	CurrRect.Xmin=GRect.Xmin + DRect.Xmin;	CurrRect.Xmax=GRect.Xmin + DRect.Xmax;
	CurrRect.Ymin=GRect.Ymin + DRect.Ymax;	CurrRect.Ymax=GRect.Ymin + DRect.Ymin;
	if(dirty) DoAutoscale();
	CurrDisp->SetRect(CurrRect, units, &x_axis, &y_axis);
	CurrDisp->disp_x = CurrDisp->un2fix(GRect.Xmin);
	CurrDisp->disp_y = CurrDisp->un2fiy(GRect.Ymin);
	if(!frm_g && !(frm_g = new FrmRect(this, 0L, &GRect, 0L))) return;
	frm_g->SetColor(COL_GRECT, ColGR);	frm_g->SetColor(COL_GRECTLINE, ColGRL);
	frm_g->DoPlot(CurrDisp);
	if(type == GT_STANDARD) {
		if(!frm_d && !(frm_d = new FrmRect(this, &GRect, &DRect, 0L))) return;
		SetMinMaxRect(&rDims, CurrDisp->co2ix(CurrRect.Xmin), CurrDisp->co2iy(CurrRect.Ymax), 
			CurrDisp->co2ix(CurrRect.Xmax), CurrDisp->co2iy(CurrRect.Ymin));
		frm_g->Command(CMD_MINRC, &rDims, CurrDisp);
		SetMinMaxRect(&rDims, CurrDisp->co2ix(GRect.Xmin), CurrDisp->co2iy(GRect.Ymax), 
			CurrDisp->co2ix(GRect.Xmax), CurrDisp->co2iy(GRect.Ymin));
		frm_d->Command(CMD_MAXRC, &rDims, CurrDisp);
		frm_d->SetColor(COL_DRECT, ColDR);		frm_d->DoPlot(CurrDisp);
		frm_g->Command(CMD_SETCHILD, &DRect, CurrDisp);
		}
	//do all axes
	if(Axes) for(i = 0; i< NumAxes; i++) if(Axes[i]){
		Axes[i]->SetColor(COL_BG, ColGR);
		Axes[i]->DoPlot(CurrDisp);
		}
	//do all plots
	if(Plots) for(i = 0; i < NumPlots; i++) if(Plots[i]) {
		if(Plots[i]->Id >= GO_PLOT && Plots[i]->Id < GO_GRAPH) {
			if(((Plot*)Plots[i])->hidden == 0) Plots[i]->DoPlot(CurrDisp);
			}
		else {
			Plots[i]->DoPlot(CurrDisp);
			}
		}
	if(bModified && data) data->Command(CMD_MRK_DIRTY, 0L, 0L);
	if(parent && parent->Id == GO_GRAPH) {
		parent->Command(CMD_AXIS, 0L, 0L);
		}
	if(target && ToolMode == TM_STANDARD) target->MouseCursor(MC_ARROW, false);
}

bool
Graph::Command(int cmd, void *tmpl, anyOutput *o)
{
	MouseEvent *mev;
	GraphObj **tmpPlots;
	char *f_name;
	RECT rc;
	int i, j;
	DWORD delflg = 0L;

	if(!o) o = Disp ? Disp : CurrDisp;
	switch (cmd){
    case CMD_CAN_CLOSE:
		HideTextCursor();
		if(bModified) {
			if (Undo.isEmpty(CurrDisp)) return true;
			bModified = false;
#ifdef USE_WIN_SECURE
			sprintf_s(TmpTxt, TMP_TXT_SIZE, "%s has not been saved.\nDo you want to save it now?", name);
#else
			sprintf(TmpTxt, "%s has not been saved.\nDo you want to save it now?", name);
#endif
			i = YesNoCancelBox(TmpTxt);
			if(i == 2) return false;
			else if(i == 1) if(! SaveGraphAs(this)) return false;
			}
		return true;
	case CMD_SAVEAS:
		return SaveGraphAs(this);
	case CMD_SCALE:
		return DoScale((scaleINFO*)tmpl, o);
	case CMD_LAYERS:
		Undo.SetDisp(o ? o : CurrDisp);
		return ShowLayers(this);
	case CMD_HASSTACK:
		return(NumPlots > 1);
	case CMD_SAVEPOS:
		Undo.ValRect(this, &GRect, 0L);
		Undo.ValRect(this, &DRect, UNDO_CONTINUE);
		return true;
	case CMD_LEGEND:
		Undo.SetDisp(o ? o : CurrDisp);
		if(Id == GO_PAGE) {
			if(CurrGraph && CurrGraph->parent == this) return CurrGraph->Command(cmd, tmpl, o);
			if(!CurrGraph && NumPlots == 1 && Plots[0] && Plots[0]->Id == GO_GRAPH){
				CurrGraph = (Graph*)Plots[0];
				return CurrGraph->Command(cmd, tmpl, o);
				}
			InfoBox("No graph selected!\nCreate a new graph first or select\n"
				"a graph before you can add a legend.");
			return false;
			}
		if(Id == GO_GRAPH && !tmpl && Plots) {
			for(i = 0; i< NumPlots; i++){
				if(Plots[i] && Plots[i]->Id == GO_LEGEND) {
					tmpl = (void*)Plots[i];
					Undo.ObjConf(Plots[i], 0L);
					break;
					}
				}
			if(!tmpl) {
				if(!(tmpl = (void*) new Legend(this, data)))return false;
				if(Disp) {
					Undo.SetDisp(Disp);
					tmpPlots = (GraphObj**)memdup(Plots, sizeof(GraphObj*) * (NumPlots+2), 0);
					if(!tmpPlots) return false;
					Undo.ListGOmoved(Plots, tmpPlots, NumPlots);
					Undo.SetGO(this, &tmpPlots[NumPlots++], (Plot *)tmpl, 0L);
					free(Plots);		Plots = tmpPlots;	bModified = true;
					}
				else if(Plots = (GraphObj**)realloc(Plots, sizeof(GraphObj*) * (NumPlots+2))){
					Plots[NumPlots++] = (GraphObj*)tmpl;	Plots[NumPlots] = 0L;
					}
				else return false;
				}
			if(type == GT_CIRCCHART)((Legend*)tmpl)->SetSize(SIZE_XPOS, DRect.Xmin*3.0);
			for(i = 0; i< NumPlots; i++) if(Plots[i]) Plots[i]->Command(cmd, tmpl,o);
			if(Disp) Command(CMD_REDRAW, 0L, CurrDisp);
			}
		else if(Id == GO_GRAPH) {
			for(i = 0; i< NumPlots; i++) if(Plots[i]) Plots[i]->Command(cmd, tmpl,o);
			}
		return true;
	case CMD_REPL_GO:
		if(!(tmpPlots = (GraphObj **)tmpl) || !tmpPlots[0] || !tmpPlots[1]) return false;
		if(Axes) for(i = 0; i < NumAxes; i++) if(Axes[i] && Axes[i] == tmpPlots[0]){
			tmpPlots[1]->parent = this;
			tmpPlots[1]->Command(CMD_SET_DATAOBJ, data, o);
			Axes[i] = (Axis *)tmpPlots[1];
			tmpPlots[0]->parent = 0L;		//disable messaging
			//check for default axes
			if(((Axis*)tmpPlots[0])->GetAxis() == &x_axis) {
				if(x_axis.breaks) free(x_axis.breaks);
				memcpy(&x_axis, Axes[i]->axis, sizeof(AxisDEF));
				if(x_axis.owner == Axes[i]) free(Axes[i]->axis);
				Axes[i]->axis = &x_axis;			x_axis.owner = this;
				}
			else if(((Axis*)tmpPlots[0])->GetAxis() == &y_axis) {
				if(y_axis.breaks) free(y_axis.breaks);
				memcpy(&y_axis, Axes[i]->axis, sizeof(AxisDEF));
				if(y_axis.owner == Axes[i]) free(Axes[i]->axis);
				Axes[i]->axis = &y_axis;			y_axis.owner = this;
				}
			DeleteGO(tmpPlots[0]);
			return bModified = dirty = true;
			}
		if(Plots) for(i = 0; i < NumPlots; i++) if(Plots[i] && Plots[i] == tmpPlots[0]) { 
			return bModified = dirty = ReplaceGO((GraphObj**)&Plots[i], tmpPlots);
			}
		return false;
	case CMD_MUTATE:
		if(!parent || !(tmpPlots = (GraphObj **)tmpl) || !tmpPlots[0] || !tmpPlots[1]) return false;
		if(Plots) for (i = 0; i < NumPlots; i++) if(Plots[i] && Plots[i] == tmpPlots[0]) {
			Undo.MutateGO((GraphObj**)&Plots[i], tmpPlots[1], 0L, o);
			if(ToolMode) Command(CMD_TOOLMODE, 0L, o);
			return bModified = true;
			}
		break;
	case CMD_REDRAW:
		if(!CurrDisp) {
			DoPlot(CurrDisp);			return true;
			}
		if(Disp)CurrDisp = Disp;		bDialogOpen = false;
		if(parent && (parent->Id == GO_PAGE || parent->Id == GO_GRAPH)) return parent->Command(cmd, tmpl, o);
		if(CurrDisp && CurrDisp->Erase(ColBG)) CurrDisp->StartPage();
		CurrDisp->MrkMode = MRK_NONE;			DoPlot(CurrDisp);
		if(CurrDisp) CurrDisp->EndPage();		if(CurrGO == this) CurrGO = 0L;
		if(CurrGO && CurrGO == CurrLabel && CurrLabel->Id == GO_LABEL)
			CurrDisp->ShowMark(CurrLabel, MRK_GODRAW);
		return true;
	case CMD_ZOOM:
		return DoZoom((char*)tmpl);
	case CMD_MOUSECURSOR:
		if(o)o->MouseCursor(PasteObj ? MC_PASTE : MC_ARROW, false);	
		return true;
	case CMD_AXIS:			//one of the plots has changed scaling: reset
		CurrAxes = Axes;
		if(o)o->SetRect(CurrRect, units, &x_axis, &y_axis);
		return true;
	case CMD_REG_AXISPLOT:	//notification: plot can handle its own axes
		if(nscp > 0 && nscp <= NumPlots && 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_BUSY:
		if(Disp) Disp->MouseCursor(MC_WAIT, true);
		break;
	case CMD_UNDO:
		Command(CMD_TOOLMODE, 0L, o);
		if(CurrDisp) CurrDisp->MouseCursor(MC_WAIT, true);
		Undo.Restore(true, CurrDisp);
		if(CurrDisp) CurrDisp->MouseCursor(MC_ARROW, true);
		return true;
	case CMD_ADDAXIS:
		Undo.SetDisp(o ? o : CurrDisp);		Command(CMD_TOOLMODE, 0L, o);
		if(Id == GO_PAGE) {
			if(CurrGraph && CurrGraph->parent == this) return CurrGraph->Command(cmd, tmpl, o);
			if(!CurrGraph && NumPlots == 1 && Plots[0] && Plots[0]->Id == GO_GRAPH){
				CurrGraph = (Graph*)Plots[0];
				return CurrGraph->Command(cmd, tmpl, o);
				}
			InfoBox("No graph selected!\nCreate a new graph first or select\n"
				"a graph before you can add an axis.");
			return false;
			}
		else {
			if(type == GT_3D && Plots){
				for(i = 0; i < NumPlots; i++)
					if(Plots[i] && (Plots[i]->Id == GO_PLOT3D || Plots[i]->Id == GO_FUNC3D
						|| Plots[i]->Id == GO_FITFUNC3D))
						return Plots[i]->Command(cmd, tmpl, o); 
				}
			else if(AddAxis()) {
				Command(CMD_REDRAW, tmpl, o);
				return true;
				}
			}
		return false;
	case CMD_CONFIG:
		Command(CMD_TOOLMODE, 0L, o);		if(CurrGO == this) CurrGO = 0L;
		return Configure();
	case CMD_FILENAME:
		if(tmpl) {
			i = (int)strlen((char*)tmpl)+2;
			filename = (char*)realloc(filename, i );
			rlp_strcpy(filename, i, (char*)tmpl);
			}
		break;
	case CMD_SETNAME:
		if(OwnDisp && CurrDisp && tmpl && *((char*)tmpl)){
			CurrDisp->Caption((char*)tmpl, false);
			i = (int)strlen((char*)tmpl);
			j = name && name[0] ? (int)strlen(name) : i;
			name = (char*)realloc(name, i > j ? i+2 : j+2);
			rlp_strcpy(name, i+2, (char*)tmpl);
			}
		else return false;
		return true;
	case CMD_SET_DATAOBJ:
		Id = GO_GRAPH;
		data = (DataObj *)tmpl;
		//do axes
		if(Axes) for(i = 0; i< NumAxes; i++) if(Axes[i]) Axes[i]->Command(cmd, tmpl, o);
		//do all plots
		if(Plots) for(i = 0; i< NumPlots; i++) if(Plots[i]) Plots[i]->Command(cmd, tmpl,o);
		return true;
	case CMD_OPEN:
		Command(CMD_TOOLMODE, 0L, o);
		if(!parent) return false;
		f_name = OpenGraphName(filename);
		if(f_name && f_name[0]) {
			if(data) data->Command(CMD_UPDHISTORY, 0L, 0L);
			return OpenGraph(Id == GO_PAGE ? this : parent, f_name, 0L, false);
			}
		return true;
	case CMD_UPDHISTORY:
		if(data) data->Command(CMD_UPDHISTORY, 0L, 0L);
		return true;
	case CMD_UPDATE:
		Command(CMD_TOOLMODE, 0L, o);
		Undo.SetDisp(CurrDisp);
		if(parent && parent->Id != GO_PAGE && parent->Id != GO_GRAPH){
			Undo.ValInt(this, &ToolMode, 0L);		//stub, all plots have UNDO_CONTINUE
			}
		if(CurrDisp) CurrDisp->MouseCursor(MC_WAIT, true);
		for(i = 0; i < NumAxes; i++) if(Axes[i]) Axes[i]->Command(cmd, tmpl, o);
		if(CurrDisp) CurrDisp->MouseCursor(MC_WAIT, false);
		for(i = 0; Plots && i < NumPlots; i++) 
			if(Plots[i]) Plots[i]->Command(cmd, tmpl, o);
		dirty = bModified = true;		CurrDisp->StartPage();
		if(CurrDisp) CurrDisp->MouseCursor(MC_WAIT, false);
		DoPlot(CurrDisp);		CurrDisp->EndPage();		CurrGO = 0L;
		return true;
	case CMD_DELOBJ_CONT:
		delflg = UNDO_CONTINUE;
	case CMD_DELOBJ:
		Command(CMD_TOOLMODE, 0L, o);
		bModified = true;
		if(!tmpl) return false;
		for(i = 0; i < NumAxes; i++) if(Axes[i] && (void*)Axes[i] == tmpl){
			if(Axes[i]->Command(CMD_CAN_CLOSE, 0L, o)) {
				Undo.DeleteGO((GraphObj**)(&Axes[i]), delflg, o);
				return Command(CMD_REDRAW, 0L, o);
				}
			else {
				InfoBox("Axes used for scaling\ncan not be deleted.");
				return false;
				}
			}
		for(i = 0; Plots && i < NumPlots; i++) if(Plots[i] && (void*)Plots[i] == tmpl) {
			Undo.DeleteGO(&Plots[i], delflg, o);			HideTextCursor();
			Undo.StoreListGO(this, &Plots, &NumPlots, UNDO_CONTINUE);
			for(i = j = 0; i < NumPlots; i++) if(Plots[i]) Plots[j++] = Plots[i];
			NumPlots = j;			//compress list of objects
			return Command(CMD_REDRAW, NULL, o);
			}
		if(tmpl == (void*)frm_g && parent && (parent->Id == GO_PAGE || parent->Id == GO_GRAPH)) 
			return parent->Command(CMD_DELOBJ_CONT, (void*)this, o);
		return false;
	case CMD_TOOLMODE:
		if(o && tmpl) {
			Undo.SetDisp(o);
			o->CheckMenu(ToolMode & 0x0f, false);
			o->CheckMenu(ToolMode = tmpl ? (*((int*)tmpl)) & 0x0f : TM_STANDARD, true);
			}
		if(tl_pts && tl_nPts) free(tl_pts);
		tl_pts = 0L;	tl_nPts = 0;
		if(CurrDisp) CurrDisp->MrkMode = MRK_NONE;
		if(o) switch(ToolMode & 0x0f) {
		case TM_TEXT:	
			o->MouseCursor(MC_TEXT, false);			break;
		case TM_DRAW:		case TM_POLYLINE:		case TM_POLYGON:
			o->MouseCursor(MC_DRAWPEN, false);		break;
		case TM_RECTANGLE:	
			o->MouseCursor(MC_DRAWREC, false);		break;
		case TM_ELLIPSE:		
			o->MouseCursor(MC_DRAWELLY, false);		break;
		case TM_ROUNDREC:
			o->MouseCursor(MC_DRAWRREC, false);		break;
		case TM_ARROW:
			o->MouseCursor(MC_CROSS, false);		break;
		default:
			o->MouseCursor(MC_ARROW, true);			break;
			}
		if((ToolMode & 0x0f) != TM_TEXT) HideTextCursor();
		return Command(CMD_REDRAW, 0L, CurrDisp);
	case CMD_ADDPLOT:
		Undo.SetDisp(o ? o : CurrDisp);
		if(Id == GO_PAGE) {
			if(CurrGraph && CurrGraph->parent == this) return CurrGraph->Command(cmd, tmpl, o);
			if(!CurrGraph && NumPlots == 1 && Plots[0] && Plots[0]->Id == GO_GRAPH){
				CurrGraph = (Graph*)Plots[0];
				return CurrGraph->Command(cmd, tmpl, o);
				}
			InfoBox("No graph selected!\nCreate a new graph first or select\n"
				"a graph before you can add a plot.");
			return false;
			}
		else if(Id == GO_GRAPH) {
			if(type == GT_3D && Plots){
				for(i = 0; i < NumPlots; i++) { 
					if(Plots[i] && (Plots[i]->Id == GO_PLOT3D || Plots[i]->Id == GO_FUNC3D
						|| Plots[i]->Id == GO_FITFUNC3D)) {
						if(Plots[i]->Command(cmd, tmpl, CurrDisp)) return Command(CMD_REDRAW, 0L, o);
						else return false;
						}
					}
				return false;
				}
			else return AddPlot(0x0);
			}
		return false;
	case CMD_MRK_DIRTY:
		return (dirty = true);
	case CMD_CURRLEFT:	case CMD_CURRIGHT:	case CMD_ADDCHAR:	case CMD_ADDCHARW:
	case CMD_BACKSP:	case CMD_POS_FIRST:	case CMD_POS_LAST:
		defs.SetDisp(o);
		if(tmpl && *((int*)tmpl) == 27) {			//Escape
			HideCopyMark();
			if(CurrGO && CurrGO->Id == GO_TEXTFRAME) {
				CurrGO->DoMark(o, false);				o->MrkMode = MRK_NONE;
				CurrGO = 0L;
				}
			else o->HideMark();
			CurrLabel = 0L;								HideTextCursor();
			}
		if(CurrLabel) return CurrLabel->Command(cmd, tmpl, o);
		if(CurrGO && CurrGO->Id == GO_TEXTFRAME) return CurrGO->Command(cmd, tmpl, o);
		else if(CurrGO && CurrGO->Id != GO_LABEL && tmpl && *((int*)tmpl) == 13) {
			CurrGO->PropertyDlg();
			}
	case CMD_CURRUP:	case CMD_CURRDOWN:
		if(CurrLabel && CurrLabel == CurrGO){
			if(CurrLabel->parent && CurrLabel->parent->Id == GO_MLABEL)
				return CurrLabel->parent->Command(cmd, tmpl, o);
			return true;
			}
		else if(CurrGO && CurrGO->Id == GO_TEXTFRAME) return CurrGO->Command(cmd, tmpl, o);
	case CMD_SHIFTLEFT:	case CMD_SHIFTRIGHT:	case CMD_SHIFTUP:	case CMD_SHIFTDOWN:
		if(Id == GO_PAGE || (CurrGraph && CurrGraph->parent == this)) {
			if(CurrGraph && CurrGraph->parent == this) return CurrGraph->Command(cmd, tmpl, o);
			else if(CurrGO && CurrGO->Id == GO_TEXTFRAME) return CurrGO->Command(cmd, tmpl, o);
			else if(CurrGO && CurrGO->Id == GO_LABEL) return CurrGO->Command(cmd, tmpl, o);
			}
		else {
			if(type == GT_3D && Plots) {
				for(i = 0; i < NumPlots; i++) {
					if(Plots[i] && (Plots[i]->Id == GO_PLOT3D || Plots[i]->Id == GO_FUNC3D
						|| Plots[i]->Id == GO_FITFUNC3D))
						return Plots[i]->Command(cmd, tmpl, CurrDisp);
					}
				}
			else if(CurrGO && CurrGO->Id == GO_TEXTFRAME) return CurrGO->Command(cmd, tmpl, o);
			else if(CurrGO && CurrGO->Id == GO_LABEL) return CurrGO->Command(cmd, tmpl, o);
			}
		return false;
	case CMD_MOVE_TOP:	case CMD_MOVE_UP:
	case CMD_MOVE_DOWN:	case CMD_MOVE_BOTTOM:
		Undo.StoreListGO(this, &Plots, &NumPlots, 0L);
		if(MoveObj(cmd, (GraphObj *)tmpl)){
			bModified = true;
			CurrDisp->StartPage();			DoPlot(CurrDisp);
			CurrDisp->EndPage();			return true;
			}
		return false;
	case CMD_DELETE:
		if(!CurrGO) return false;			bModified = true;
		if(CurrGO->Id == GO_TEXTFRAME) return CurrGO->Command(cmd, tmpl, o);
		if(CurrGO == CurrLabel) return CurrLabel->Command(cmd, tmpl, o);
		if(CurrGO->Id == GO_FRAMERECT) if(!(CurrGO = CurrGO->parent))return false;
		if(CurrGO->parent == this) return Command(CMD_DELOBJ, (void*)CurrGO, o);
		if(CurrGO->parent)return CurrGO->parent->Command(CMD_DELOBJ, (void*)CurrGO, o);
		return false;
	case CMD_DROP_GRAPH:
		if(Disp) {
			if(!(tmpPlots = (GraphObj**)memdup(Plots, (NumPlots+2)*sizeof(GraphObj*), 0))) 
				return false;
			Undo.ListGOmoved(Plots, tmpPlots, NumPlots);
			free(Plots);			Plots = tmpPlots;	Plots[NumPlots] = Plots[NumPlots+1] = 0L;
			Undo.SetGO(this, &Plots[NumPlots], (GraphObj*)tmpl, 0L);
			}
		else if(Plots = (GraphObj**)realloc(Plots, sizeof(GraphObj*) * (NumPlots+2))){
			Plots[NumPlots] = (GraphObj*)tmpl;	Plots[NumPlots+1] = 0L;
			}
		else return false;
		if(Plots[NumPlots]){
			Plots[NumPlots]->parent = this;
			Plots[NumPlots]->Command(CMD_SET_DATAOBJ, data, 0L);
			if(Plots[NumPlots]->Id == GO_GRAPH) CurrGraph = (Graph*)Plots[NumPlots];
			Plots[NumPlots]->moveable = 1;		//all page items should be freely
			}									//   moveable by user
		NumPlots++;
		if(CurrDisp) {
			CurrDisp->StartPage();		DoPlot(CurrDisp);		CurrDisp->EndPage();
			}
		return true;
	case CMD_DROP_PLOT:
		if(!tmpl) return false;
		((GraphObj*)tmpl)->parent = this;
		if(((GraphObj*)tmpl)->Id < GO_PLOT) {
			if(!NumPlots && Id != GO_PAGE) return false;
			Plots = (GraphObj**)realloc(Plots, (NumPlots+2)*sizeof(GraphObj*));
			Plots[NumPlots] = (GraphObj*)tmpl;
			NumPlots++;
			if(CurrDisp) {
				CurrDisp->StartPage();
				DoPlot(CurrDisp);
				CurrDisp->EndPage();
				}
			return true;
			}
		if(Id == GO_GRAPH) CurrGraph = this;	bModified =true;
		if(!NumPlots) {
			Plots = (GraphObj**)calloc(2, sizeof(GraphObj*));
			if(Plots) {
				Plots[0] = (Plot *)tmpl;	Plots[0]->parent = this;
				switch(Plots[0]->Id) {
				case GO_PIECHART:		case GO_RINGCHART:	case GO_STARCHART:
					type = GT_CIRCCHART;
					break;
				case GO_POLARPLOT:
					type = GT_POLARPLOT;
					break;
				case GO_SCATT3D:		case GO_PLOT3D:		case GO_FUNC3D:
				case GO_FITFUNC3D:
					type = GT_3D;
					break;
				default:
					type = GT_STANDARD;
					break;
					}
				Bounds.Xmin = x_axis.min = ((Plot*)Plots[0])->Bounds.Xmin;
				Bounds.Xmax = x_axis.max = ((Plot*)Plots[0])->Bounds.Xmax;
				Bounds.Ymin = y_axis.min = ((Plot*)Plots[0])->Bounds.Ymin;
				Bounds.Ymax = y_axis.max = ((Plot*)Plots[0])->Bounds.Ymax;
				if(Bounds.Ymax == Bounds.Ymin) {
					if(Bounds.Ymax != 0.0f) {
						Bounds.Ymax = y_axis.max = Bounds.Ymax + (Bounds.Ymax)/10.0f;
						Bounds.Ymin = y_axis.min = Bounds.Ymin - (Bounds.Ymax)/10.0f;
						}
					else {
						Bounds.Ymax = y_axis.max = 1.0f;
						Bounds.Ymin = y_axis.min = -1.0f;
						}
					}
				if(Bounds.Xmax == Bounds.Xmin) {
					if(Bounds.Xmax != 0.0f) {
						Bounds.Xmax = x_axis.max = Bounds.Xmax + (Bounds.Xmax)/10.0f;
						Bounds.Xmin = x_axis.min = Bounds.Xmin - (Bounds.Xmax)/10.0f;
						}
					else {
						Bounds.Xmax = 1.0f;
						Bounds.Xmin = -1.0f;
						}
					}
				NiceAxis(&x_axis, 4);				NiceAxis(&y_axis, 4);
				NumPlots = 1;
				dirty = false;
				return true;
				}
			return false;
			}
		else {
			if(Disp) {
				Undo.SetDisp(Disp);
				tmpPlots = (GraphObj**)memdup(Plots, sizeof(GraphObj*) * (NumPlots+2), 0);
				Undo.ListGOmoved(Plots, tmpPlots, NumPlots);
				Undo.SetGO(this, &tmpPlots[NumPlots++], (Plot *)tmpl, 0L);
				free(Plots);			Plots = tmpPlots;
				}
			else if(Plots = (GraphObj**)realloc(Plots, sizeof(GraphObj*) * (NumPlots+2))){
				Plots[NumPlots++] = (Plot*)tmpl;	Plots[NumPlots] = 0L;
				}
			else return false;
			if(type == GT_STANDARD && ((x_axis.flags & AXIS_AUTOSCALE) || 
				(y_axis.flags & AXIS_AUTOSCALE))) {
				if(x_axis.flags & AXIS_AUTOSCALE) {
					Bounds.Xmin = x_axis.min = ((Plot *)tmpl)->Bounds.Xmin < Bounds.Xmin ?
						((Plot *)tmpl)->Bounds.Xmin : Bounds.Xmin;
					Bounds.Xmax = x_axis.max = ((Plot *)tmpl)->Bounds.Xmax > Bounds.Xmax ?
						((Plot *)tmpl)->Bounds.Xmax : Bounds.Xmax;
					NiceAxis(&x_axis, 4);
					if(Axes)for(i = 0; i < NumAxes; i++) 
						if(Axes[i]) Axes[i]->Command(CMD_AUTOSCALE, &x_axis, o);
					}
				if(y_axis.flags & AXIS_AUTOSCALE) {
					Bounds.Ymin = y_axis.min = ((Plot *)tmpl)->Bounds.Ymin < Bounds.Ymin ? 
						((Plot *)tmpl)->Bounds.Ymin : Bounds.Ymin;
					Bounds.Ymax = y_axis.max = ((Plot *)tmpl)->Bounds.Ymax > Bounds.Ymax ?
						((Plot *)tmpl)->Bounds.Ymax : Bounds.Ymax;
					NiceAxis(&y_axis, 4);
					if(Axes)for(i = 0; i < NumAxes; i++) 
						if(Axes[i]) Axes[i]->Command(CMD_AUTOSCALE, &y_axis, o);
					}
				}
			dirty = false;
			//Redraw graph to show all plots
			if(CurrDisp) {
				CurrDisp->StartPage();
				DoPlot(CurrDisp);
				CurrDisp->EndPage();
				}
			return true;
			}
		break;
	case CMD_PASTE_OBJ:
		if(!tmpl) return false;
		Undo.SetDisp(o ? o : CurrDisp);
		PasteObj = (GraphObj*)tmpl;
		if(PasteObj->Id == GO_GRAPH || PasteObj->Id == GO_POLYLINE || PasteObj->Id == GO_POLYGON 
			|| PasteObj->Id == GO_RECTANGLE || PasteObj->Id == GO_ROUNDREC || PasteObj->Id == GO_ELLIPSE
			|| PasteObj->Id == GO_BEZIER) {
			ToolMode = TM_PASTE;			o->MouseCursor(MC_PASTE, false);
			return true;
			}
		PasteObj = 0L;
		return false;
	case CMD_MOUSE_EVENT:
		mev = (MouseEvent *)tmpl;		defs.SetDisp(o);
		if(CurrGO && CurrGO->moveable && mev->Action == MOUSE_LBDOWN &&
			ToolMode == TM_STANDARD && (mev->StateFlags & 24) == 0 &&
			(TrackGO = (GraphObj*)CurrGO->ObjThere(mev->x, mev->y))){
			ToolMode |= TM_MOVE;
			}
		else if(mev->Action == MOUSE_LBDOWN){
			if(CurrGO && (CurrGO->Id == GO_TEXTFRAME || CurrGO->Id == GO_LABEL) && CurrGO->Command(cmd, tmpl, o)) return true;
			CurrGO = 0L;		rc_mrk.left = mev->x;		rc_mrk.top = mev->y;
			if((ToolMode == TM_TEXT || ToolMode == TM_STANDARD) && Command(CMD_TEXTTHERE, tmpl, o)) {
				o->CheckMenu(TM_TEXT, false);		o->CheckMenu(TM_STANDARD, true);
				ToolMode = TM_STANDARD;				return true;
				}
			SuspendAnimation(o, true);
			}
		if(ToolMode != TM_STANDARD && ExecTool(mev)) return true;
		switch(mev->Action) {
		case MOUSE_RBUP:
			i = ToolMode;							ToolMode = TM_STANDARD;
			mev->Action = MOUSE_LBUP;				//fake select
			CurrGO = 0L;		Command(cmd, tmpl, o);		ToolMode = i;
			if(!CurrGO) CurrGO = this;
			//the default behaviour for right button click is the same as for
			//   double click: execute properties dialog, just continue.
		case MOUSE_LBDOUBLECLICK:
			Undo.SetDisp(o);							bDialogOpen = true;
			if(!CurrGO){
				mev->Action = MOUSE_LBUP;
				Command(CMD_MOUSE_EVENT, mev, CurrDisp);
				mev->Action = MOUSE_LBDOUBLECLICK;
				if(!CurrGO) CurrGO = this;
				if(CurrGO->Command(CMD_CONFIG, 0L, o))	return Command(CMD_REDRAW, 0L, o);
				}
			else if(CurrGO->Id < GO_PLOT) {
				if(CurrGO->PropertyDlg()) {
					bModified = true;					return Command(CMD_REDRAW, 0L, o);
					}
				}
			else if(CurrGO->Command(CMD_CONFIG, 0L, o)) return Command(CMD_REDRAW, 0L, o);
			else o->HideMark();
			TrackGO = 0L;	CurrLabel = 0L;			bDialogOpen = false;
			if(CurrGO == this) CurrGO = 0L;
			return false;
		case MOUSE_LBUP:
			if(bDialogOpen) {
				bDialogOpen = false;					return false;
				}
			Undo.SetDisp(o);		SuspendAnimation(o, false);
			if(Id == GO_GRAPH && parent && parent->Id == GO_SPREADDATA){
				CurrGO = TrackGO = 0L;
				CurrGraph = 0L;
				}
		case MOUSE_MOVE:
			if(mev->Action == MOUSE_MOVE && !(mev->StateFlags & 0x01)) return false;
			//do all axes
			for(i = 0; Axes && i< NumAxes; i++)
				if(Axes[i] && Axes[i]->Command(cmd, tmpl,o)) return true;
			//do all plots
			if(Plots && NumPlots > 0) for(i = NumPlots-1; i>=0; i--){
				if(Plots[i] && Plots[i]->Command(cmd, tmpl, o)){
					if(Plots[i]->Id != GO_GRAPH && Id == GO_GRAPH) CurrGraph = this;
					return true;
					}
				}
			if(frm_d && frm_d->Command(cmd, tmpl, o) || frm_g && frm_g->Command(cmd, tmpl, o)) {
				if(Id == GO_GRAPH) CurrGraph = this;
				return true;
				}
			if(mev->Action == MOUSE_MOVE && ToolMode == TM_STANDARD && 
				rc_mrk.left >=0 && rc_mrk.top >=0) ToolMode = TM_MARK;
			if(!CurrGO) CurrGraph = 0L;
			return false;
			}
		break;
	case CMD_TEXTTHERE:
		//do all axes
		for(i = 0; Axes && i< NumAxes; i++)
			if(Axes[i] && Axes[i]->Command(cmd, tmpl,o)) return true;
		//do all plots
		if(Plots)for(i = NumPlots-1; i>=0; i--)
			if(Plots[i] && Plots[i]->Command(cmd, tmpl,o)) return true;
		break;
	case CMD_SETSCROLL:
		if(o) {
			if(!(o->ActualSize(&rc)))return false;
			i = o->un2iy(GRect.Ymax);
			o->SetScroll(true, -(i>>3), i+(i>>3), (rc.bottom -rc.top)>>1, - iround(o->VPorg.fy));
			i = o->un2ix(GRect.Xmax);
			o->SetScroll(false, -(i>>3), i+(i>>3), (rc.right -rc.left)>>1, - iround(o->VPorg.fx));
			if(CurrDisp && o->Erase(ColBG)) Command(CMD_REDRAW, 0L, o);
			return true;
			}
		return false;
	case CMD_SETHPOS:
		if(o && tmpl) o->VPorg.fx = - (double)(*((int*)tmpl));
		return Command(CMD_SETSCROLL, tmpl, o);
	case CMD_SETVPOS:
		if(o && tmpl) o->VPorg.fy = - (double)(*((int*)tmpl));
		return Command(CMD_SETSCROLL, tmpl, o);
	case CMD_OBJTREE:
		for(i = 0; Plots && i < NumPlots; i++) if(Plots[i]) {
			((ObjTree*)tmpl)->Command(CMD_UPDATE, Plots[i], 0L);
			if(Plots[i]->Id == GO_STACKBAR || Plots[i]->Id == GO_GRAPH || 
				Plots[i]->Id == GO_PLOT3D || Plots[i]->Id == GO_POLARPLOT ||
				Plots[i]->Id == GO_FUNC3D || Plots[i]->Id == GO_FITFUNC3D)
				Plots[i]->Command(cmd, tmpl, o);
			}
		return true;
		}
	return false;
}

double 
Graph::DefSize(int select)
{
	switch(select) {
	case SIZE_LB_XDIST:
	case SIZE_LB_YDIST:			return 0.0f;
	case SIZE_GRECT_TOP:		return GRect.Ymin;
	case SIZE_GRECT_BOTTOM:		return GRect.Ymax;
	case SIZE_GRECT_LEFT:		return GRect.Xmin;
	case SIZE_GRECT_RIGHT:		return GRect.Xmax;
	case SIZE_DRECT_TOP:		return DRect.Ymin;
	case SIZE_DRECT_BOTTOM:		return DRect.Ymax;
	case SIZE_DRECT_LEFT:		return DRect.Xmin;
	case SIZE_DRECT_RIGHT:		return DRect.Xmax;
	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_SCALE:			return scale > 0.0 ? scale : 1.0;
	case SIZE_BOUNDS_LEFT:		return x_axis.flags & AXIS_INVERT ? x_axis.max : x_axis.min;
	case SIZE_BOUNDS_RIGHT:		return x_axis.flags & AXIS_INVERT ? x_axis.min : x_axis.max;
	case SIZE_BOUNDS_TOP:		return y_axis.flags & AXIS_INVERT ? y_axis.min : y_axis.max;
	case SIZE_BOUNDS_BOTTOM:	return y_axis.flags & AXIS_INVERT ? y_axis.max : y_axis.min;
	case SIZE_YAXISX:
		if(y_axis.flags & AXIS_X_DATA) return CurrDisp->fx2fix(y_axis.loc[0].fx);
		else return CurrDisp->co2fix(y_axis.loc[0].fx);
	case SIZE_XAXISY:
		if(x_axis.flags & AXIS_Y_DATA) return CurrDisp->fy2fiy(x_axis.loc[0].fy);
		else return CurrDisp->co2fiy(x_axis.loc[0].fy);
		}
	if(scale > 0.0) return scale * defs.GetSize(select);
	else return defs.GetSize(select);
}
bool
Graph::hasTransp()
{
	if(OwnDisp && Disp && (Disp->OC_type & OC_TRANSPARENT)) return true;
	return false;
}

void
Graph::DoAutoscale()
{
	int i;
	fRECT oB;

	memcpy(&oB, &Bounds, sizeof(fRECT));
	if(type == GT_STANDARD && ((x_axis.flags & AXIS_AUTOSCALE) ||
		(y_axis.flags & AXIS_AUTOSCALE))) {
		for(i = 0; i < NumPlots; i++) {
			if(Plots[i] && (Plots[i]->Id == GO_PLOTSCATT || Plots[i]->Id == GO_BUBBLEPLOT ||
				Plots[i]->Id == GO_BOXPLOT || Plots[i]->Id == GO_STACKBAR ||
				Plots[i]->Id == GO_STACKPG || Plots[i]->Id == GO_DENSDISP ||
				Plots[i]->Id == GO_LIMITS || Plots[i]->Id == GO_FUNCTION ||
				Plots[i]->Id == GO_FITFUNC || Plots[i]->Id== GO_FREQDIST)) {
				bModified = true;
				if(dirty) {
					if(x_axis.flags & AXIS_AUTOSCALE) {
						Bounds.Xmin = HUGE_VAL;			Bounds.Xmax = -HUGE_VAL;
						}
					if(y_axis.flags & AXIS_AUTOSCALE) {
						Bounds.Ymin = HUGE_VAL;			Bounds.Ymax = -HUGE_VAL;
						}
					dirty = false;
					}
				Plots[i]->Command(CMD_AUTOSCALE, 0L, CurrDisp);
				if(!((Plot*)Plots[i])->hidden) {
					if(x_axis.flags & AXIS_AUTOSCALE) {
						Bounds.Xmin = ((Plot*)Plots[i])->Bounds.Xmin < Bounds.Xmin ?
							((Plot*)Plots[i])->Bounds.Xmin : Bounds.Xmin;
						Bounds.Xmax = ((Plot*)Plots[i])->Bounds.Xmax > Bounds.Xmax ?
							((Plot*)Plots[i])->Bounds.Xmax : Bounds.Xmax;
						}
					if(y_axis.flags & AXIS_AUTOSCALE) {
						Bounds.Ymin = ((Plot*)Plots[i])->Bounds.Ymin < Bounds.Ymin ?
							((Plot*)Plots[i])->Bounds.Ymin : Bounds.Ymin;
						Bounds.Ymax = ((Plot*)Plots[i])->Bounds.Ymax > Bounds.Ymax ?
							((Plot*)Plots[i])->Bounds.Ymax : Bounds.Ymax;
						}
					}
				}
			}
		if(Bounds.Xmax <= Bounds.Xmin) {
			Bounds.Xmax = oB.Xmax > oB.Xmin ? oB.Xmax : oB.Xmax + 1.0;
			Bounds.Xmin = oB.Xmin < oB.Xmax ? oB.Xmin : oB.Xmin - 1.0;
			}
		if(Bounds.Ymax <= Bounds.Ymin) {
			Bounds.Ymax = oB.Ymax > oB.Ymin ? oB.Ymax : oB.Ymax + 1.0;
			Bounds.Ymin = oB.Ymin < oB.Ymax ? oB.Ymin : oB.Ymin - 1.0;
			}
		if(x_axis.flags & AXIS_AUTOSCALE){
			x_axis.min = Bounds.Xmin;	x_axis.max = Bounds.Xmax;
			NiceAxis(&x_axis, 4);
			if(x_axis.min <= 0.0 && ((x_axis.flags & 0xf000) == AXIS_LOG ||
				(x_axis.flags & 0xf000) == AXIS_RECI)) {
				x_axis.min = base4log(&x_axis, 0);
				}
			if(Axes)for(i = 0; i < NumAxes; i++)
				if(Axes[i]) Axes[i]->Command(CMD_AUTOSCALE, &x_axis, 0L);
			}
		if(y_axis.flags & AXIS_AUTOSCALE){
			y_axis.min = Bounds.Ymin;	y_axis.max = Bounds.Ymax;
			NiceAxis(&y_axis, 4);
			if(y_axis.min <= 0.0 && ((y_axis.flags & 0xf000) == AXIS_LOG ||
				(y_axis.flags & 0xf000) == AXIS_RECI)) {
				y_axis.min = base4log(&y_axis, 1);
				}
			if(Axes)for(i = 0; i < NumAxes; i++)
				if(Axes[i]) Axes[i]->Command(CMD_AUTOSCALE, &y_axis, 0L);
			}
		}
	dirty = false;
}

void
Graph::CreateAxes(int templ)
{
	AxisDEF tmp_axis;
	TextDEF label_def, tlbdef;
	char label_text[500];
	Label *label;
	double ts, lb_ydist, lb_xdist, tlb_dist;
	DWORD ptick, ntick, utick;
	char xa_desc[50], ya_desc[50];

	if(Axes && NumAxes) return;
	label_def.ColTxt = defs.Color(COL_AXIS);				label_def.ColBg = 0x00ffffffL;
	label_def.fSize = DefSize(SIZE_TICK_LABELS)*1.2;		label_def.RotBL = label_def.RotCHAR = 0.0;
	label_def.iSize = 0;									label_def.Align = TXA_VTOP | TXA_HCENTER;
	label_def.Mode = TXM_TRANSPARENT;						label_def.Style = TXS_NORMAL;
	label_def.Font = FONT_HELVETICA;						label_def.text = label_text;
	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_HCENTER;
	tlbdef.Style = TXS_NORMAL;								tlbdef.Mode = TXM_TRANSPARENT;
	tlbdef.Font = FONT_HELVETICA;							tlbdef.text = 0L;
	ts = DefSize(SIZE_AXIS_TICKS);
	rlp_strcpy(xa_desc, 50, (char*)"x-axis");				rlp_strcpy(ya_desc, 50, (char*)"y-axis");
	if(Plots && NumPlots && Plots[0] && Plots[0]->Id >= GO_PLOT && Plots[0]->Id < GO_GRAPH) {
		if(((Plot*)Plots[0])->x_info) rlp_strcpy(xa_desc, 50,((Plot*)Plots[0])->x_info); 
		if(((Plot*)Plots[0])->y_info) rlp_strcpy(ya_desc, 50,((Plot*)Plots[0])->y_info); 
		}
	switch (tickstyle & 0x07){
	case 1:						//ticks inside
		ntick = AXIS_POSTICKS;		ptick = AXIS_POSTICKS;	utick = AXIS_NEGTICKS;
		ts *= 0.5;
		break;
	case 2:						//centered, symetrical
		ptick = ntick = utick = AXIS_SYMTICKS;
		ts *= 0.75;
		break;
	default:					//ticks outside
		ptick = AXIS_NEGTICKS;		ntick = AXIS_NEGTICKS; utick = AXIS_POSTICKS;
		break;
		}
	tlb_dist = NiceValue(ts * 2.0);
	lb_ydist = NiceValue((ts+DefSize(SIZE_AXIS_TICKS))*2.0);
	lb_xdist = NiceValue((ts+DefSize(SIZE_AXIS_TICKS))*3.0);
	switch(templ) {
	case 0:
		Axes = (Axis**)calloc(5, sizeof(Axis *));
		x_axis.loc[0].fx = GRect.Xmin + DRect.Xmin;
		x_axis.loc[1].fx = GRect.Xmin + DRect.Xmax;
		x_axis.loc[0].fy = x_axis.loc[1].fy = GRect.Ymin + DRect.Ymax;
		y_axis.loc[0].fy = GRect.Ymin + DRect.Ymin;
		y_axis.loc[1].fy = GRect.Ymin + DRect.Ymax;
		y_axis.loc[0].fx = y_axis.loc[1].fx = GRect.Xmin + DRect.Xmin;;
		if((Axes[0] = new Axis(this, data, &x_axis, AXIS_BOTTOM | ptick |	
			AXIS_AUTOTICK | AXIS_AUTOSCALE | ((tickstyle & 0x100) ? AXIS_GRIDLINE : 0)))){
			Axes[0]->SetSize(SIZE_LB_YDIST, lb_ydist);
			Axes[0]->SetSize(SIZE_TLB_YDIST, tlb_dist);
			rlp_strcpy(label_text, 500, xa_desc);
			label = new Label(Axes[0], data, (DRect.Xmin+DRect.Xmax)/2.0, 
				GRect.Ymin+DRect.Ymax+DefSize(SIZE_AXIS_TICKS)*4.0f, &label_def, LB_Y_PARENT);
			if(label && Axes[0]->Command(CMD_DROP_LABEL, (void*)label, 0L)) label = 0L;
			else if(label) DeleteGO(label);
			tlbdef.Align = TXA_VTOP | TXA_HCENTER;
			Axes[0]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		if((Axes[1] = new Axis(this, data, &y_axis, y_axis.flags | AXIS_LEFT | ntick | AXIS_AUTOTICK |
			AXIS_AUTOSCALE | ((tickstyle & 0x200) ? AXIS_GRIDLINE : 0)))){
			Axes[1]->SetSize(SIZE_LB_XDIST, -lb_xdist); 
			Axes[1]->SetSize(SIZE_TLB_XDIST, -tlb_dist); 
			rlp_strcpy(label_text, 500, ya_desc);
			label_def.RotBL = 90.0;			label_def.Align = TXA_VBOTTOM | TXA_HCENTER;
			label = new Label(Axes[1], data, GRect.Xmin + DRect.Xmin - DefSize(SIZE_AXIS_TICKS)*6.0, 
				(DRect.Ymax+DRect.Ymin)/2.0, &label_def, LB_X_PARENT);
			if(label && Axes[1]->Command(CMD_DROP_LABEL, (void*)label, 0L)) label = 0L;
			else if(label) DeleteGO(label);
			tlbdef.Align = TXA_VCENTER | TXA_HRIGHT;
			Axes[1]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		label = 0L;
		memcpy(&tmp_axis, &x_axis, sizeof(AxisDEF));
		tmp_axis.owner = NULL;
		tmp_axis.loc[0].fy = tmp_axis.loc[1].fy = GRect.Ymin + DRect.Ymax;
		if((Axes[2] = new Axis(this, data, &tmp_axis, AXIS_TOP | AXIS_NOTICKS | AXIS_AUTOTICK))){
			Axes[2]->SetSize(SIZE_LB_YDIST, -lb_ydist); 
			Axes[2]->SetSize(SIZE_TLB_YDIST, -tlb_dist); 
			tlbdef.Align = TXA_VBOTTOM | TXA_HCENTER;
			Axes[2]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		memcpy(&tmp_axis, &y_axis, sizeof(AxisDEF));
		tmp_axis.owner = NULL;
		tmp_axis.loc[0].fx = tmp_axis.loc[1].fx = GRect.Xmin + DRect.Xmax;
		if((Axes[3] = new Axis(this, data, &tmp_axis, AXIS_RIGHT | AXIS_NOTICKS | AXIS_AUTOTICK))){
			Axes[3]->SetSize(SIZE_LB_XDIST, lb_xdist); 
			Axes[3]->SetSize(SIZE_TLB_XDIST, tlb_dist); 
			tlbdef.Align = TXA_VCENTER | TXA_HLEFT;
			Axes[3]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		NumAxes = 4;
		break;
	case 1:
		Axes = (Axis**)calloc(7, sizeof(Axis *));
		if(x_axis.Start >= 0.0f) x_axis.min = x_axis.Start = -x_axis.Step;
		if(y_axis.Start >= 0.0f) y_axis.min = y_axis.Start = -y_axis.Step;
		x_axis.loc[0].fx = GRect.Xmin + DRect.Xmin;
		x_axis.loc[1].fx = GRect.Xmin + DRect.Xmax;
		x_axis.loc[0].fy = x_axis.loc[1].fy = 0.0f;
		y_axis.loc[0].fy = GRect.Ymin + DRect.Ymin;
		y_axis.loc[1].fy = GRect.Ymin + DRect.Ymax;
		y_axis.loc[0].fx = y_axis.loc[1].fx = 0.0f;
		if((Axes[0] = new Axis(this, data, &x_axis, ptick | AXIS_Y_DATA |	
			AXIS_AUTOTICK | AXIS_AUTOSCALE | ((tickstyle & 0x100) ? AXIS_GRIDLINE : 0)))){
			Axes[0]->SetSize(SIZE_LB_YDIST, lb_ydist);
			Axes[0]->SetSize(SIZE_TLB_YDIST, tlb_dist);
			rlp_strcpy(label_text, 500, xa_desc);
			label_def.Align = TXA_VTOP | TXA_HRIGHT;
			label = new Label(Axes[0], data, GRect.Xmin + DRect.Xmax - DefSize(SIZE_AXIS_TICKS)*2.0, 
				GRect.Ymin+DRect.Ymax+DefSize(SIZE_AXIS_TICKS)*4.0f, &label_def, LB_Y_PARENT);
			if(label && Axes[0]->Command(CMD_DROP_LABEL, (void*)label, 0L)) label = 0L;
			else if(label) DeleteGO(label);
			tlbdef.Align = TXA_VTOP | TXA_HCENTER;
			Axes[0]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		if((Axes[1] = new Axis(this, data, &y_axis, ntick | AXIS_AUTOTICK | AXIS_X_DATA |
			AXIS_AUTOSCALE | ((tickstyle & 0x200) ? AXIS_GRIDLINE : 0)))){
			Axes[1]->SetSize(SIZE_LB_XDIST, -lb_xdist); 
			Axes[1]->SetSize(SIZE_TLB_XDIST, -tlb_dist);
			rlp_strcpy(label_text, 500, ya_desc);
			label_def.RotBL = 90.0;			label_def.Align = TXA_VBOTTOM | TXA_HRIGHT;
			label = new Label(Axes[1], data, GRect.Xmin + DRect.Xmin - DefSize(SIZE_AXIS_TICKS)*6.0, 
				GRect.Ymin + DRect.Ymin + DefSize(SIZE_AXIS_TICKS)*2.0, 
				&label_def, LB_X_PARENT);
			if(label && Axes[1]->Command(CMD_DROP_LABEL, (void*)label, 0L)) label = 0L;
			else if(label) DeleteGO(label);
			tlbdef.Align = TXA_VCENTER | TXA_HRIGHT;
			Axes[1]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		memcpy(&tmp_axis, &x_axis, sizeof(AxisDEF));
		tmp_axis.owner = NULL;
		tmp_axis.loc[0].fy = tmp_axis.loc[1].fy = GRect.Ymin + DRect.Ymax;
		if(Axes[2] = new Axis(this, data, &tmp_axis, AXIS_TOP | AXIS_NOTICKS | AXIS_AUTOTICK)){
			Axes[2]->SetSize(SIZE_LB_YDIST, -lb_ydist); 
			Axes[2]->SetSize(SIZE_TLB_YDIST, -tlb_dist);
			tlbdef.Align = TXA_VBOTTOM | TXA_HCENTER;
			Axes[2]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		tmp_axis.loc[0].fy = tmp_axis.loc[1].fy = GRect.Ymin + DRect.Ymin;
		if(Axes[3] = new Axis(this, data, &tmp_axis, AXIS_BOTTOM | AXIS_NOTICKS | AXIS_AUTOTICK)){
			Axes[3]->SetSize(SIZE_LB_YDIST, lb_xdist); 
			Axes[3]->SetSize(SIZE_TLB_YDIST, tlb_dist); 
			tlbdef.Align = TXA_VTOP | TXA_HCENTER;
			Axes[3]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		memcpy(&tmp_axis, &y_axis, sizeof(AxisDEF));
		tmp_axis.owner = NULL;
		tmp_axis.loc[0].fx = tmp_axis.loc[1].fx = GRect.Xmin + DRect.Xmin;
		if(Axes[4] = new Axis(this, data, &tmp_axis, AXIS_LEFT | AXIS_NOTICKS | AXIS_AUTOTICK)){
			Axes[4]->SetSize(SIZE_LB_XDIST, -lb_xdist); 
			Axes[4]->SetSize(SIZE_TLB_XDIST, -tlb_dist); 
			tlbdef.Align = TXA_VCENTER | TXA_HRIGHT;
			Axes[4]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		tmp_axis.loc[0].fx = tmp_axis.loc[1].fx = GRect.Xmin + DRect.Xmax;
		if(Axes[5] = new Axis(this, data, &tmp_axis, AXIS_RIGHT | AXIS_NOTICKS | AXIS_AUTOTICK)){
			Axes[5]->SetSize(SIZE_LB_XDIST, lb_xdist); 
			Axes[5]->SetSize(SIZE_TLB_XDIST, tlb_dist); 
			tlbdef.Align = TXA_VCENTER | TXA_HLEFT;
			Axes[5]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		NumAxes = 6;
		break;
	case 2:
		Axes = (Axis**)calloc(3, sizeof(Axis *));
		x_axis.loc[0].fx = GRect.Xmin + DRect.Xmin;
		x_axis.loc[1].fx = GRect.Xmin + DRect.Xmax;
		x_axis.loc[0].fy = x_axis.loc[1].fy = GRect.Ymin + DRect.Ymax;
		y_axis.loc[0].fy = GRect.Ymin + DRect.Ymin;
		y_axis.loc[1].fy = GRect.Ymin + DRect.Ymax;
		y_axis.loc[0].fx = y_axis.loc[1].fx = GRect.Xmin + DRect.Xmin;
		if((Axes[0] = new Axis(this, data, &x_axis, AXIS_BOTTOM | ptick |	
			AXIS_AUTOTICK | AXIS_AUTOSCALE | ((tickstyle & 0x100) ? AXIS_GRIDLINE : 0)))){
			Axes[0]->SetSize(SIZE_LB_YDIST, lb_ydist);
			Axes[0]->SetSize(SIZE_TLB_YDIST, tlb_dist);
			rlp_strcpy(label_text, 500, xa_desc);
			label = new Label(Axes[0], data, GRect.Xmin + (DRect.Xmin+DRect.Xmax)/2.0f, 
				GRect.Ymin+DRect.Ymax+DefSize(SIZE_AXIS_TICKS)*4.0f, &label_def, LB_Y_PARENT);
			if(label && Axes[0]->Command(CMD_DROP_LABEL, (void*)label, 0L)) label = 0L;
			else if(label) DeleteGO(label);
			tlbdef.Align = TXA_VTOP | TXA_HCENTER;
			Axes[0]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		if((Axes[1] = new Axis(this, data, &y_axis, AXIS_LEFT | ntick | AXIS_AUTOTICK |
			AXIS_AUTOSCALE | ((tickstyle & 0x200) ? AXIS_GRIDLINE : 0)))){
			Axes[1]->SetSize(SIZE_LB_XDIST, -lb_xdist); 
			Axes[1]->SetSize(SIZE_TLB_XDIST, -tlb_dist); 
			rlp_strcpy(label_text, 500, ya_desc);
			label_def.RotBL = 90.0;			label_def.Align = TXA_VBOTTOM | TXA_HCENTER;
			label = new Label(Axes[1], data, GRect.Xmin + DRect.Xmin - DefSize(SIZE_AXIS_TICKS)*6.0, 
				GRect.Ymin+(DRect.Ymax+DRect.Ymin)/2.0, &label_def, LB_X_PARENT);
			if(label && Axes[1]->Command(CMD_DROP_LABEL, (void*)label, 0L)) label = 0L;
			else if(label) DeleteGO(label);
			tlbdef.Align = TXA_VCENTER | TXA_HRIGHT;
			Axes[1]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		label = 0L;
		NumAxes = 2;
		break;
	case 3:
		label_def.Align = TXA_VBOTTOM | TXA_HCENTER;
		Axes = (Axis**)calloc(3, sizeof(Axis *));
		x_axis.loc[0].fx = GRect.Xmin + DRect.Xmin;
		x_axis.loc[1].fx = GRect.Xmin + DRect.Xmax;
		x_axis.loc[0].fy = x_axis.loc[1].fy = GRect.Ymin + DRect.Ymin;
		y_axis.loc[0].fy = GRect.Ymin + DRect.Ymin;
		y_axis.loc[1].fy = GRect.Ymin + DRect.Ymax;
		y_axis.loc[0].fx = y_axis.loc[1].fx = GRect.Xmin + DRect.Xmin;
		if((Axes[0] = new Axis(this, data, &x_axis, AXIS_TOP | utick |	
			AXIS_AUTOTICK | AXIS_AUTOSCALE | ((tickstyle & 0x100) ? AXIS_GRIDLINE : 0)))){
			Axes[0]->SetSize(SIZE_LB_YDIST, -lb_ydist);
			Axes[0]->SetSize(SIZE_TLB_YDIST, -tlb_dist);
			rlp_strcpy(label_text, 500, xa_desc);
			label = new Label(Axes[0], data, GRect.Xmin + (DRect.Xmin+DRect.Xmax)/2.0f, 
				GRect.Ymin+DRect.Ymax+DefSize(SIZE_AXIS_TICKS)*4.0f, &label_def, LB_Y_PARENT);
			if(label && Axes[0]->Command(CMD_DROP_LABEL, (void*)label, 0L)) label = 0L;
			else if(label) DeleteGO(label);
			tlbdef.Align = TXA_VBOTTOM | TXA_HCENTER;
			Axes[0]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		if((Axes[1] = new Axis(this, data, &y_axis, AXIS_LEFT | ntick | AXIS_AUTOTICK |
			AXIS_AUTOSCALE | AXIS_INVERT | ((tickstyle & 0x200) ? AXIS_GRIDLINE : 0)))){
			Axes[1]->SetSize(SIZE_LB_XDIST, -lb_xdist); 
			Axes[1]->SetSize(SIZE_TLB_XDIST, -tlb_dist); 
			rlp_strcpy(label_text, 500, ya_desc);
			label_def.RotBL = 90.0;			label_def.Align = TXA_VBOTTOM | TXA_HCENTER;
			label = new Label(Axes[1], data, GRect.Xmin + DRect.Xmin - DefSize(SIZE_AXIS_TICKS)*6.0, 
				GRect.Ymin+(DRect.Ymax+DRect.Ymin)/2.0, &label_def, LB_X_PARENT);
			if(label && Axes[1]->Command(CMD_DROP_LABEL, (void*)label, 0L)) label = 0L;
			else if(label) DeleteGO(label);
			tlbdef.Align = TXA_VCENTER | TXA_HRIGHT;
			Axes[1]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		label = 0L;
		NumAxes = 2;
		break;
	case 4:
		Axes = (Axis**)calloc(3, sizeof(Axis *));
		if(x_axis.Start >= 0.0f) x_axis.min = -x_axis.Step;
		x_axis.loc[0].fx = GRect.Xmin + DRect.Xmin;
		x_axis.loc[1].fx = GRect.Xmin + DRect.Xmax;
		x_axis.loc[0].fy = x_axis.loc[1].fy = GRect.Ymin + DRect.Ymax;
		y_axis.loc[0].fy = GRect.Ymin + DRect.Ymin;
		y_axis.loc[1].fy = GRect.Ymin + DRect.Ymax;
		y_axis.loc[0].fx = y_axis.loc[1].fx = 0.0f;
		if((Axes[0] = new Axis(this, data, &x_axis, AXIS_BOTTOM | ptick |	
			AXIS_AUTOTICK | AXIS_AUTOSCALE | ((tickstyle & 0x100) ? AXIS_GRIDLINE : 0)))){
			Axes[0]->SetSize(SIZE_LB_YDIST, lb_ydist);
			Axes[0]->SetSize(SIZE_TLB_YDIST, tlb_dist);
			rlp_strcpy(label_text, 500, xa_desc);
			label = new Label(Axes[0], data, GRect.Xmin + (DRect.Xmin+DRect.Xmax)/2.0f, 
				GRect.Ymin+DRect.Ymax+DefSize(SIZE_AXIS_TICKS)*4.0f, &label_def, LB_Y_PARENT);
			if(label && Axes[0]->Command(CMD_DROP_LABEL, (void*)label, 0L)) label = 0L;
			else if(label) DeleteGO(label);
			tlbdef.Align = TXA_VTOP | TXA_HCENTER;
			Axes[0]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		if((Axes[1] = new Axis(this, data, &y_axis, ntick | AXIS_AUTOTICK | AXIS_X_DATA |
			AXIS_AUTOSCALE | ((tickstyle & 0x200) ? AXIS_GRIDLINE : 0)))){
			Axes[1]->SetSize(SIZE_LB_XDIST, -lb_xdist); 
			Axes[1]->SetSize(SIZE_TLB_XDIST, -tlb_dist); 
			rlp_strcpy(label_text, 500, ya_desc);
			label_def.RotBL = 90.0;			label_def.Align = TXA_VBOTTOM | TXA_HCENTER;
			label = new Label(Axes[1], data, GRect.Xmin + DRect.Xmin - DefSize(SIZE_AXIS_TICKS)*6.0, 
				GRect.Ymin+(DRect.Ymax+DRect.Ymin)/2.0, &label_def, LB_X_PARENT);
			if(label && Axes[1]->Command(CMD_DROP_LABEL, (void*)label, 0L)) label = 0L;
			else if(label) DeleteGO(label);
			tlbdef.Align = TXA_VCENTER | TXA_HRIGHT;
			Axes[1]->Command(CMD_TLB_TXTDEF, (void*)&tlbdef, 0L);
			}
		label = 0L;
		NumAxes = 2;
		break;
		}
	if(Plots[0] && Plots[0]->Id >= GO_PLOT && Plots[0]->Id < GO_GRAPH && NumAxes > 1) {
		if(((Plot*)Plots[0])->x_tv && Axes[0])Axes[0]->atv = ((Plot*)Plots[0])->x_tv->Copy();
		else if(((Plot*)Plots[0])->x_dtype == ET_DATETIME)x_axis.flags |= AXIS_DATETIME;
		if(((Plot*)Plots[0])->y_tv && Axes[1])Axes[1]->atv = ((Plot*)Plots[0])->y_tv->Copy();
		else if(((Plot*)Plots[0])->y_dtype == ET_DATETIME)y_axis.flags |= AXIS_DATETIME;
		}
}

bool
Graph::DoScale(scaleINFO* sc, anyOutput *o)
{
	int i;
	scaleINFO sc0;

	if(sc->sy.fy <= 0.0) return false;
	GRect.Xmax = sc->sx.fx + GRect.Xmax* sc->sx.fy;
	GRect.Xmin = sc->sx.fx + GRect.Xmin * sc->sx.fy;
	GRect.Ymax = sc->sy.fx + GRect.Ymax * sc->sy.fy;
	GRect.Ymin = sc->sy.fx + GRect.Ymin * sc->sy.fy;
	DRect.Xmax *= sc->sx.fy;	DRect.Xmin *= sc->sx.fy;
	DRect.Ymax *= sc->sy.fy;	DRect.Ymin *= sc->sy.fy;
	if(sc->sx.fy == 1.0 && sc->sx.fy  == sc->sy.fy && sc->sx.fy == sc->sz.fy) return true;
	memcpy(&sc0, sc, sizeof(scaleINFO));
	sc0.sx.fx = sc0.sy.fx = sc0.sz.fx = 0.0;	sc0.sx.fy = sc0.sz.fy = sc->sy.fy;
	if(Axes) for(i = 0; i< NumAxes; i++) if(Axes[i]) Axes[i]->Command(CMD_SCALE, &sc0, o);
	if(Plots) for(i = 0; i < NumPlots; i++) if(Plots[i]){
		if(Plots[i]->Id == GO_GRAPH || Plots[i]->Id == GO_PAGE) Plots[i]->Command(CMD_SCALE, sc, o);
		else Plots[i]->Command(CMD_SCALE, &sc0, o);
		}
	scale = scale > 0.0 ? scale * sc->sy.fy : sc->sy.fy;
	return true;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Pages are graphic objects containing graphs and drawn objects
Page::Page(GraphObj *par, DataObj *d):Graph(par, d, 0L, 0)
{
	FileIO(INIT_VARS);
	cGraphs--;		cPages++;		Id = GO_PAGE;	bModified = true;
}

Page::Page(int src):Graph(src)
{
	int i;

	//most of the object is read by Graph::FileIO()
	ColBG = 0x00e8e8e8L;
	LineDef.width = 0.0;	LineDef.patlength = 1.0;
	LineDef.color = LineDef.pattern = 0x0L;
	FillDef.type = FILL_NONE;
	FillDef.color = 0x00ffffffL;	//use white paper
	FillDef.scale = 1.0;
	FillDef.hatch = 0L;
	cGraphs--;		cPages++;	bModified = false;
	if(Plots) for(i = 0; i < NumPlots; i++) if(Plots[i]) Plots[i]->moveable = 1;
}

void
Page::DoPlot(anyOutput *o)
{
	int i;
	POINT pts[3];

	if(!o && !Disp) {
		Disp = NewDispClass(this);
		Disp->SetMenu(MENU_PAGE);
#ifdef USE_WIN_SECURE
		i = sprintf_s(TmpTxt, TMP_TXT_SIZE, "Page %d", cPages);
#else
		i = sprintf(TmpTxt, "Page %d", cPages);
#endif
		if(!name && (name = (char*)malloc(i += 2)))rlp_strcpy(name, i, TmpTxt);
		Disp->Caption(TmpTxt, false);
		Disp->VPorg.fy = iround(Disp->MenuHeight);
		Disp->VPscale = 0.5;
		Disp->CheckMenu(ToolMode, true);
		OwnDisp = true;
		}
	//the first output class is the display class
	if(!Disp && (!(Disp = o))) return;
	CurrDisp = o ? o : Disp;
	CurrDisp->Erase(CurrDisp->dFillCol = ColBG);
	if(OwnDisp && CurrDisp == Disp)LineDef.color = 0x0L;
	else LineDef.color = FillDef.color;
	CurrDisp->SetLine(&LineDef);
	CurrDisp->SetFill(&FillDef);
	CurrDisp->oRectangle(rDims.left = CurrDisp->co2ix(GRect.Xmin), 
		rDims.top = CurrDisp->co2iy(GRect.Ymin), rDims.right = CurrDisp->co2ix(GRect.Xmax),
		rDims.bottom = CurrDisp->co2iy(GRect.Ymax));
	pts[0].x = rDims.left+7;				pts[0].y = pts[1].y = rDims.bottom;
	pts[1].x = pts[2].x = rDims.right;		pts[2].y = rDims.top +3;
	CurrDisp->oPolyline(pts, 3);
	//do all plots
	if(Plots) for(i = 0; i < NumPlots; i++) if(Plots[i]) Plots[i]->DoPlot(CurrDisp);
	if(PasteObj) {
		ToolMode = TM_PASTE;	CurrDisp->MouseCursor(MC_PASTE, false);
		}
}

bool
Page::Command(int cmd, void *tmpl, anyOutput *o)
{
	Graph *g;

	switch(cmd) {
	case CMD_MOUSE_EVENT:
		return Graph::Command(cmd, tmpl, o);
	case CMD_REDRAW:
		if(Disp) {
			Disp->StartPage();		DoPlot(Disp);		Disp->EndPage();
			}
		return true;
	case CMD_CONFIG:
		return Configure();
	case CMD_NEWGRAPH:
		if((g = new Graph(this, data, Disp, 0)) && g->PropertyDlg() && 
			Command(CMD_DROP_GRAPH, g, o))return true;
		else if(g) DeleteGO(g);
		return false;
	case CMD_SET_DATAOBJ:
		Graph::Command(cmd, tmpl, o);
		Id = GO_PAGE;
		return true;
	case CMD_SCALE:
		return true;
	default:
		return Graph::Command(cmd, tmpl, o);
	}
}

double 
Page::DefSize(int select)
{
	return defs.GetSize(select);
}

