/*******************************************************************************
 J2DPlotWidget.cc

	J2DPlotWidget class.

	Copyright @ 1997 by Glenn W. Bach. All rights reserved.

 ******************************************************************************/

#include <J2DPlotWidget.h>
#include <JPlotDataBase.h>
#include <J2DPlotData.h>
#include <fitplot.h>

#include <JString.h>
#include <JPagePrinter.h>
#include <JFontStyle.h>
#include <JColormap.h>

#include <jGlobals.h>
#include <jMath.h>
#include <jASCIIConstants.h>

#include <ctype.h>
#include <iostream.h>
#include <jAssert.h>

const JSize kLabelBuffer 			= 10;
const JSize kRightBuffer 			= 30;
const JSize kLegendBuffer 			= 15;
const JSize kLegendItemBuffer 		= 15;
const JSize kMaxLegendStringWidth	= 200;
const JSize kTitleBuffer 			= 15;
const JSize kMajorTicLength 		= 4;
const JSize kCursorValueBuffer		= 5;

const JFloat kDefaultXMax 			= 10;
const JFloat kDefaultXMin 			= 0;
const JFloat kDefaultYMax 			= 10;
const JFloat kDefaultYMin 			= 0;

// setup information

const JFileVersion kCurrentSetupVersion = 1;

	// version 1 stores all 8 cursor values, regardless of visibility

const JFileVersion kCurrentCurveSetupVersion = 0;

// JBroadcaster messages

const JCharacter* J2DPlotWidget::kTitleChanged    = "TitleChanged::J2DPlotWidget";
const JCharacter* J2DPlotWidget::kScaleChanged    = "ScaleChanged::J2DPlotWidget";
const JCharacter* J2DPlotWidget::kGeometryChanged = "GeometryChanged::J2DPlotWidget";
const JCharacter* J2DPlotWidget::kPlotChanged     = "PlotChanged::J2DPlotWidget";
const JCharacter* J2DPlotWidget::kIsEmpty         = "IsEmpty::J2DPlotWidget";
const JCharacter* J2DPlotWidget::kCurveAdded      = "CurveAdded::J2DPlotWidget";
const JCharacter* J2DPlotWidget::kCurveRemoved    = "CurveRemoved::J2DPlotWidget";

// Cursors
const JCharacter* J2DPlotWidget::kCursorsChanged = "CursorsChanged::J2DPlotWidget";
const JCharacter* J2DPlotWidget::kCursorMarked   = "CursorMarked::J2DPlotWidget";
const JCharacter* J2DPlotWidget::kMarkRemoved    = "MarkRemoved::J2DPlotWidget";

/*******************************************************************************
 Constructor

 ******************************************************************************/

J2DPlotWidget::J2DPlotWidget
	(
	JColorIndex black,
	JColorIndex white,
	JColorIndex gray,
	JColorIndex selection
	)
	:
	JBroadcaster(),
	itsBlackColor(black),
	itsWhiteColor(white),
	itsGrayColor(gray),
	itsSelectionColor(selection),
	itsLegendRect(0,0,0,0)
{
	itsTitle 		= "Title";
	itsXLabel 		= "X Label";
	itsYLabel		= "Y Label";

	itsShowLegend 	= kFalse;
	itsShowGrid   	= kFalse;
	itsShowFrame  	= kTrue;

	itsCurvesInfo 	= new JArray<JCurveInfo>;
	assert(itsCurvesInfo != NULL);

	itsColors 		= new JArray<JColorIndex>();
	assert(itsColors != NULL);

	itsColorUsage 	= new JArray<JSize>();
	assert(itsColorUsage != NULL);

	itsCurves 		= new JPtrArray<JPlotDataBase>();
	assert(itsCurves != NULL);

	itsColors->AppendElement(itsBlackColor);
	itsColorUsage->AppendElement(0);

	for (JIndex i = 0; i < kSymbolCount; i++)
		{
		itsSymbolUsage[i] = 0;
		}

	itsFontName 	= JGetDefaultFontName();
	itsFontSize 	= kJDefaultFontSize;

	itsLegendWidth 	= 0;

	itsGeometryNeedsAdjustment	= kTrue;
	itsAutomaticRefresh 		= kTrue;
	itsCurrentXDecimalPoints 	= 0;
	itsCurrentYDecimalPoints 	= 0;

	itsXAxisIsLinear 			= kTrue;
	itsYAxisIsLinear 			= kTrue;

	itsIsZoomed 				= kFalse;
	itsShowXMinorTics 			= kTrue;
	itsShowYMinorTics 			= kTrue;
	itsUseRealXStart			= kFalse;
	itsUseRealYStart			= kFalse;

	itsSmoothSteps 				= 100;
	itsSmoothDivisor 			= 1;
	itsDefaultXMax 				= kDefaultXMax;
	itsDefaultXMin 				= kDefaultXMin;
	itsDefaultYMax 				= kDefaultYMax;
	itsDefaultYMin 				= kDefaultYMin;

	itsUsingRange 				= kFalse;
	itsForceXExp 				= kFalse;
	itsForceYExp 				= kFalse;

	itsIsPrintingFlag			= kFalse;

	// Cursors

	itsXMarks = new JArray<JFloat>;
	assert(itsXMarks != NULL);

	itsYMarks = new JArray<JFloat>;
	assert(itsYMarks != NULL);

	itsXCursorVisible 	= kFalse;
	itsYCursorVisible 	= kFalse;
	itsDualCursors 		= kFalse;
	itsXCursorPos1 		= 0;
	itsXCursorPos2 		= 0;
	itsYCursorPos1 		= 0;
	itsYCursorPos2 		= 0;
	itsSelectedCursor 	= kNoCursor;
	itsIsInited 		= kFalse;
	itsChangingScale 	= kFalse;
}

/*******************************************************************************
 Destructor

 ******************************************************************************/

J2DPlotWidget::~J2DPlotWidget()
{
	delete itsColors;
	delete itsColorUsage;

	const JSize count = itsCurves->GetElementCount();
	for (JIndex i=count; i>=1; i--)
		{
		JCurveInfo info = itsCurvesInfo->GetElement(i);
		if (info.own)
			{
			StopListening(itsCurves->NthElement(i));
			itsCurves->DeleteElement(i);
			}
		delete info.name;
		}
	delete itsCurves;
	delete itsCurvesInfo;

	// Cursors

	delete itsXMarks;
	delete itsYMarks;
}

/*******************************************************************************
 Receive

 ******************************************************************************/

void
J2DPlotWidget::Receive
	(
	JBroadcaster*	sender,
	const Message&	message
	)
{
	if (message.Is(JPlotDataBase::kCurveChanged))
		{
		if (!itsIsZoomed)
			{
			AdjustScaling();
			}

		PWRefresh();
		}
	else
		{
		JBroadcaster::Receive(sender, message);
		}
}

/*******************************************************************************
 SetLabels

 ******************************************************************************/

void
J2DPlotWidget::SetLabels
	(
	const JCharacter* title,
	const JCharacter* xLabel,
	const JCharacter* yLabel
	)
{
	itsTitle = title;
	itsXLabel = xLabel;
	itsYLabel = yLabel;
	Broadcast(TitleChanged());
	UpdatePlot();
}

/*******************************************************************************
 SetXLabel

 ******************************************************************************/

void
J2DPlotWidget::SetXLabel
	(
	const JCharacter* xLabel
	)
{
	itsXLabel = xLabel;
	UpdatePlot();
}

/*******************************************************************************
 SetYLabel

 ******************************************************************************/

void
J2DPlotWidget::SetYLabel
	(
	const JCharacter* yLabel
	)
{
	itsYLabel = yLabel;
	UpdatePlot();
}

/*******************************************************************************
 SetTitle

 ******************************************************************************/

void
J2DPlotWidget::SetTitle
	(
	const JCharacter* xLabel
	)
{
	itsTitle = xLabel;
	Broadcast(TitleChanged());
	UpdatePlot();
}

/*******************************************************************************
 ShowLegend

 ******************************************************************************/

void
J2DPlotWidget::ShowLegend
	(
	const JBoolean show
	)
{
	if (itsShowLegend != show)
		{
		itsShowLegend = show;
		UpdatePlot(kTrue);
		if (!show)
			{
			itsLegendRect.Set(0,0,0,0);
			}
		}
}

/*******************************************************************************
 ShowFrame

 ******************************************************************************/

void
J2DPlotWidget::ShowFrame
	(
	const JBoolean show
	)
{
	if (itsShowFrame != show)
		{
		itsShowFrame = show;
		if (show == kFalse)
			{
			itsShowGrid = kFalse;
			}
		UpdatePlot(kTrue);
		}
}

/*******************************************************************************
 ShowGrid

 ******************************************************************************/

void
J2DPlotWidget::ShowGrid
	(
	const JBoolean show
	)
{
	if (itsShowGrid != show)
		{
		itsShowGrid = show;
		if (show)
			{
			itsShowFrame = kTrue;
			}
		UpdatePlot(kTrue);
		}
}

/*******************************************************************************
 ShowCurve

 ******************************************************************************/

void
J2DPlotWidget::ShowCurve
	(
	const JIndex curveIndex,
	const JBoolean show
	)
{
	assert (itsCurvesInfo->IndexValid(curveIndex));

	JCurveInfo info = itsCurvesInfo->GetElement(curveIndex);
	info.curve = show;
	itsCurvesInfo->SetElement(curveIndex, info);
	UpdatePlot();
}

/*******************************************************************************
 ShowLines

 ******************************************************************************/

void
J2DPlotWidget::ShowLines
	(
	const JIndex curveIndex,
	const JBoolean show
	)
{
	assert (itsCurvesInfo->IndexValid(curveIndex));

	JCurveInfo info = itsCurvesInfo->GetElement(curveIndex);
	info.lines = show;
	itsCurvesInfo->SetElement(curveIndex, info);
	UpdatePlot();
}

/*******************************************************************************
 ShowSymbol

 ******************************************************************************/

void
J2DPlotWidget::ShowSymbol
	(
	const JIndex curveIndex,
	const JBoolean show
	)
{
	assert (itsCurvesInfo->IndexValid(curveIndex));

	JCurveInfo info = itsCurvesInfo->GetElement(curveIndex);
	info.symbols = show;
	itsCurvesInfo->SetElement(curveIndex, info);
	UpdatePlot();
}

/*******************************************************************************
 ProtectCurve

 ******************************************************************************/

void
J2DPlotWidget::ProtectCurve
	(
	const JIndex curveIndex,
	const JBoolean protect
	)
{
	assert (itsCurvesInfo->IndexValid(curveIndex));

	JCurveInfo info = itsCurvesInfo->GetElement(curveIndex);
	info.protect = protect;
	itsCurvesInfo->SetElement(curveIndex, info);
	ProtectionChanged();
}

/*******************************************************************************
 GetCurveCount

 ******************************************************************************/

JSize
J2DPlotWidget::GetCurveCount()
	const
{
	return itsCurves->GetElementCount();
}

/*******************************************************************************
 SetCurveName

 ******************************************************************************/

void
J2DPlotWidget::SetCurveName
	(
	const JIndex		index,
	const JCharacter*	xLabel
	)
{
	assert (itsCurvesInfo->IndexValid(index));

	JCurveInfo info = itsCurvesInfo->GetElement(index);
	delete info.name;
	JString* name = new JString(xLabel);
	assert(name != NULL);
	info.name = name;
	itsCurvesInfo->SetElement(index, info);
	UpdatePlot(kTrue);
}

/*******************************************************************************
 GetCurveName

 ******************************************************************************/

const JString&
J2DPlotWidget::GetCurveName
	(
	const JIndex index
	)
	const
{
	assert (itsCurvesInfo->IndexValid(index));
	JCurveInfo info = itsCurvesInfo->GetElement(index);
	return *(info.name);
}

/*******************************************************************************
 GetCurve

 ******************************************************************************/

JPlotDataBase&
J2DPlotWidget::GetCurve
	(
	const JIndex index
	)
{
	assert( itsCurves->IndexValid(index) );
	return *(itsCurves->NthElement(index));
}

const JPlotDataBase&
J2DPlotWidget::GetCurve
	(
	const JIndex index
	)
	const
{
	assert( itsCurves->IndexValid(index) );
	return *(itsCurves->NthElement(index));
}

/*******************************************************************************
 AddCurve

 ******************************************************************************/

JIndex
J2DPlotWidget::AddCurve
	(
	JPlotDataBase* 		data,
	const JBoolean 		ownsData,
	const JCharacter* 	name,
	const JBoolean 		line,
	const JBoolean 		symbol,
	const JBoolean 		point
	)
{
	itsCurves->Append(data);

	JString* str = new JString(name);
	assert(str != NULL);

	JCurveInfo info(kTrue, line, symbol, point,
					ownsData, kTrue, kTrue, kFalse,
					GetFirstAvailableColor(),
					GetFirstAvailableSymbol(), str);

	itsCurvesInfo->AppendElement(info);

	ListenTo(data);
	Broadcast(CurveAdded(itsCurves->GetElementCount()));
	Broadcast(ScaleChanged());	// Do I still need this?
	itsChangingScale = kTrue;
	UpdatePlot(kTrue, JNegate(itsIsZoomed));
	Broadcast(ScaleChanged());
	itsChangingScale = kTrue;
	return itsCurves->GetElementCount();
}

/*******************************************************************************
 AddCurve

 ******************************************************************************/

JIndex
J2DPlotWidget::AddCurve
	(
	JArray<JFloat>& 	x,
	JArray<JFloat>& 	y,
	const JBoolean 		listen,
	const JCharacter*	label,
	const JBoolean 		line,
	const JBoolean 		symbol,
	const JBoolean 		point
	)
{
	J2DPlotData* newData;
	if (J2DPlotData::Create(&newData, x, y, listen))
		{
		return AddCurve(newData, kTrue, label, line, symbol, point);
		}
	return 0;
}

/*******************************************************************************
 RemoveCurve

 ******************************************************************************/

void
J2DPlotWidget::RemoveCurve
	(
	const JIndex index
	)
{
	assert (itsCurvesInfo->IndexValid(index));
	JCurveInfo info = itsCurvesInfo->GetElement(index);

	if (info.protect)
		{
		JGetUserNotification()->ReportError("This curve can not be removed.");
		return;
		}

	if (info.own)
		{
		itsCurves->DeleteElement(index);
		}
	else
		{
		itsCurves->RemoveElement(index);
		}

	const JSize colorUsage = itsColorUsage->GetElement(info.color);
	assert( colorUsage > 0 );
	itsColorUsage->SetElement(index, colorUsage - 1);

	const JIndex symbolIndex = info.symbol;
	assert( symbolIndex < kSymbolCount &&
			itsSymbolUsage [ symbolIndex ] > 0 );
	itsSymbolUsage [ symbolIndex ]--;

	delete info.name;
	itsCurvesInfo->RemoveElement(index);

	if (itsCurves->GetElementCount() != 0)
		{
		UpdatePlot(kTrue, kTrue);
		Broadcast(CurveRemoved(index));
		}
	else
		{
		Broadcast(IsEmpty());		// deletes us
		}
}

/*******************************************************************************
 ResetScaling

 ******************************************************************************/

void
J2DPlotWidget::ResetScaling()
{
	itsIsZoomed = kFalse;

	JFloat maxXData;
	JFloat minXData;
	JFloat maxYData;
	JFloat minYData;

	GetRanges(&maxXData, &minXData, &maxYData, &minYData);

	if (!itsXAxisIsLinear)
		{
		if (maxXData <= 0)
			{
			itsXAxisIsLinear = kFalse;
			}
		else
			{
			JFloat pMin = minXData;
			GetMinimumPositiveValue(kTrue, &pMin);

			JFloat start;
			JFloat end;
			JFloat inc;

			FindLogRange(maxXData, pMin, &inc, &start, &end);
			SetXScaling(start, end, inc);
			itsIsZoomed = kFalse;
			}
		}

	if (!itsYAxisIsLinear)
		{
		if (maxYData <= 0)
			{
			itsYAxisIsLinear = kFalse;
			}
		else
			{
			JFloat pMin = minYData;
			GetMinimumPositiveValue(kFalse, &pMin);

			JFloat start;
			JFloat end;
			JFloat inc;

			FindLogRange(maxYData, pMin, &inc, &start, &end);
			SetYScaling(start, end, inc);
			itsIsZoomed = kFalse;
			}
		}

	for (int i = 0; i < 3; i++)
		{
		if (itsXAxisIsLinear)
			{
			itsCurrentXScale[i] = itsDefXScale[i];
			}

		if (itsYAxisIsLinear)
			{
			itsCurrentYScale[i] = itsDefYScale[i];
			}
		}

	itsUseRealXStart = kFalse;
	itsUseRealYStart = kFalse;
	SetPlotDecPlaces();
	Broadcast(ScaleChanged());
	itsChangingScale = kTrue;
	UpdatePlot(kTrue, kTrue);
}

/*******************************************************************************
 Zoom

 ******************************************************************************/

void
J2DPlotWidget::Zoom
	(
	const JRect zRect,
	const JBoolean clean
	)
{
	JRect rect;
	if (!JIntersection(zRect, JRect(itsYAxisEnd,   itsXAxisStart,
									itsYAxisStart, itsXAxisEnd), &rect))
		{
		return;
		}

	JFloat tempXMax = itsCurrentXScale[kMax];
	JFloat tempXMin = itsCurrentXScale[kMin];
	JFloat tempXInc = itsCurrentXScale[kInc];
	JFloat tempYMax = itsCurrentYScale[kMax];
	JFloat tempYMin = itsCurrentYScale[kMin];
	JFloat tempYInc = itsCurrentYScale[kInc];

	JBoolean tempUseRXStart	= itsUseRealXStart;
	JFloat tempRealXStart	= itsCurrentXScale[kStart];
	JBoolean tempUseRYStart	= itsUseRealYStart;
	JFloat tempRealYStart	= itsCurrentYScale[kStart];

	JFloat maxXData = (rect.right - itsXAxisStart)/itsXTrans + itsCurrentXScale[kMin];
	JFloat minXData = (rect.left - itsXAxisStart)/itsXTrans + itsCurrentXScale[kMin];
	JFloat xstart;
	JFloat xend;
	JFloat xinc;
	JFloat xrealstart;
	if (itsXAxisIsLinear)
		{
		FindRange(maxXData, minXData, &xinc, &xstart, &xend);
		if (!clean)
			{
			xrealstart	= xstart;
			xstart 		= minXData;
			xend 		= maxXData;
			}
		}
	else
		{
		FindLogRange(pow(10,maxXData), pow(10,minXData), &xinc, &xstart, &xend);
		if (!clean && (xinc == 1))
			{
			xstart	= minXData;
			xend 	= maxXData;
			}
		}

	JFloat maxYData = (itsYAxisStart - rect.top)/itsYTrans + itsCurrentYScale[kMin];
	JFloat minYData = (itsYAxisStart - rect.bottom)/itsYTrans + itsCurrentYScale[kMin];
	JFloat ystart;
	JFloat yend;
	JFloat yinc;
	JFloat yrealstart;
	if (itsYAxisIsLinear)
		{
		FindRange(maxYData, minYData, &yinc, &ystart, &yend);
		if (!clean)
			{
			yrealstart	= ystart;
			ystart		= minYData;
			yend 		= maxYData;
			}
		}
	else
		{
		FindLogRange(pow(10,maxYData), pow(10,minYData), &yinc, &ystart, &yend);
		if (!clean && (yinc == 1))
			{
			ystart	= minYData;
			yend 	= maxYData;
			}
		}

	JFloat denom = itsDefXScale[kMax] - itsDefXScale[kMin];
	if (!itsXAxisIsLinear)
		{
		denom = log10(fabs(denom));
		}
	JFloat xzoomratio = (itsCurrentXScale[kMax] - itsCurrentXScale[kMin])/denom;

	denom = itsDefYScale[kMax] - itsDefYScale[kMin];
	if (!itsYAxisIsLinear)
		{
		denom = log10(fabs(denom));
		}
	JFloat yzoomratio = (itsCurrentYScale[kMax] - itsCurrentYScale[kMin])/denom;

	if ((xzoomratio < 0.003) || (yzoomratio < 0.003))
		{
		itsCurrentXScale[kMax]  = tempXMax;
		itsCurrentXScale[kMin]  = tempXMin;
		itsCurrentXScale[kInc]  = tempXInc;
		itsCurrentYScale[kMax]  = tempYMax;
		itsCurrentYScale[kMin]  = tempYMin;
		itsCurrentYScale[kInc]  = tempYInc;
		itsUseRealXStart		= tempUseRXStart;
		itsCurrentXScale[kStart]= tempRealXStart;
		itsUseRealYStart		= tempUseRYStart;
		itsCurrentYScale[kStart]= tempRealYStart;

		JGetUserNotification()->ReportError("You are zoomed in as far as you can go.");
//		SetPlotDecPlaces();
//		Broadcast(ScaleChanged());
//		itsChangingScale = kTrue;
//		UpdatePlot(kTrue);
		return;
		}

	SetXScaling(xstart, xend, xinc);
	if (!clean)
		{
		itsCurrentXScale[kStart] = xrealstart;
		itsUseRealXStart		 = kTrue;
		}

	SetYScaling(ystart, yend, yinc);
	if (!clean)
		{
		itsCurrentYScale[kStart] = yrealstart;
		itsUseRealYStart		 = kTrue;
		}

	SetPlotDecPlaces();
	Broadcast(ScaleChanged());
	itsChangingScale = kTrue;
	UpdatePlot(kTrue);
	itsIsZoomed = kTrue;
}

/*******************************************************************************
 SetXScaling

 ******************************************************************************/

void
J2DPlotWidget::SetXScaling
	(
	const JFloat min,
	const JFloat max,
	const JFloat inc
	)
{
	JFloat start;
	JFloat end;
	JFloat newinc = inc;
	if (inc == 0)
		{
		FindLogRange(pow(10,max), pow(10,min), &newinc, &start, &end);
		}
	itsCurrentXScale[0] = min;
	itsCurrentXScale[1] = max;
	itsCurrentXScale[2] = newinc;
	SetPlotDecPlaces();
	Broadcast(ScaleChanged());
	itsChangingScale 	= kTrue;
	UpdatePlot(kTrue);
	itsIsZoomed 		= kTrue;
	itsUseRealXStart	= kFalse;
}

/*******************************************************************************
 SetYScaling

 ******************************************************************************/

void
J2DPlotWidget::SetYScaling
	(
	const JFloat min,
	const JFloat max,
	const JFloat inc
	)
{
	JFloat start;
	JFloat end;
	JFloat newinc = inc;
	if (inc == 0)
		{
		FindLogRange(pow(10,max), pow(10,min), &newinc, &start, &end);
		}
	itsCurrentYScale[0] = min;
	itsCurrentYScale[1] = max;
	itsCurrentYScale[2] = newinc;
	SetPlotDecPlaces();
	Broadcast(ScaleChanged());
	itsChangingScale 	= kTrue;
	UpdatePlot(kTrue);
	itsIsZoomed 		= kTrue;
	itsUseRealYStart	= kFalse;
}

/*******************************************************************************
 GetXScaling

 ******************************************************************************/

void
J2DPlotWidget::GetXScaling
	(
	JFloat* min,
	JFloat* max,
	JFloat* inc
	)
{
	if (itsXAxisIsLinear)
		{
		*min = itsCurrentXScale[kMin];
		*max = itsCurrentXScale[kMax];
		*inc = itsCurrentXScale[kInc];
		}
	else
		{
		*min = pow(10,itsCurrentXScale[kMin]);
		*max = pow(10,itsCurrentXScale[kMax]);
		*inc = pow(10, itsCurrentXScale[kInc]);
		}
}

/*******************************************************************************
 GetYScaling

 ******************************************************************************/

void
J2DPlotWidget::GetYScaling
	(
	JFloat* min,
	JFloat* max,
	JFloat* inc
	)
{
	if (itsYAxisIsLinear)
		{
		*min = itsCurrentYScale[kMin];
		*max = itsCurrentYScale[kMax];
		*inc = itsCurrentYScale[kInc];
		}
	else
		{
		*min = pow(10,itsCurrentYScale[kMin]);
		*max = pow(10,itsCurrentYScale[kMax]);
		*inc = pow(10,itsCurrentYScale[kInc]);
		}
}

/*******************************************************************************
 Print

 ******************************************************************************/

void
J2DPlotWidget::Print
	(
	JPagePrinter& p
	)
{
	if (p.OpenDocument())
		{
		JBoolean cancelled  = kFalse;
		JBoolean redoBounds = kFalse;

		if (p.NewPage())
			{
			itsIsPrintingFlag  = kTrue;
			itsPrintWidth      = JRound(p.GetPageWidth() * 0.9);
			JCoordinate xShift = 0;
			JCoordinate yShift = 0;
			if (p.GetOrientation() == JPagePrinter::kPortrait)
				{
				itsPrintHeight = JRound(itsPrintWidth * 0.8);
				xShift = (p.GetPageWidth() - itsPrintWidth)/2;
				if ((GetXMarkCount() == 0) && (GetYMarkCount() == 0))
					{
					yShift = (p.GetPageHeight() - itsPrintHeight)/2;
					}
				}
			else
				{
				itsPrintHeight = p.GetPageHeight();
				}
			p.ShiftOrigin(xShift, yShift);
			PWBoundsChanged();
			redoBounds = kTrue;
			PWDraw(p, p.GetPageRect());
			p.ShiftOrigin(-xShift, -yShift);

			JBoolean putOnSamePage = kFalse;
			JRect partialPageRect;
			if (p.GetOrientation() == JPagePrinter::kPortrait)
				{
				putOnSamePage            = kTrue;
				const JCoordinate xShift = (p.GetPageWidth() - itsPrintWidth)/2;
				partialPageRect.Set(itsPrintHeight, xShift,
									p.GetPageHeight(),  xShift + itsPrintWidth);
				}
			if (!PrintMarks(p, putOnSamePage, partialPageRect))
				{
				cancelled = kTrue;
				}
			}

		if (!cancelled)
			{
			p.CloseDocument();
			}
		itsIsPrintingFlag = kFalse;
		if (redoBounds)
			{
			PWBoundsChanged();
			}
		}
}

/*******************************************************************************
 PWDraw (virtual protected)

	This is virtual so derived classes can override it to draw extra
	things on the plot.

 ******************************************************************************/

void
J2DPlotWidget::PWDraw
	(
	JPainter&		p,
	const JRect&	rect
	)
{
	p.SetFontName(itsFontName);
	p.SetFontSize(itsFontSize);

	if (itsGeometryNeedsAdjustment)
		{
		AdjustGeometry(p);
		}

	if (itsShowLegend)
		{
		DrawLegend(p);
		}

	DrawAxes(p);

	SetClipping(p);

	DrawCurves(p);
	DrawPoints(p);
	DrawErrors(p);
	DrawVectors(p);

	DrawMarks(p);
	DrawCursors(p);

	p.ResetClipRect();

	DrawCursorLabels(p);
}

/*******************************************************************************
 PWBoundsChanged

 ******************************************************************************/

void
J2DPlotWidget::PWBoundsChanged()
{
	itsSmoothSteps = PWGetWidth()/itsSmoothDivisor;
	UpdatePlot(kTrue);
}

/*******************************************************************************
 PWGetWidth (protected)

 ******************************************************************************/

JSize
J2DPlotWidget::PWGetWidth()
	const
{
	if (itsIsPrintingFlag)
		{
		return itsPrintWidth;
		}
	else
		{
		return PWGetGUIWidth();
		}
}

/*******************************************************************************
 PWGetHeight (protected)

 ******************************************************************************/

JSize
J2DPlotWidget::PWGetHeight()
	const
{
	if (itsIsPrintingFlag)
		{
		return itsPrintHeight;
		}
	else
		{
		return PWGetGUIHeight();
		}
}

/*******************************************************************************
 AdjustGeometry

 ******************************************************************************/

void
J2DPlotWidget::AdjustGeometry
	(
	JPainter& p
	)
{
	itsCurrentStringHeight 			= p.GetLineHeight();
	const JCoordinate kYLabelBuffer	= itsCurrentStringHeight + 2 * kLabelBuffer;
	const JCoordinate kXLabelBuffer = itsCurrentStringHeight + 2 * kLabelBuffer;
	const JCoordinate kTitleHeight	= itsCurrentStringHeight + 2 * kTitleBuffer;

	JSize maxLabelWidth = 0;

	const JSize count = itsCurves->GetElementCount();
	for (JSize i = 1; i <= count; i++)
		{
		JCurveInfo info = itsCurvesInfo->GetElement(i);
		JSize width = p.GetStringWidth(*(info.name));
		if (width > kMaxLegendStringWidth)
			{
			TruncateCurveName(p,i);
			}
		if (width > maxLabelWidth && info.curve)
			{
			maxLabelWidth = width;
			}
		}

	if (maxLabelWidth > kMaxLegendStringWidth)
		{
		itsMaxCurveNameWidth = kMaxLegendStringWidth;
		}
	else
		{
		itsMaxCurveNameWidth = maxLabelWidth;
		}

	itsLegendWidth = itsMaxCurveNameWidth + 3 * kLegendItemBuffer;
	JString str;
	JString strMin;
	JSize strWidthMin;
	JSize strWidth;

	if (itsYAxisIsLinear == kTrue)
		{
		if (itsForceYExp)
			{
			str = JString(itsCurrentYScale[kMax], itsCurrentYDecimalPoints, JString::kUseGivenExponent, itsCurrentYExp);
			strMin = JString(itsCurrentYScale[kMin], itsCurrentYDecimalPoints, JString::kUseGivenExponent, itsCurrentYExp);
			}
		else
			{
			str = JString(itsCurrentYScale[kMax], itsCurrentYDecimalPoints);
			strMin = JString(itsCurrentYScale[kMin], itsCurrentYDecimalPoints);
			}

		strWidthMin = p.GetStringWidth(strMin);
		strWidth = p.GetStringWidth(str);

		if (strWidthMin > strWidth)
			{
			strWidth = strWidthMin;
			}
		}
	else
		{
		JArray<JFloat> major;
		JArray<JFloat> minor;
		GetLogTicks(itsCurrentYScale[kMin], itsCurrentYScale[kMax], itsCurrentYScale[kInc], &major, &minor);

		strWidth = 0;
		JString str;
		JSize count = major.GetElementCount();
		for (JSize i = 1; i <= count; i++)
			{
			GetLogAxisString(major.GetElement(i), &str);
			JSize tempWidth = p.GetStringWidth(str);
			if (tempWidth > strWidth)
				{
				strWidth = tempWidth;
				}
			}
		if (count == 0)
			{
			count = minor.GetElementCount();
			for (JSize i = 1; i <= count; i++)
				{
				GetLogAxisString(minor.GetElement(i), &str);
				JSize tempWidth = p.GetStringWidth(str);
				if (tempWidth > strWidth)
					{
					strWidth = tempWidth;
					}
				}
			if (count == 0)
				{
				GetLogAxisString(pow(10, itsCurrentYScale[kMin]), &str);
				JSize tempWidth = p.GetStringWidth(str);
				if (tempWidth > strWidth)
					{
					strWidth = tempWidth;
					}
				GetLogAxisString(pow(10, itsCurrentYScale[kMax]), &str);
				tempWidth = p.GetStringWidth(str);
				if (tempWidth > strWidth)
					{
					strWidth = tempWidth;
					}
				}
			}
		}

	JString strXMin;
	JString strXMax;
	if (itsXAxisIsLinear)
		{
		if (itsForceXExp)
			{
			strXMax = JString(itsCurrentXScale[kMax], itsCurrentXDecimalPoints, JString::kUseGivenExponent, itsCurrentXExp);
			strXMin = JString(itsCurrentXScale[kMin], itsCurrentXDecimalPoints, JString::kUseGivenExponent, itsCurrentXExp);
			}
		else
			{
			strXMax = JString(itsCurrentXScale[kMax], itsCurrentXDecimalPoints);
			strXMin = JString(itsCurrentXScale[kMin], itsCurrentXDecimalPoints);
			}
		}
	else
		{
		if ( (itsCurrentXScale[kMin] > 3) || (itsCurrentXScale[kMin] < -3) )
			{
			strXMin = JString(pow(10,itsCurrentXScale[kMin]), 0, JString::kForceExponent);
			}

		else if (itsCurrentXScale[kMin] < 0)
			{
			int prec = (int)fabs(itsCurrentXScale[kMin]);
			strXMin = JString(pow(10,itsCurrentXScale[kMin]), prec);
			}
		else
			{
			strXMin = JString(pow(10,itsCurrentXScale[kMin]), 0);
			}

		if ( (itsCurrentXScale[kMax] > 3) || (itsCurrentXScale[kMax] < -3) )
			{
			strXMin = JString(pow(10,itsCurrentXScale[kMax]), 0, JString::kForceExponent);
			}

		else if (itsCurrentXScale[kMax] < 0)
			{
			int prec = (int)fabs(itsCurrentXScale[kMax]);
			strXMax = JString(pow(10,itsCurrentXScale[kMax]), prec);
			}
		else
			{
			strXMax = JString(pow(10,itsCurrentXScale[kMax]), 0);
			}
		}
	JSize strXWidthMin = p.GetStringWidth(strXMin);
	JSize strXWidthMax = p.GetStringWidth(strXMax);


	if (itsShowFrame)
		{
		if (strWidth < strXWidthMin/2)
			{
			strWidth = strXWidthMin/2;
			}

		itsXAxisStart = strWidth + kYLabelBuffer + kMajorTicLength; // tics inside

		if (itsShowLegend)
			{
			itsXAxisEnd = PWGetWidth() - 2 * kLegendBuffer - itsLegendWidth - strXWidthMax/2;
			}

		else
			{
			itsXAxisEnd = PWGetWidth() - kRightBuffer - strXWidthMax/2;
			}
		}

	else
		{
		if ( ((itsCurrentXScale[kMin] >= 0) && (itsCurrentXScale[kMax] >= 0)) ||
				!itsXAxisIsLinear )
			{
			if (strWidth < strXWidthMin/2)
				{
				strWidth = strXWidthMin/2;
				}

			itsXAxisStart = strWidth + kYLabelBuffer + 2 * kMajorTicLength;

			if (itsShowLegend)
				{
				itsXAxisEnd = PWGetWidth() - 2 * kLegendBuffer - itsLegendWidth - strXWidthMax/2;
				}

			else
				{
				itsXAxisEnd = PWGetWidth() - kRightBuffer - strXWidthMax/2;
				}
			}
		else if ( (itsCurrentXScale[kMin] <= 0) && (itsCurrentXScale[kMax] <= 0) )
			{
			if (strWidth < strXWidthMax/2)
				{
				strWidth = strXWidthMax/2;
				}

			itsXAxisStart = kYLabelBuffer + strXWidthMin/2;

			if (itsShowLegend)
				{
				itsXAxisEnd = PWGetWidth() - 2 * kLegendBuffer - itsLegendWidth
							- strWidth -2 *  kMajorTicLength;
				}

			else
				{
				itsXAxisEnd = PWGetWidth() - kRightBuffer
							- strWidth -2 *  kMajorTicLength;
				}
			}

		else
			{
			itsXAxisStart = kYLabelBuffer + strXWidthMin/2;

			if (itsShowLegend)
				{
				itsXAxisEnd = PWGetWidth() - 2 * kLegendBuffer - itsLegendWidth - strXWidthMax/2;
				}

			else
				{
				itsXAxisEnd = PWGetWidth() - kRightBuffer - strXWidthMax/2;
				}
			}
		}

	itsMaxXLabelWidth = strWidth;
	JSize cursorLabelHeight	= GetCursorLabelHeight(p);

	if (itsShowFrame)
		{
		itsYAxisStart = PWGetHeight() -
			(kXLabelBuffer +  kMajorTicLength + itsCurrentStringHeight + cursorLabelHeight) ;

		itsYAxisEnd = kTitleHeight;
		}

	else
		{
		if ( ((itsCurrentYScale[kMin] >= 0) && (itsCurrentYScale[kMax] >= 0)) ||
			 !itsYAxisIsLinear)
			{
			itsYAxisStart = PWGetHeight() -
			(kXLabelBuffer +  2 * kMajorTicLength + itsCurrentStringHeight + cursorLabelHeight) ;

			itsYAxisEnd = kTitleHeight;
			}

		else if ( (itsCurrentYScale[kMin] <= 0) && (itsCurrentYScale[kMax] <= 0) )
			{
			itsYAxisStart = PWGetHeight() - (kXLabelBuffer + cursorLabelHeight);

			itsYAxisEnd = kTitleHeight + 2 * kMajorTicLength + itsCurrentStringHeight;
			}

		else
			{
			itsYAxisStart = PWGetHeight() - (kXLabelBuffer + itsCurrentStringHeight/2 + cursorLabelHeight);

			itsYAxisEnd = kTitleHeight;
			}
		}

	itsXTrans = (itsXAxisEnd - itsXAxisStart)/
		(itsCurrentXScale[kMax] - itsCurrentXScale[kMin]);

	if (itsXTrans >= 35)
		{
		itsShowXMinorTics = kTrue;
		}
	else
		{
		itsShowXMinorTics = kFalse;
		}

	itsYTrans = (itsYAxisStart - itsYAxisEnd)/
					(itsCurrentYScale[kMax] - itsCurrentYScale[kMin]);

	if (itsYTrans >= 35)
		{
		itsShowYMinorTics = kTrue;
		}
	else
		{
		itsShowYMinorTics = kFalse;
		}

	itsGeometryNeedsAdjustment = kFalse;
//	if (itsChangingScale)
//		{
//		UpdateCursorInternals();
//		}
	AdjustCursors();
	itsChangingScale = kFalse;
	Broadcast(GeometryChanged());
}

/*******************************************************************************
 AdjustScaling

 ******************************************************************************/

void
J2DPlotWidget::AdjustScaling()
{
	itsIsZoomed = kFalse;

	JFloat maxXData;
	JFloat minXData;
	JFloat maxYData;
	JFloat minYData;

	GetRanges(&maxXData, &minXData, &maxYData, &minYData);
	AdjustScaling(kTrue, itsXAxisIsLinear, maxXData, minXData, itsCurrentXScale, itsDefXScale);
	AdjustScaling(kFalse, itsYAxisIsLinear, maxYData, minYData, itsCurrentYScale, itsDefYScale);

	itsGeometryNeedsAdjustment = kTrue;
	SetPlotDecPlaces();
}

/*******************************************************************************
 AdjustScaling

 ******************************************************************************/

void
J2DPlotWidget::AdjustScaling
	(
	const JBoolean  isX,
	const JBoolean	linear,
	const JFloat 	max,
	const JFloat 	min,
	JFloat 			scale[],
	JFloat 			defScale[]
	)
{
	if (linear)
		{
		JFloat start;
		JFloat end;
		JFloat inc;

		FindRange(max, min, &inc, &start, &end);
		scale[kInc] = inc;
		scale[kMin] = start;
		scale[kMax] = end;

		for (int i = 0; i < 3; i++)
			{
			defScale[i] = scale[i];
			}
		}
	else
		{
		if (max > 0)
			{
			JFloat pMin = min;
			GetMinimumPositiveValue(isX, &pMin);

			JFloat start;
			JFloat end;
			JFloat inc;

			FindLogRange(max, pMin, &inc, &start, &end);
			scale[kInc] = inc;
			scale[kMin] = start;
			scale[kMax] = end;
			}
		}

}

/*******************************************************************************
 GetRanges

 ******************************************************************************/

void
J2DPlotWidget::GetRanges
	(
	JFloat *maxX,
	JFloat *minX,
	JFloat *maxY,
	JFloat *minY
	)
{
	JFloat maxXTemp;
	JFloat minXTemp;
	JFloat maxYTemp;
	JFloat minYTemp;

	JFloat testXMax;
	JFloat testXMin;
	JFloat testYMax;
	JFloat testYMin;
	JIndex i;
	const JSize count = itsCurves->GetElementCount();
	for (i = 1; i <= count; i++)
		{
		JPlotDataBase* data = itsCurves->NthElement(i);
		data->GetXRange(&testXMin, &testXMax);
		AdjustExtremes(JConvertToBoolean(i == 1),
						&maxXTemp, testXMax,
						&minXTemp, testXMin);
		}

	for (i = 1; i <= count; i++)
		{
		JPlotDataBase* data = itsCurves->NthElement(i);
		if (data->GetYRange(minXTemp, maxXTemp, &testYMin, &testYMax))
			{
			AdjustExtremes(JConvertToBoolean(i == 1),
							&maxYTemp, testYMax,
							&minYTemp, testYMin);
			}
		}
	if (count == 0)
		{
		maxXTemp = itsDefaultXMax;
		minXTemp = itsDefaultXMin;
		maxYTemp = itsDefaultYMax;
		minYTemp = itsDefaultYMin;
		}

	*maxX = maxXTemp;
	*minX = minXTemp;
	*maxY = maxYTemp;
	*minY = minYTemp;
}

/*******************************************************************************
 AdjustExtremes

 ******************************************************************************/

void
J2DPlotWidget::AdjustExtremes
	(
	const JBoolean	first,
	JFloat* 		max,
	const JFloat 	testMax,
	JFloat* 		min,
	const JFloat 	testMin
	)
{
	if (first)
		{
		*max = testMax;
		*min = testMin;
		}
	else
		{
		if (testMax > *max)
			{
			*max = testMax;
			}
		if (testMin < *min)
			{
			*min = testMin;
			}
		}
}

/*******************************************************************************
 GetMinimumPositiveValue

 ******************************************************************************/

JBoolean
J2DPlotWidget::GetMinimumPositiveValue
	(
	const JBoolean 	isX,
	JFloat 			*min
	)
{
	JFloat minTemp;

	JBoolean foundPMin = kFalse;

	const JSize count = itsCurves->GetElementCount();
	for (JSize i = 1; i <= count; i++)
		{
		JPlotDataBase* data = itsCurves->NthElement(i);
		const JSize dcount = data->GetElementCount();
		for (JSize j = 1; j <= dcount; j++)
			{
			J2DDataPoint point;
			data->GetElement(j, &point);
			JFloat value;
			JFloat errValue;
			JFloat other;
			JBoolean linear;
			JBoolean linearOther;
			if (isX)
				{
				value 		= point.x;
				errValue 	= point.x - point.xmerr;
				other		= point.y;
				linear 		= itsXAxisIsLinear;
				linearOther	= itsYAxisIsLinear;
				}
			else
				{
				value 		= point.y;
				errValue 	= point.y - point.ymerr;
				other		= point.x;
				linear 		= itsYAxisIsLinear;
				linearOther	= itsXAxisIsLinear;
				}

			if (linear || (errValue > 0))
				{
				value = errValue;
				}
			JBoolean otherOK 	= kTrue;
			if ((linearOther == kFalse) && (other <= 0))
				{
				otherOK = kFalse;
				}
			if ((value > 0) && otherOK)
				{
				if (foundPMin)
					{
					if (value < minTemp)
						{
						minTemp = value;
						}
					}
				else
					{
					if ( (foundPMin && value < minTemp) ||
						 !foundPMin)
						{
						minTemp = value;
						foundPMin = kTrue;
						}
					}
				}
			}
		}

	*min = minTemp;

	return foundPMin;
}

/*******************************************************************************
 DrawAxes

 ******************************************************************************/

void
J2DPlotWidget::DrawAxes
	(
	JPainter& p
	)
{
	DrawTicks(p);

	if (itsShowFrame)
		{
		p.Rect(itsXAxisStart, itsYAxisEnd,
			itsXAxisEnd - itsXAxisStart + 1, itsYAxisStart - itsYAxisEnd + 1);

		itsYAxisRect.Set(itsYAxisEnd,
						 itsXAxisStart - itsMaxXLabelWidth - kMajorTicLength,
						 itsYAxisStart, itsXAxisStart);
		itsXAxisRect.Set(itsYAxisStart, itsXAxisStart,
						 itsYAxisStart + kMajorTicLength + itsCurrentStringHeight,
						 itsXAxisEnd);
		}

	else
		{
		// Draw vertical axis

		if ( ((itsCurrentXScale[kMin] >= 0) && (itsCurrentXScale[kMax] >= 0)) ||
				!itsXAxisIsLinear)
			{
			p.Line(itsXAxisStart, itsYAxisEnd,
					itsXAxisStart, itsYAxisStart);

			itsYAxisRect.Set(itsYAxisEnd,
							 itsXAxisStart - itsMaxXLabelWidth - 2 * kMajorTicLength,
							 itsYAxisStart, itsXAxisStart);
			}

		else if ( (itsCurrentXScale[kMin] <= 0) && (itsCurrentXScale[kMax] <= 0) )
			{
			p.Line(itsXAxisEnd, itsYAxisEnd,
					itsXAxisEnd, itsYAxisStart);

			itsYAxisRect.Set(itsYAxisEnd, itsXAxisEnd,
							 itsYAxisStart, itsXAxisEnd + itsMaxXLabelWidth + 2 * kMajorTicLength);
			}

		else
			{
			p.Line(JRound(itsXAxisStart - itsCurrentXScale[kMin]*itsXTrans), itsYAxisEnd,
				   JRound(itsXAxisStart - itsCurrentXScale[kMin]*itsXTrans), itsYAxisStart);

			itsYAxisRect.Set(itsYAxisEnd, JRound(itsXAxisStart - itsCurrentXScale[kMin]*itsXTrans) - itsMaxXLabelWidth - 2 * kMajorTicLength,
							 itsYAxisStart,
							 JRound(itsXAxisStart - itsCurrentXScale[kMin]*itsXTrans));
			}

		// Draw horizontal axis

		if ( ((itsCurrentYScale[kMin] >= 0) && (itsCurrentYScale[kMax] >= 0)) ||
				!itsYAxisIsLinear)
			{
			p.Line(itsXAxisStart, itsYAxisStart,
					itsXAxisEnd, itsYAxisStart);

			itsXAxisRect.Set(itsYAxisStart, itsXAxisStart,
							 itsYAxisStart + 2 * kMajorTicLength + itsCurrentStringHeight,
							 itsXAxisEnd);
			}

		else if ( (itsCurrentYScale[kMin] <= 0) && (itsCurrentYScale[kMax] <= 0) )
			{
			p.Line(itsXAxisStart, itsYAxisEnd,
					itsXAxisEnd, itsYAxisEnd);

			itsXAxisRect.Set(itsYAxisEnd - 2 * kMajorTicLength - itsCurrentStringHeight, itsXAxisStart,
							 itsYAxisEnd,
							 itsXAxisEnd);
			}

		else
			{
			p.Line(itsXAxisStart, JRound(itsYAxisStart + itsCurrentYScale[kMin]*itsYTrans),
				   itsXAxisEnd,   JRound(itsYAxisStart + itsCurrentYScale[kMin]*itsYTrans));

			itsXAxisRect.Set(JRound(itsYAxisStart + itsCurrentYScale[kMin]*itsYTrans),
							 itsXAxisStart,
							 JRound(itsYAxisStart + itsCurrentYScale[kMin]*itsYTrans) + 2 * kMajorTicLength + itsCurrentStringHeight,
							 itsXAxisEnd);
			}
		}

	DrawLabels(p);
}

/*******************************************************************************
 DrawTicks

 ******************************************************************************/

void
J2DPlotWidget::DrawTicks
	(
	JPainter& p
	)
{
	JSize yTick;
	JSize yLabel;
	JSize labelHeight = p.GetLineHeight();

	if (itsShowFrame)
		{
		yLabel = itsYAxisStart + kMajorTicLength;
		}

	else if ( ((itsCurrentYScale[kMin] >= 0) && (itsCurrentYScale[kMax] >= 0)) ||
				!itsYAxisIsLinear )
		{
		yTick  = itsYAxisStart;
		yLabel = itsYAxisStart + 2 * kMajorTicLength;
		}

	else if ( (itsCurrentYScale[kMin] <= 0) && (itsCurrentYScale[kMax] <= 0) )
		{
		yTick  = itsYAxisEnd;
		yLabel = itsYAxisEnd - 2 * kMajorTicLength - labelHeight;
		}

	else
		{
	 	yTick  = JRound(itsYAxisStart + itsCurrentYScale[kMin]*itsYTrans);
	 	yLabel = yTick + 2 * kMajorTicLength;
	 	}

	JFloat min = itsCurrentXScale[kMin];
	if (itsUseRealXStart)
		{
		min = itsCurrentXScale[kStart];
		}

	JSize tickCount = JRound( (itsCurrentXScale[kMax] - min)
							 /itsCurrentXScale[kInc]) + 1;


	JSize xTick;
	JSize xLabel;
	JBoolean xLabelEndKnown = kTrue;

	if (itsShowFrame)
		{
		xLabel = itsXAxisStart - kMajorTicLength;
		}

	else if ( ((itsCurrentXScale[kMin] >= 0) && (itsCurrentXScale[kMax] >= 0)) ||
				!itsXAxisIsLinear )
		{
		xTick  = itsXAxisStart - kMajorTicLength;
		xLabel = itsXAxisStart - 2 * kMajorTicLength;
		}

	else if ( (itsCurrentXScale[kMin] <= 0) && (itsCurrentXScale[kMax] <= 0) )
		{
		xTick  = itsXAxisEnd - kMajorTicLength;
		xLabel = itsXAxisEnd + 2 * kMajorTicLength;
		xLabelEndKnown = kFalse;
		}

	else
		{
	 	xTick  = JRound(itsXAxisStart - itsCurrentXScale[kMin]*itsXTrans - kMajorTicLength);
	 	xLabel = xTick - 2 * kMajorTicLength;
	 	}

	min = itsCurrentYScale[kMin];
	if (itsUseRealYStart)
		{
		min = itsCurrentYScale[kStart];
		}
	JSize yTickCount = JRound( (itsCurrentYScale[kMax] - min)
						 /itsCurrentYScale[kInc]) + 1;


	if (itsYAxisIsLinear)
		{
		DrawYTicks(p, xTick, xLabel, xLabelEndKnown, yTickCount);
		}
	else
		{
		DrawYLogMinorTicks(p, xTick, yTickCount, xLabel, xLabelEndKnown);
		}
	if (itsXAxisIsLinear)
		{
		DrawXTicks(p, yTick, yLabel, tickCount);
		}
	else
		{
		DrawXLogMinorTicks(p, yTick, tickCount, yLabel);
		}

}

/*******************************************************************************
 DrawXTicks

 ******************************************************************************/

void
J2DPlotWidget::DrawXTicks
	(
	JPainter& 	p,
	JCoordinate yTick,
	JCoordinate yLabel,
	JSize 		tickCount
	)
{
	JSize labelHeight 	= p.GetLineHeight();
	JFloat axisTemp 	= itsCurrentXScale[kMin];
	JFloat axisMin	 	= itsCurrentXScale[kMin];

	if (itsUseRealXStart)
		{
		axisTemp = itsCurrentXScale[kStart];
		axisMin	 = axisTemp;
		}

	for (JSize i = 1; i <= tickCount; i++)
		{
//		if ((axisTemp >= itsCurrentXScale[kMin] - 1) &&
//			(axisTemp <= itsCurrentXScale[kMax] + 1))
//			{
			DrawXTick(p, axisTemp, yTick, kMajorTicLength,
					  yLabel, kTrue, kFalse);
//			}
		axisTemp = axisMin + i * itsCurrentXScale[kInc];
		if (fabs(axisTemp) < (itsCurrentXScale[kInc]/100000))
			{
			axisTemp = 0;
			}
		}
}

/*******************************************************************************
 DrawXLogMinorTicks

 ******************************************************************************/

void
J2DPlotWidget::DrawXLogMinorTicks
	(
	JPainter& 	p,
	JCoordinate yTick,
	JSize 		tickCount,
	JCoordinate yLabel
	)
{
	JSize i;
	JArray<JFloat> major;
	JArray<JFloat> minor;

	GetLogTicks(itsCurrentXScale[kMin], itsCurrentXScale[kMax], itsCurrentXScale[kInc], &major, &minor);

	for (i = 1; i <= major.GetElementCount(); i++)
		{
		DrawXTick(p, major.GetElement(i), yTick,
				  kMajorTicLength, yLabel,
				  kTrue, kFalse);
		}

	if (itsCurrentXScale[kInc] > 1)
		{
		return;
		}

	JBoolean drawStr = JConvertToBoolean(major.GetElementCount() == 0);
	for (i = 1; i <= minor.GetElementCount(); i++)
		{
		DrawXTick(p, minor.GetElement(i), yTick,
				  kMajorTicLength - 1, yLabel,
				  drawStr, kTrue);
		}

	if ((major.GetElementCount() == 0) && (minor.GetElementCount() == 0))
		{
		DrawXTick(p, pow(10, itsCurrentXScale[kMin]), yTick,
				  kMajorTicLength, yLabel,
				  kTrue, kFalse);
		DrawXTick(p, pow(10, itsCurrentXScale[kMax]), yTick,
				  kMajorTicLength, yLabel,
				  kTrue, kFalse);
		}
}

/******************************************************************************
 DrawXTick (private)

 ******************************************************************************/

void
J2DPlotWidget::DrawXTick
	(
	JPainter& 	p,
	JFloat 		value,
	JCoordinate yVal,
	JCoordinate tickLength,
	JCoordinate yLabel,
	JBoolean 	drawStr,
	JBoolean 	lightGrid
	)
{
	JSize labelHeight	= p.GetLineHeight();
	JCoordinate x;
	if ((GetFrameX(value, &x)) && (x >= itsXAxisStart) && (x <= itsXAxisEnd))
		{
		if (drawStr)
			{
			JString str;
			if (itsXAxisIsLinear)
				{
				if (itsForceXExp)
					{
					str = JString(value, itsCurrentXDecimalPoints, JString::kUseGivenExponent, itsCurrentXExp);
					}
				else
					{
					str = JString(value, itsCurrentXDecimalPoints);
					}
				}
			else
				{
				GetLogAxisString(value, &str);
				}
			const JCoordinate sWidth = p.GetStringWidth(str);
			JSize strStart = x - sWidth/2;
			p.String(strStart, yLabel, str, sWidth, JPainter::kHAlignCenter, labelHeight);
			}
		if (itsShowFrame && (x > itsXAxisStart + 1) && (x < itsXAxisEnd - 1))
			{
			if (itsShowGrid)
				{
				if (lightGrid)
					{
					p.SetPenColor(itsGrayColor);
					}
				p.Line(x, itsYAxisStart, x, itsYAxisEnd);
				p.SetPenColor(itsBlackColor);
				}
			else
				{
				p.Line(x, itsYAxisStart, x, itsYAxisStart - tickLength);
				p.Line(x, itsYAxisEnd, x, itsYAxisEnd + tickLength);
				}
			}
		else if (!itsShowFrame)
			{
			p.Line(x, yVal - tickLength, x, yVal + tickLength);
			}
		}
}

/*******************************************************************************
 DrawYTicks

 ******************************************************************************/

void
J2DPlotWidget::DrawYTicks
	(
	JPainter&	p,
	JCoordinate xTick,
	JCoordinate xLabel,
	JBoolean 	xLabelEndKnown,
	JSize 		tickCount
	)
{
	// y axis
	JSize labelHeight 	= p.GetLineHeight();
	JFloat axisTemp 	= itsCurrentYScale[kMin];
	JFloat axisMin	 	= itsCurrentYScale[kMin];
	if (itsUseRealYStart)
		{
		axisTemp = itsCurrentYScale[kStart];
		axisMin	 = axisTemp;
		}

	for (JSize i = 1; i <= tickCount; i++)
		{
		DrawYTick(p, axisTemp, xTick, kMajorTicLength,
				  xLabel, xLabelEndKnown,
				  kTrue, kFalse);
		axisTemp = axisMin + i * itsCurrentYScale[kInc];
		if (fabs(axisTemp) < (itsCurrentYScale[kInc]/100000))
			{
			axisTemp = 0;
			}
		}

}

/*******************************************************************************
 DrawYLogMinorTicks

 ******************************************************************************/

void
J2DPlotWidget::DrawYLogMinorTicks
	(
	JPainter& 	p,
	JCoordinate xTick,
	JSize 		tickCount,
	JCoordinate xLabel,
	JBoolean 	xLabelEndKnown
	)
{
	JSize i;
	JArray<JFloat> major;
	JArray<JFloat> minor;

	GetLogTicks(itsCurrentYScale[kMin], itsCurrentYScale[kMax], itsCurrentYScale[kInc], &major, &minor);
	JSize count = major.GetElementCount();
	for (i = 1; i <= count; i++)
		{
		DrawYTick(p, major.GetElement(i), xTick,
				  kMajorTicLength, xLabel, xLabelEndKnown,
				  kTrue, kFalse);
		}

	if (itsCurrentYScale[kInc] > 1)
		{
		return;
		}

	JBoolean drawStr = JConvertToBoolean(major.GetElementCount() == 0);
	for (i = 1; i <= minor.GetElementCount(); i++)
		{
		DrawYTick(p, minor.GetElement(i), xTick,
				  kMajorTicLength - 1, xLabel, xLabelEndKnown,
				  drawStr, kTrue);
		}

	if ((major.GetElementCount() == 0) && (minor.GetElementCount() == 0))
		{
		DrawYTick(p, pow(10, itsCurrentYScale[kMin]), xTick,
				  kMajorTicLength, xLabel, xLabelEndKnown,
				  kTrue, kFalse);
		DrawYTick(p, pow(10, itsCurrentYScale[kMax]), xTick,
				  kMajorTicLength, xLabel, xLabelEndKnown,
				  kTrue, kFalse);
		}

}

/******************************************************************************
 DrawYTick (private)

 ******************************************************************************/

void
J2DPlotWidget::DrawYTick
	(
	JPainter& 	p,
	JFloat 		value,
	JCoordinate xVal,
	JCoordinate tickLength,
	JCoordinate xLabel,
	JCoordinate xLabelEndKnown,
	JBoolean 	drawStr,
	JBoolean 	lightGrid
	)
{
	JSize labelHeight	= p.GetLineHeight();
	JCoordinate y;
	if ((GetFrameY(value, &y)) && (y >= itsYAxisEnd) && (y <= itsYAxisStart))
		{
		if (drawStr)
			{
			JString str;
			if (itsYAxisIsLinear)
				{
				if (itsForceYExp)
					{
					str = JString(value, itsCurrentYDecimalPoints, JString::kUseGivenExponent, itsCurrentYExp);
					}
				else
					{
					str = JString(value, itsCurrentYDecimalPoints);
					}
				}
			else
				{
				GetLogAxisString(value, &str);
				}
			JSize strStart;
			const JCoordinate sWidth = p.GetStringWidth(str);
			if (xLabelEndKnown)
				{
				strStart = xLabel - sWidth;
				p.String(strStart, y - labelHeight/2,
					str, sWidth, JPainter::kHAlignRight, labelHeight);
				}
			else
				{
				strStart = xLabel;
				p.String(strStart, y - labelHeight/2,
					str, sWidth, JPainter::kHAlignLeft, labelHeight);
				}
			}
		if (itsShowFrame && (y > itsYAxisEnd + 1) && (y < itsYAxisStart - 1))
			{
			if (itsShowGrid)
				{
				if (lightGrid)
					{
					p.SetPenColor(itsGrayColor);
					}
				p.Line(itsXAxisStart, y, itsXAxisEnd, y);
				p.SetPenColor(itsBlackColor);
				}
			else
				{
				p.Line(itsXAxisStart, y, itsXAxisStart + tickLength, y);
				p.Line(itsXAxisEnd, y, itsXAxisEnd - tickLength, y);
				}
			}
		else if (!itsShowFrame)
			{
			JCoordinate tStart = xVal + 1;
			if (!lightGrid)
				{
				tStart = xVal;
				}
			p.Line(tStart, y, tStart + 2 * tickLength, y);
			}
		}
}

/*******************************************************************************
 DrawLabels

 ******************************************************************************/

void
J2DPlotWidget::DrawLabels
	(
	JPainter& p
	)
{
	JSize cursorLabelHeight	= GetCursorLabelHeight(p);
	JCoordinate yWidth 		= p.GetStringWidth(itsYLabel);
	JCoordinate yStart		= itsYAxisStart - (itsYAxisStart - itsYAxisEnd - yWidth)/2;

	JCoordinate xWidth 		= p.GetStringWidth(itsXLabel);
	JCoordinate xStart		= itsXAxisStart + (itsXAxisEnd - itsXAxisStart - xWidth)/2;
	JCoordinate xTop		= PWGetHeight() - kLabelBuffer - itsCurrentStringHeight - cursorLabelHeight;

	JCoordinate titleWidth	= p.GetStringWidth(itsTitle);
	JCoordinate titleStart	= itsXAxisStart + (itsXAxisEnd - itsXAxisStart - titleWidth)/2;

	itsYLabelRect.Set(	yStart - yWidth, kLabelBuffer,
						yStart, kLabelBuffer + itsCurrentStringHeight);

	itsXLabelRect.Set(	xTop,
						xStart, xTop + itsCurrentStringHeight,
						xStart + xWidth);

	itsTitleRect.Set(	kTitleBuffer, titleStart,
						2 * kTitleBuffer + itsCurrentStringHeight,
						titleStart + titleWidth);

// Draw Axis Labels

//	p.String(90.0, 0, itsYAxisStart, itsYLabel, itsYAxisStart - itsYAxisEnd,
//			 JPainter::kHAlignCenter, kYLabelBuffer, JPainter::kVAlignCenter);

	p.String(90.0, kLabelBuffer, yStart, itsYLabel, yWidth,
			 JPainter::kHAlignCenter, itsCurrentStringHeight,
			 JPainter::kVAlignCenter);

//	p.String(itsXAxisStart, PWGetHeight() - kXLabelBuffer - cursorLabelHeight, itsXLabel,
//			 itsXAxisEnd - itsXAxisStart, JPainter::kHAlignCenter, kXLabelBuffer,
//			 JPainter::kVAlignCenter);

	p.String(xStart, xTop, itsXLabel,
			 xWidth, JPainter::kHAlignCenter,
			 itsCurrentStringHeight, JPainter::kVAlignCenter);

// Draw Title

//	p.String(itsXAxisStart, 0, itsTitle, itsXAxisEnd - itsXAxisStart, JPainter::kHAlignCenter,
//			 kTitleBuffer, JPainter::kVAlignCenter);

	p.String(titleStart, kTitleBuffer, itsTitle,
			 titleWidth, JPainter::kHAlignCenter,
			 itsCurrentStringHeight, JPainter::kVAlignCenter);
}

/*******************************************************************************
 DrawCurves

 ******************************************************************************/

void
J2DPlotWidget::DrawCurves
	(
	JPainter& p
	)
{
	const JSize count = itsCurves->GetElementCount();
	for (JIndex i = 1; i <= count; i++)
		{
		JCurveInfo info = itsCurvesInfo->GetElement(i);
		if (info.lines && info.curve)
			{
			const JPlotDataBase* data = itsCurves->NthElement(i);
			JIndex colorIndex = info.color;
			p.SetPenColor(itsColors->GetElement(colorIndex));
			JBoolean firstPoint = kTrue;
			JPoint lastPoint;
			JPoint thisPoint;
			J2DDataPoint point;
			const JSize dcount = data->GetElementCount();
			for (JSize j = 1; j <= dcount; j++)
				{
				data->GetElement(j, &point);
				const JBoolean ok = GetFramePoint(point.x, point.y, &thisPoint);
				if (!ok)
					{
					firstPoint = kTrue;
					}
				else if (firstPoint && ok)
					{
					firstPoint = kFalse;
					}
				else if (!firstPoint)
					{
					p.Line(lastPoint, thisPoint);
					}

				lastPoint = thisPoint;
				}
			}
		}
}

/*******************************************************************************
 DrawPoints

 ******************************************************************************/

void
J2DPlotWidget::DrawPoints
	(
	JPainter& p
	)
{
	const JSize count = itsCurves->GetElementCount();
	for (JIndex i = 1; i <= count; i++)
		{
		JCurveInfo info = itsCurvesInfo->GetElement(i);
		if (info.symbols && info.curve)
			{
			const JPlotDataBase* data = itsCurves->NthElement(i);
			JIndex colorIndex = info.color;
			p.SetPenColor(itsColors->GetElement(colorIndex));

			JPoint thisPoint;
			J2DDataPoint point;
			const JSize dcount = data->GetElementCount();
			for (JIndex j = 1; j <= dcount; j++)
				{
				data->GetElement(j, &point);
				const JBoolean ok = GetFramePoint(point.x, point.y, &thisPoint);

				if (ok &&
					(thisPoint.x >= itsXAxisStart) &&
					(thisPoint.x <= itsXAxisEnd) &&
					(thisPoint.y >= itsYAxisEnd) &&
					(thisPoint.y <= itsYAxisStart))
					{
					if (!itsUsingRange ||
					 	( (point.x > itsRangeXMin) && (point.x < itsRangeXMax) &&
						  (point.y > itsRangeYMin) && (point.y < itsRangeYMax) ))
						{
						DrawSymbol(p, thisPoint, info.symbol);
						}
					}
				}
			}
		}
}

/*******************************************************************************
 DrawErrors

 ******************************************************************************/

void
J2DPlotWidget::DrawErrors
	(
	JPainter& p
	)
{
	const JSize count = itsCurves->GetElementCount();
	for (JIndex i = 1; i <= count; i++)
		{
		JCurveInfo info = itsCurvesInfo->GetElement(i);
		const JPlotDataBase* data = itsCurves->NthElement(i);
		if (info.curve && (data->HasXErrors() || data->HasYErrors()))
			{
			JIndex colorIndex = info.color;
			p.SetPenColor(itsColors->GetElement(colorIndex));

			JPoint thisPoint;
			JCoordinate xError;
			JCoordinate yError;
			JCoordinate xmError;
			JCoordinate ymError;
			for (JSize j = 1; j <= data->GetElementCount(); j++)
				{
				JBoolean noPoint = kFalse;
				xError = 0;
				yError = 0;
				J2DDataPoint point;
				data->GetElement(j, &point);
				if (itsXAxisIsLinear)
					{
					GetFrameX(point.x, &thisPoint.x);
					if (point.xerr != 0)
						{
						GetFrameX(point.x + point.xerr, &xError);
						GetFrameX(point.x - point.xmerr, &xmError);
						}
					}
				else
					{
					if (point.x > 0)
						{
						GetFrameX(point.x, &thisPoint.x);
						if (point.xerr != 0)
							{
							GetFrameX(point.x + point.xerr, &xError);
							if (point.x - point.xmerr > 0)
								{
								GetFrameX(point.x - point.xmerr, &xmError);
								}
							else
								{
								xmError = thisPoint.x;
								}
							}
						}
					else
						{
						noPoint = kTrue;
						}
					}

				if (itsYAxisIsLinear)
					{
					GetFrameY(point.y, &thisPoint.y);
					if (point.yerr != 0)
						{
						GetFrameY(point.y + point.yerr, &yError);
						GetFrameY(point.y - point.ymerr, &ymError);
						}
					}
				else
					{
					if (point.y > 0)
						{
						GetFrameY(point.y, &thisPoint.y);
						if (point.yerr != 0)
							{
							GetFrameY(point.y + point.yerr, &yError);
							if (point.y - point.ymerr > 0)
								{
								GetFrameY(point.y - point.ymerr, &ymError);
								}
							else
								{
								ymError = thisPoint.y;
								}
							}
						}
					else
						{
						noPoint = kTrue;
						}
					}

				if ((xError != 0) && (info.xerrors) &&
					(noPoint == kFalse) && data->HasXErrors())
					{
					p.Line(	xmError, thisPoint.y,
							xError, thisPoint.y);
					if (xmError != thisPoint.x)
						{
						p.Line(	xmError, thisPoint.y - 2,
								xmError, thisPoint.y + 2);
						}
					p.Line(	xError, thisPoint.y - 2,
							xError, thisPoint.y + 2);
					}
				if ((yError != 0) && (info.yerrors) &&
					(noPoint == kFalse)  && data->HasYErrors())
					{
					p.Line(	thisPoint.x, ymError,
							thisPoint.x, yError);
					if (ymError != thisPoint.y)
						{
						p.Line(	thisPoint.x - 2, ymError,
								thisPoint.x + 2, ymError);
						}
					p.Line(	thisPoint.x - 2, yError,
							thisPoint.x + 2, yError);
					}
				}
			}
		}
}

/*******************************************************************************
 DrawVectors (private)

 ******************************************************************************/

void
J2DPlotWidget::DrawVectors
	(
	JPainter& p
	)
{
	const JSize count = itsCurves->GetElementCount();
	for (JIndex i = 1; i <= count; i++)
		{
		JCurveInfo info = itsCurvesInfo->GetElement(i);
		if (!info.curve)
			{
			continue;
			}

		const JPlotDataBase* data              = itsCurves->NthElement(i);
		const JPlotDataBase::PlotDataType type = data->GetType();

		const JColorIndex colorIndex = itsColors->GetElement(info.color);
		p.SetPenColor(colorIndex);

		JPoint thisPoint, vectorHead;
		J2DDataPoint point;
		J2DVectorPoint vectorPoint;
		for (JIndex j = 1; j <= data->GetElementCount(); j++)
			{
			data->GetElement(j, &point);
			const JBoolean ok = GetFramePoint(point.x, point.y, &thisPoint);

			if ( ok &&
				 (thisPoint.x >= itsXAxisStart) &&
				 (thisPoint.x <= itsXAxisEnd) &&
				 (thisPoint.y >= itsYAxisEnd) &&
				 (thisPoint.y <= itsYAxisStart))
				{
				if (!itsUsingRange ||
				 	( (point.x > itsRangeXMin) && (point.x < itsRangeXMax) &&
					  (point.y > itsRangeYMin) && (point.y < itsRangeYMax) ))
					{
					if (type == JPlotDataBase::kVectorPlot)
						{
						data->GetElement(j, &vectorPoint);
						if (GetFramePoint(vectorPoint.x + vectorPoint.vx, vectorPoint.y + vectorPoint.vy, &vectorHead))
							{
							p.Line(thisPoint, vectorHead);
							}
						}
					if (info.points)
						{
						p.Point(thisPoint);
						}
					}
				}
			}
		}
}

/*******************************************************************************
 SetClipping (private)

 ******************************************************************************/

void
J2DPlotWidget::SetClipping
	(
	JPainter& p
	)
	const
{
	JCoordinate yClipEnd   = itsYAxisEnd;
	JCoordinate yClipStart = itsYAxisStart;
	JCoordinate xClipEnd   = itsXAxisEnd;
	JCoordinate xClipStart = itsXAxisStart;
	if (itsUsingRange)
		{
		if (itsRangeXMax < itsCurrentXScale[kMax])
			{
			xClipEnd = JRound(itsRangeXMax*itsXTrans - itsCurrentXScale[kMin]*itsXTrans + itsXAxisStart);
			}
		if (itsRangeXMin > itsCurrentXScale[kMin])
			{
			xClipStart = JRound(itsRangeXMin*itsXTrans - itsCurrentXScale[kMin]*itsXTrans + itsXAxisStart);
			}
		if (itsRangeYMax < itsCurrentYScale[kMax])
			{
			yClipEnd = JRound(itsYAxisStart - itsRangeYMax*itsYTrans + itsCurrentYScale[kMin]*itsYTrans);
			}
		if (itsRangeYMin > itsCurrentYScale[kMin])
			{
			yClipStart = JRound(itsYAxisStart - itsRangeYMin*itsYTrans + itsCurrentYScale[kMin]*itsYTrans);
			}
		}

	p.SetClipRect(JRect(yClipEnd, xClipStart, yClipStart+1, xClipEnd+1));
}

/*******************************************************************************
 DrawSymbol

 ******************************************************************************/

void
J2DPlotWidget::DrawSymbol
	(
	JPainter& p,
	const JPoint point,
	const JSymbolType type
	)
{
	if (type == kCircle)
		{
		p.Ellipse(point.x - 2, point.y - 2, 5, 5);
		}

	else if (type == kDiamond)
		{
		p.Line(point.x - 3, point.y, point.x, point.y - 3);
		p.LineTo(point.x + 3, point.y);
		p.LineTo(point.x, point.y + 3);
		p.LineTo(point.x - 3, point.y);
		}

	else if (type == kSquare)
		{
		p.Rect(point.x - 2, point.y - 2, 5, 5);
		}

	else if (type == kTriangle)
		{
		p.Line(point.x - 2, point.y + 2, point.x, point.y - 2);
		p.LineTo(point.x + 2, point.y + 2);
		p.LineTo(point.x - 2, point.y + 2);
		}

	else if (type == kPlus)
		{
		p.Line(point.x, point.y + 4, point.x, point.y - 3);
		p.Line(point.x - 3, point.y, point.x + 3, point.y);
		}

	else if (type == kCross)
		{
		p.Line(point.x - 3, point.y + 3, point.x + 3, point.y - 3);
		p.Line(point.x + 4, point.y + 4, point.x - 3, point.y - 3);
		}

}

/*******************************************************************************
 DrawLegend

 ******************************************************************************/

void
J2DPlotWidget::DrawLegend
	(
	JPainter& p
	)
{
	JSize items = itsCurves->GetElementCount();
	JSize visibleItems = 0;
	JIndex i;
	for (i = 1; i <= items; i++)
		{
		JCurveInfo info = itsCurvesInfo->GetElement(i);
		if (info.curve)
			{
			visibleItems++;
			}
		}
	JSize strHeight 		= p.GetLineHeight();
	JSize cursorLabelHeight	= GetCursorLabelHeight(p);

	JSize height = (visibleItems + 2) * strHeight;
	JSize starty = (PWGetHeight() - height - cursorLabelHeight)/2;
	JSize startx = PWGetWidth() - kLegendBuffer - itsLegendWidth;

	itsLegendRect.Set(starty, startx, starty + height, startx + itsLegendWidth);
	p.Rect(startx, starty, itsLegendWidth, height);

	JSize vis = 1;
	for (i = 1; i <= items; i++)
		{
		JCurveInfo info = itsCurvesInfo->GetElement(i);
		p.SetPenColor(itsBlackColor);

		if (info.curve)
			{
			JSize yPos = starty + vis*strHeight;
			p.String(startx + 2 * kLegendItemBuffer,
					yPos, *(info.name),
					itsMaxCurveNameWidth, JPainter::kHAlignLeft, strHeight);

			p.SetPenColor(itsColors->GetElement(info.color));

			if (info.lines)
				{
				p.Line( startx + kLegendItemBuffer/2,
						yPos + strHeight/2,
						JRound(startx + 1.5*kLegendItemBuffer + 1),
						yPos + strHeight/2 );
				}

			if (info.symbols)
				{
				DrawSymbol(p, JPoint(startx + kLegendItemBuffer,
							yPos + strHeight/2),
							info.symbol);
				}

			if (info.points)
				{
				p.Point(JPoint(startx + kLegendItemBuffer, yPos + strHeight/2));
				}

			const JPlotDataBase* data              = itsCurves->NthElement(i);
			const JPlotDataBase::PlotDataType type = data->GetType();
			if (type == JPlotDataBase::kVectorPlot)
				{
				JPoint vstart(startx + kLegendItemBuffer, yPos + strHeight/2);
				JPoint vend = vstart + JPoint(4,-4);
				p.Line(vstart, vend);
				}
			vis++;
			}
		}

	p.SetPenColor(itsBlackColor);
}

/*******************************************************************************
 GetFirstAvailableSymbol

 ******************************************************************************/

JSymbolType
J2DPlotWidget::GetFirstAvailableSymbol()
{
	assert( kCircle == 0 );
	JSize min          = itsSymbolUsage [ kCircle ];
	JSymbolType minType = kCircle;

	for (JIndex type = 1; type < kSymbolCount; type ++)
		{
		if (itsSymbolUsage[type] < min)
			{
			min     = itsSymbolUsage[type];
			minType = (JSymbolType) type;
			}
		}

	itsSymbolUsage[minType]++;
	return minType;
}

/*******************************************************************************
 GetFirstAvailableColor

 ******************************************************************************/

JIndex
J2DPlotWidget::GetFirstAvailableColor()
{
	JSize min = itsColorUsage->GetElement(1);

	JIndex minIndex = 1;

	for (JIndex color = 1; color <= itsColors->GetElementCount(); color ++)
		{
		const JSize usage = itsColorUsage->GetElement(color);
		if (usage < min)
			{
			min      = usage;
			minIndex = color;
			}
		}

	itsColorUsage->SetElement(minIndex, min + 1);
	return minIndex;
}

/*******************************************************************************
 SetFontName

 ******************************************************************************/

void
J2DPlotWidget::SetFontName
	(
	const JCharacter* name
	)
{
	itsFontName = name;
	UpdatePlot(kTrue);
}

/*******************************************************************************
 SetFontSize

 ******************************************************************************/

void
J2DPlotWidget::SetFontSize
	(
	const JSize size
	)
{
	itsFontSize = size;
	UpdatePlot(kTrue);
}

/*******************************************************************************
 GetDecPlaces

 ******************************************************************************/

JSize
J2DPlotWidget::GetDecPlaces
	(
	const JFloat value
	)
{
	JSize places = 0;
	JString valStr(value);
	if ((value >= 10000) ||
		(value < 0.001))
		{
		valStr = JString(value, JString::kPrecisionAsNeeded, JString::kForceExponent);
		}
	JIndex dotIndex = 0;
	JIndex eIndex;

	if (value == 0)
		{
		return 0;
		}

	JBoolean hadE = kFalse;;
	valStr.LocateSubstring(".", &dotIndex);
	if (valStr.LocateSubstring("e", kFalse, &eIndex))
		{
		if (dotIndex == 0)
			{
			places = 0;
			}
		else
			{
			places = eIndex - dotIndex - 1;
			}
		hadE = kTrue;
		}

	if (!hadE)
		{
		JCoordinate logPlace = JLFloor(log10(fabs(value)));
		if (logPlace >= 0)
			{
			logPlace = 0;
			}
		else
			{
			logPlace *= -1;
			}
		places = logPlace;
		while (!(floor(value*pow(10.0,places)) - value*pow(10.0,places) < 0.000001) &&
				(places <= 8) )
			{
			places ++;
			}
		}

	return places;
}

/*******************************************************************************
 SetPlotDecPlaces

 ******************************************************************************/

void
J2DPlotWidget::SetPlotDecPlaces()
{
	SetCurrentExp();

	JCoordinate maxDP = GetDecPlaces(itsCurrentXScale[kMax]);
	JCoordinate minDP = GetDecPlaces(itsCurrentXScale[kMin]);
	JCoordinate incDP = GetDecPlaces(itsCurrentXScale[kInc]);
	JCoordinate max = maxDP;
	if (minDP == max + 1)
		{
		max = minDP;
		}
	if (incDP == max + 1)
		{
		max = incDP;
		}

//	if ((max > 0) && (itsCurrentXScale[kInc] < pow(10,-max)))
//		{
//		max++;
//		}

	if ((itsForceXExp) && (floor(log10(itsCurrentXScale[kInc])) < itsCurrentXExp))
		{
		max++;
		}

	itsCurrentXDecimalPoints = max;

	JCoordinate pl = JLFloor(log10(itsCurrentXScale[kInc]));
	if (pl > 0)
		{
		itsCurrentXDecimalPoints = 0;
		if (itsForceXExp)
			{
			if (itsCurrentXExp >= pl)
				{
				itsCurrentXDecimalPoints = itsCurrentXExp - pl;
				}
			}
		}
	else
		{
		itsCurrentXDecimalPoints = (pl * -1);
		if (itsForceXExp)
			{
			if (itsCurrentXExp >= 0)
				{
				itsCurrentXDecimalPoints += itsCurrentXExp;
				}
			}
		}

	maxDP = GetDecPlaces(itsCurrentYScale[kMax]);
	minDP = GetDecPlaces(itsCurrentYScale[kMin]);
	incDP = GetDecPlaces(itsCurrentYScale[kInc]);

	max = maxDP;
	if (minDP > max)
		{
		max = minDP;
		}
	if (incDP > max)
		{
		max = incDP;
		}

//	if (itsCurrentYScale[kInc] < pow(10,-max))
//		{
//		max++;
//		}
	if ((itsForceYExp) && (floor(log10(itsCurrentYScale[kInc])) < itsCurrentYExp))
		{
		max++;
		}
	itsCurrentYDecimalPoints = max;
	pl = JLFloor(log10(itsCurrentYScale[kInc]));
	if (pl > 0)
		{
		itsCurrentYDecimalPoints = 0;
		if (itsForceYExp)
			{
			if (itsCurrentYExp >= pl)
				{
				itsCurrentYDecimalPoints = itsCurrentYExp - pl;
				}
			}
		}
	else
		{
		itsCurrentYDecimalPoints = (pl * -1);
		if (itsForceYExp)
			{
			if (itsCurrentYExp >= 0)
				{
				itsCurrentYDecimalPoints += itsCurrentYExp;
				}
			}
		}
}

/*******************************************************************************
 SetXAxisLinear

 ******************************************************************************/

void
J2DPlotWidget::SetXAxisLinear
	(
	const JBoolean linear
	)
{
	if (itsXAxisIsLinear == linear)
		{
		return;
		}

	itsXAxisIsLinear = linear;
	Broadcast(ScaleChanged());
//	ResetScaling();
}

/*******************************************************************************
 SetYAxisLinear

 ******************************************************************************/

void
J2DPlotWidget::SetYAxisLinear
	(
	const JBoolean linear
	)
{
	if (itsYAxisIsLinear == linear)
		{
		return;
		}
	itsYAxisIsLinear = linear;
	Broadcast(ScaleChanged());
//	ResetScaling();
}

/*******************************************************************************
 GetLogIncrement

 ******************************************************************************/

JFloat
J2DPlotWidget::GetLogIncrement
	(
	JFloat max,
	JFloat min
	)
{
	JFloat maxTemp = ceil(max);
	JFloat minTemp = floor(min);
	JBoolean found = kFalse;
	JFloat inc = 1;

	while (!found)
		{
		JFloat tempInc = inc;

		if ((maxTemp - minTemp)/tempInc <= 10)
			{
			found = kTrue;
			}
		else
			{
			tempInc = inc * 2;
			if ((maxTemp - minTemp)/tempInc <= 10)
				{
				inc = tempInc;
				found = kTrue;
				}

			else
				{
				tempInc = inc * 5;
				if ((maxTemp - minTemp)/tempInc <= 10)
					{
					inc = tempInc;
					found = kTrue;
					}
				}
			}

		if (!found)
			{
			inc *= 10;
			}
		}

	return inc;
}

/*******************************************************************************
 FindLogRange

 ******************************************************************************/

void
J2DPlotWidget::FindLogRange
	(
	JFloat max,
	JFloat min,
	JFloat *inc,
	JFloat *start,
	JFloat *end
	)
{
	if (max == min)
		{
		max *= 2;
		}
	JFloat tempMin = floor(log10(min));
	JFloat tempMax = ceil(log10(max));
	JFloat tempInc = GetLogIncrement(tempMax, tempMin);
	if (tempInc > 1)
		{
		if (tempMin <= 0)
			{
			int counter = 0;
			while (-tempInc*counter > tempMin)
				{
				counter ++;
				}
			*start = -tempInc*counter;
			}
		else
			{
			int counter = 0;
			while (tempInc*(counter + 1) < tempMin)
				{
				counter ++;
				}
			*start = tempInc*counter;
			}

		if (tempMax <= 0)
			{
			int counter = 0;
			while (-tempInc*(counter + 1) > tempMax)
				{
				counter ++;
				}
			*end = -tempInc*counter;
			}
		else
			{
			int counter = 0;
			while (tempInc*counter < tempMax)
				{
				counter ++;
				}
			*end = tempInc*counter;
			}
		}
	else
		{
		*start = tempMin;
		*end = tempMax;
		}
	*inc = tempInc;
}

/*******************************************************************************
 TruncateCurveName

 ******************************************************************************/

void
J2DPlotWidget::TruncateCurveName
	(
	JPainter& p,
	const JIndex index
	)
{
	assert (itsCurvesInfo->IndexValid(index));

	JCurveInfo info = itsCurvesInfo->GetElement(index);
	while (p.GetStringWidth(*(info.name)) > kMaxLegendStringWidth)
		{
		(info.name)->RemoveSubstring((info.name)->GetLength(),(info.name)->GetLength());
		}
}

/*******************************************************************************
 ShowXErrors

 ******************************************************************************/

void
J2DPlotWidget::ShowXErrors
	(
	const JBoolean show,
	const JIndex index
	)
{
	assert (itsCurvesInfo->IndexValid(index));

	JCurveInfo info = itsCurvesInfo->GetElement(index);
	if (info.xerrors != show)
		{
		info.xerrors = show;
		itsCurvesInfo->SetElement(index, info);
		UpdatePlot();
		}
}

/*******************************************************************************
 ShowYErrors

 ******************************************************************************/

void
J2DPlotWidget::ShowYErrors
	(
	const JBoolean show,
	const JIndex index
	)
{
	assert (itsCurvesInfo->IndexValid(index));

	JCurveInfo info = itsCurvesInfo->GetElement(index);
	if (info.yerrors != show)
		{
		info.yerrors = show;
		itsCurvesInfo->SetElement(index, info);
		UpdatePlot();
		}
}

/******************************************************************************
 XErrorsAreVisible

 ******************************************************************************/

JBoolean
J2DPlotWidget::XErrorsAreVisible
	(
	const JIndex index
	)
{
	assert (itsCurvesInfo->IndexValid(index));

	JCurveInfo info = itsCurvesInfo->GetElement(index);
	return info.xerrors;
}

/******************************************************************************
 YErrorsAreVisible

 ******************************************************************************/

JBoolean
J2DPlotWidget::YErrorsAreVisible
	(
	const JIndex index
	)
{
	assert (itsCurvesInfo->IndexValid(index));

	JCurveInfo info = itsCurvesInfo->GetElement(index);
	return info.yerrors;
}

/*******************************************************************************
 ArrayIsInCurve

 ******************************************************************************/

JBoolean
J2DPlotWidget::ArrayIsInCurve
	(
	JArray<JFloat>* testArray
	)
{
/*
	for (JSize i = 1; i <= itsCurves->GetElementCount(); i++)
		{
		if ((itsCurves->NthElement(i))->ArrayInData(testArray))
			{
			return kTrue;
			}
		}*/
	return kFalse;

}

/*******************************************************************************
 RemoveCurveContainingArray

 ******************************************************************************/

void
J2DPlotWidget::RemoveCurvesContainingArray
	(
	JArray<JFloat>* testArray
	)
{
/*
	for (JSize i = 1; i<= itsCurves->GetElementCount(); i++)
		{
		if ((itsCurves->NthElement(i))->ArrayInData(testArray))
			{
			RemoveCurve(i);
			i--;
			}
		}
	UpdatePlot(kTrue, kTrue);
*/
}

/*******************************************************************************
 UpdatePlot

 ******************************************************************************/

void
J2DPlotWidget::UpdatePlot
	(
	const JBoolean geometry,
	const JBoolean scaling
	)
{
	if (geometry)
		{
		itsGeometryNeedsAdjustment = kTrue;
		}

	if (scaling)
		{
		AdjustScaling();
		}
	Broadcast(PlotChanged());
	PWRefresh();
}

/*******************************************************************************
 SetCurrentExp

 ******************************************************************************/

void
J2DPlotWidget::SetCurrentExp()
{
	JCoordinate max = 0;
	JCoordinate min = 0;
	JCoordinate exponent;
	if (GetExp(itsCurrentXScale[kMax], &exponent))
		{
		max = exponent;
		}

	if (GetExp(itsCurrentXScale[kMin], &exponent))
		{
		min = exponent;
		}

	if ( (max == 0) && (min == 0) )
		{
		itsCurrentXExp = 0;
		itsForceXExp = kFalse;
		}
	else if (max > 0)
		{
		if (max > min)
			{
			itsCurrentXExp = max;
			itsForceXExp = kTrue;
			}
		else
			{
			itsCurrentXExp = min;
			itsForceXExp = kTrue;
			}
		}
	else
		{
		if (min > max)
			{
			itsCurrentXExp = max;
			itsForceXExp = kTrue;
			}
		else
			{
			itsCurrentXExp = min;
			itsForceXExp = kTrue;
			}
		}

	min = 0;
	max = 0;

	if (GetExp(itsCurrentYScale[kMax], &exponent))
		{
		max = exponent;
		}

	if (GetExp(itsCurrentYScale[kMin], &exponent))
		{
		min = exponent;
		}

	if ( (max == 0) && (min == 0) )
		{
		itsCurrentYExp = 0;
		itsForceYExp = kFalse;
		}
	else if (max > 0)
		{
		if (max > min)
			{
			itsCurrentYExp = max;
			itsForceYExp = kTrue;
			}
		else
			{
			itsCurrentYExp = min;
			itsForceYExp = kTrue;
			}
		}
	else
		{
		if (min > max)
			{
			itsCurrentYExp = max;
			itsForceYExp = kTrue;
			}
		else
			{
			itsCurrentYExp = min;
			itsForceYExp = kTrue;
			}
		}
}

/*******************************************************************************
 GetExp

 ******************************************************************************/

JBoolean
J2DPlotWidget::GetExp
	(
	const JFloat value,
	JCoordinate *exp
	)
{
	JString str(value);
	if (str.Contains("e") ||
		(value >= 10000) ||
		((value < 0.001) && (value > 0)) ||
		(value <= -10000) ||
		((value >= -0.001) && (value < 0)))
		{
		*exp = (JLFloor(log10(fabs(value))));
		return kTrue;
		}
	return kFalse;
}

/******************************************************************************
 PWWriteSetup

 ******************************************************************************/

void
J2DPlotWidget::PWWriteSetup
	(
	ostream& output
	)
	const
{
	output << kCurrentSetupVersion;
	output << ' ' << GetFontName();
	output << ' ' << GetFontSize();
	output << ' ' << GetXLabel();
	output << ' ' << GetYLabel();
	output << ' ' << GetTitle();
	output << ' ' << FrameIsVisible();
	output << ' ' << GridIsVisible();
	output << ' ' << LegendIsVisible();
	output << ' ' << XAxisIsLinear();
	output << ' ' << YAxisIsLinear();
	output << ' ' << itsIsZoomed;
	output << ' ' << itsCurrentXScale[kMin];
	output << ' ' << itsCurrentXScale[kMax];
	output << ' ' << itsCurrentXScale[kInc];
	output << ' ' << itsCurrentYScale[kMin];
	output << ' ' << itsCurrentYScale[kMax];
	output << ' ' << itsCurrentYScale[kInc];

	output << ' ' << itsUsingRange;
	if (itsUsingRange)
		{
		output << ' ' << itsRangeXMin;
		output << ' ' << itsRangeXMax;
		output << ' ' << itsRangeYMin;
		output << ' ' << itsRangeYMax;
		}

	// Cursors
	output << ' ' << itsXCursorVisible;
	output << ' ' << itsYCursorVisible;
	output << ' ' << itsDualCursors;
	output << ' ' << itsXCursorPos1;
	output << ' ' << itsXCursorPos2;
	output << ' ' << itsYCursorPos1;
	output << ' ' << itsYCursorPos2;
	output << ' ' << itsXCursorVal1;
	output << ' ' << itsXCursorVal2;
	output << ' ' << itsYCursorVal1;
	output << ' ' << itsYCursorVal2;

	// Marks
	JSize i;
	output << ' ' << GetXMarkCount();
	for (i = 1; i <= GetXMarkCount(); i++)
		{
		output << ' ' << itsXMarks->GetElement(i);
		}
	output << ' ' << GetYMarkCount();
	for (i = 1; i <= GetYMarkCount(); i++)
		{
		output << ' ' << itsYMarks->GetElement(i);
		}
}

/******************************************************************************
 PWReadSetup

	This uses assert() to check the version number because we cannot use
	a marker to skip the data (since it contains strings), so it is the
	client's responsibility to check whether or not the file can be
	read (via a higher level version number).

 ******************************************************************************/

void
J2DPlotWidget::PWReadSetup
	(
	istream& input
	)
{
	JFileVersion vers = 0;
	input >> ws;
	if (isdigit(input.peek()))		// original code started with a string
		{
		input >> vers;
		assert( vers <= kCurrentSetupVersion );
		}

	JString temp;
	input >> temp;
	SetFontName(temp);
	JSize size;
	input >> size;
	SetFontSize(size);
	input >> temp;
	SetXLabel(temp);
	input >> temp;
	SetYLabel(temp);
	input >> temp;
	SetTitle(temp);
	JBoolean bTest;
	input >> bTest;
	ShowFrame(bTest);
	input >> bTest;
	ShowGrid(bTest);
	input >> bTest;
	ShowLegend(bTest);
	input >> bTest;
	SetXAxisLinear(bTest);
	input >> bTest;
	SetYAxisLinear(bTest);
	input >> itsIsZoomed;
	input >> itsCurrentXScale[kMin];
	input >> itsCurrentXScale[kMax];
	input >> itsCurrentXScale[kInc];
	input >> itsCurrentYScale[kMin];
	input >> itsCurrentYScale[kMax];
	input >> itsCurrentYScale[kInc];
	if (itsIsZoomed)
		{
		SetXScaling(itsCurrentXScale[kMin], itsCurrentXScale[kMax], itsCurrentXScale[kInc]);
		SetYScaling(itsCurrentYScale[kMin], itsCurrentYScale[kMax], itsCurrentYScale[kInc]);
		}
	input >> itsUsingRange;
	if (itsUsingRange)
		{
		input >> itsRangeXMin;
		input >> itsRangeXMax;

		input >> itsRangeYMin;
		input >> itsRangeYMax;
		}

	// Cursors
	input >> itsXCursorVisible;
	input >> itsYCursorVisible;
	input >> itsDualCursors;
	if (vers == 0)
		{
		if (itsXCursorVisible)
			{
			input >> itsXCursorPos1;
			if (itsDualCursors)
				{
				input >> itsXCursorPos2;
				}
			}
		if (itsYCursorVisible)
			{
			input >> itsYCursorPos1;
			if (itsDualCursors)
				{
				input >> itsYCursorPos2;
				}
			}
		}
	else
		{
		input >> itsXCursorPos1 >> itsXCursorPos2;
		input >> itsYCursorPos1 >> itsYCursorPos2;
		input >> itsXCursorVal1 >> itsXCursorVal2;
		input >> itsYCursorVal1 >> itsYCursorVal2;
		}

	// Marks
	JSize i, count;
	input >> count;
	for (i = 1; i <= count; i++)
		{
		JFloat value;
		input >> value;
		itsXMarks->AppendElement(value);
		}
	input >> count;
	for (i = 1; i <= count; i++)
		{
		JFloat value;
		input >> value;
		itsYMarks->AppendElement(value);
		}

	Broadcast(CursorsChanged(	itsXCursorVisible,
								itsYCursorVisible,
								itsDualCursors,
								itsXCursorVal1,
								itsXCursorVal2,
								itsYCursorVal1,
								itsYCursorVal2));
}

/******************************************************************************
 PWWriteCurveSetup

 ******************************************************************************/

void
J2DPlotWidget::PWWriteCurveSetup
	(
	ostream& output
	)
	const
{
JIndex i;

	output << kCurrentCurveSetupVersion;

	// write sizes first so they can be checked immediately when reading

	const JSize curveCount = itsCurves->GetElementCount();
	output << ' ' << curveCount;

	const JSize colorCount = itsColors->GetElementCount();
	output << ' ' << colorCount;

	output << ' ' << (JSize) kSymbolCount;

	// write curve info

	for (i=1; i<=curveCount; i++)
		{
		JCurveInfo info = itsCurvesInfo->GetElement(i);
		output << ' ' << info;
		}

	// must be able to restore color and symbol usage

	for (i=1; i<=colorCount; i++)
		{
		output << ' ' << itsColorUsage->GetElement(i);
		}

	for (i=0; i<kSymbolCount; i++)
		{
		output << ' ' << itsSymbolUsage[i];
		}
}

/******************************************************************************
 PWReadCurveSetup

	This uses assert() to check the version number because we cannot use
	a marker to skip the data (since it contains strings), so it is the
	client's responsibility to check whether or not the file can be
	read (via a higher level version number).

	This function does nothing (i.e. skips the data) if the number of
	curves in the plot does not match the number of curves in the data
	stream, or if there are fewer colors in the plot than in the data
	stream.

 ******************************************************************************/

void
J2DPlotWidget::PWReadCurveSetup
	(
	istream& input
	)
{
JIndex i;

	JFileVersion vers;
	input >> vers;
	assert( vers <= kCurrentCurveSetupVersion );

	// check sizes first so we know whether the data can be used

	const JSize origCurveCount = itsCurves->GetElementCount();
	const JSize origColorCount = itsColors->GetElementCount();

	JSize curveCount, colorCount, symbolCount;
	input >> curveCount >> colorCount >> symbolCount;

	const JBoolean setData = JI2B( curveCount  == origCurveCount &&
								   colorCount  <= origColorCount &&
								   symbolCount <= kSymbolCount );

	// curve info

	JString name;
	for (i=1; i<=curveCount; i++)
		{
		JCurveInfo info;
		info.name = new JString();
		assert(info.name != NULL);
		input >> info;

		if (setData)
			{
			itsCurvesInfo->SetElement(i,info);
			}
		}

	// restore color and symbol usage

	JSize usage;
	for (i=1; i<=colorCount; i++)
		{
		input >> usage;
		if (setData)
			{
			itsColorUsage->SetElement(i, usage);
			}
		}
	if (setData)
		{
		for (i=colorCount+1; i<=origColorCount; i++)
			{
			itsColorUsage->SetElement(i, 0);
			}
		}

	for (i=0; i<symbolCount; i++)
		{
		input >> usage;
		if (setData)
			{
			itsSymbolUsage[i] = usage;
			}
		}
	if (setData)
		{
		for (i=symbolCount; i<kSymbolCount; i++)
			{
			itsSymbolUsage[i] = 0;
			}
		}
}

/*******************************************************************************
 SetRange

 ******************************************************************************/

void
J2DPlotWidget::SetRange()
{
	SetRange(itsCurrentXScale[kMin], itsCurrentXScale[kMax],
			 itsCurrentYScale[kMin], itsCurrentYScale[kMax]);
}

/*******************************************************************************
 SetRange

 ******************************************************************************/

void
J2DPlotWidget::SetRange
	(
	const JFloat xmin,
	const JFloat xmax,
	const JFloat ymin,
	const JFloat ymax
	)
{
	itsUsingRange = kTrue;
	itsRangeXMax  = xmax;
	itsRangeXMin  = xmin;
	itsRangeYMax  = ymax;
	itsRangeYMin  = ymin;
	Broadcast(PlotChanged());
}

/*******************************************************************************
 ClearRange (public)

 ******************************************************************************/

void
J2DPlotWidget::ClearRange()
{
	if (itsUsingRange)
		{
		itsUsingRange = kFalse;
		Broadcast(PlotChanged());
		PWRefresh();
		}
}

/*******************************************************************************
 GetRange

 ******************************************************************************/

JBoolean
J2DPlotWidget::GetRange
	(
	JFloat* xmin,
	JFloat* xmax,
	JFloat* ymin,
	JFloat* ymax
	)
{
	if (itsUsingRange)
		{
		*xmin = itsRangeXMin;
		*xmax = itsRangeXMax;
		*ymin = itsRangeYMin;
		*ymax = itsRangeYMax;
		return kTrue;
		}
	return kFalse;
}

/*******************************************************************************
 GetRealX

 ******************************************************************************/

JFloat
J2DPlotWidget::GetRealX
	(
	const JCoordinate frameX
	)
{
	JFloat value;
	if (itsXAxisIsLinear)
		{
		 value = (JFloat)(frameX - itsXAxisStart)/itsXTrans + itsCurrentXScale[kMin];
		}
	else
		{
		value = pow(10, (frameX - itsXAxisStart + itsCurrentXScale[kMin]*itsXTrans)/itsXTrans);
		}
	return value;
}

/*******************************************************************************
 GetRealY

 ******************************************************************************/

JFloat
J2DPlotWidget::GetRealY
	(
	const JCoordinate frameY
	)
{
	JFloat value;
	if (itsYAxisIsLinear)
		{
		value = (itsYAxisStart - frameY + itsCurrentYScale[kMin]*itsYTrans)/itsYTrans;
		}
	else
		{
		value = pow(10, (itsYAxisStart - frameY + itsCurrentYScale[kMin]*itsYTrans)/itsYTrans);
		}
	return value;
}

/*******************************************************************************
 GetRealPoint

 ******************************************************************************/

void
J2DPlotWidget::GetRealPoint
	(
	const JPoint framePoint,
	JFloat* x,
	JFloat* y
	)
{
	*x = GetRealX(framePoint.x);
	*y = GetRealY(framePoint.y);
}

/*******************************************************************************
 GetFrameX

 ******************************************************************************/

JBoolean
J2DPlotWidget::GetFrameX
	(
	const JFloat x,
	JCoordinate* frameX
	)
{
	if (itsXAxisIsLinear)
		{
		*frameX = JRound(x*itsXTrans - itsCurrentXScale[kMin]*itsXTrans + itsXAxisStart);
		return kTrue;
		}
	else if (x > 0)
		{
		*frameX = JRound(log10(x)*itsXTrans - itsCurrentXScale[kMin]*itsXTrans + itsXAxisStart);
		return kTrue;
		}
	else
		{
		return kFalse;
		}
}

/*******************************************************************************
 GetFrameY

 ******************************************************************************/

JBoolean
J2DPlotWidget::GetFrameY
	(
	const JFloat y,
	JCoordinate* frameY
	)
{
	if (itsYAxisIsLinear)
		{
		*frameY = JRound(itsYAxisStart - y*itsYTrans + itsCurrentYScale[kMin]*itsYTrans);
		return kTrue;
		}
	else if (y > 0)
		{
		*frameY = JRound(itsYAxisStart - log10(y)*itsYTrans + itsCurrentYScale[kMin]*itsYTrans);
		return kTrue;
		}
	else
		{
		return kFalse;
		}
}

/******************************************************************************
 GetCurveNames

 ******************************************************************************/

void
J2DPlotWidget::GetCurveNames
	(
	JPtrArray<JString>* names
	)
{
	const JSize count = itsCurves->GetElementCount();
	for (JSize i = 1; i <= count; i++)
		{
		JCurveInfo info = itsCurvesInfo->GetElement(i);
		names->Append(info.name);
		}
}

/*******************************************************************************
 CurveIsVisible

 ******************************************************************************/

JBoolean
J2DPlotWidget::CurveIsVisible
	(
	const JIndex curveIndex
	)
	const
{
	assert(itsCurvesInfo->IndexValid(curveIndex));
	JCurveInfo info = itsCurvesInfo->GetElement(curveIndex);
	return info.curve;
}

/*******************************************************************************
 LinesAreVisible

 ******************************************************************************/

JBoolean
J2DPlotWidget::LinesAreVisible
	(
	const JIndex curveIndex
	)
	const
{
	assert(itsCurvesInfo->IndexValid(curveIndex));
	JCurveInfo info = itsCurvesInfo->GetElement(curveIndex);
	return info.lines;
}

/*******************************************************************************
 SymbolIsVisible

 ******************************************************************************/

JBoolean
J2DPlotWidget::SymbolIsVisible
	(
	const JIndex curveIndex
	)
	const
{
	assert(itsCurvesInfo->IndexValid(curveIndex));
	JCurveInfo info = itsCurvesInfo->GetElement(curveIndex);
	return info.symbols;
}

/*******************************************************************************
 CurveIsProtected

 ******************************************************************************/

JBoolean
J2DPlotWidget::CurveIsProtected
	(
	const JIndex curveIndex
	)
	const
{
	assert(itsCurvesInfo->IndexValid(curveIndex));
	JCurveInfo info = itsCurvesInfo->GetElement(curveIndex);
	return info.protect;
}

/*******************************************************************************
 GetCurveColor

 ******************************************************************************/

JColorIndex
J2DPlotWidget::GetCurveColor
	(
	const JIndex index
	)
	const
{
	assert(itsCurvesInfo->IndexValid(index));
	JCurveInfo info = itsCurvesInfo->GetElement(index);
	return info.color;
}

/*******************************************************************************
 GetSymbolType

 ******************************************************************************/

JSymbolType
J2DPlotWidget::GetSymbolType
	(
	const JIndex index
	)
	const
{
	assert(itsCurvesInfo->IndexValid(index));
	JCurveInfo info = itsCurvesInfo->GetElement(index);
	return info.symbol;
}

/******************************************************************************
 GetMouseCursor (protected)

 ******************************************************************************/

J2DPlotWidget::MouseCursor
J2DPlotWidget::GetMouseCursor
	(
	const JPoint& pt
	)
	const
{
	if (itsLegendRect.Contains(pt) ||
		itsTitleRect.Contains(pt) ||
		itsXLabelRect.Contains(pt) ||
		itsYLabelRect.Contains(pt) ||
		itsXAxisRect.Contains(pt) ||
		itsYAxisRect.Contains(pt))
		{
		return kDblClickChangeCursor;
		}
	else
		{
		const CursorIndex cursor = GetCursorIndex(pt);
		if (cursor == kX1 || cursor == kX2)
			{
			return kDragXCursor;
			}
		else if (cursor == kY1 || cursor == kY2)
			{
			return kDragYCursor;
			}
		else
			{
			return kZoomCursor;
			}
		}
}

/*******************************************************************************
 GetCursorIndex (protected)

 ******************************************************************************/

J2DPlotWidget::CursorIndex
J2DPlotWidget::GetCursorIndex
	(
	const JPoint& pt
	)
	const
{
	if (!JRect(itsYAxisEnd,   itsXAxisStart,
			   itsYAxisStart, itsXAxisEnd).Contains(pt))
		{
		return kNoCursor;
		}
	if (itsXCursorVisible &&
		(pt.x >= itsXCursorPos1 - 3) &&
		(pt.x <= itsXCursorPos1 + 3))
		{
		return kX1;
		}
	else if (itsXCursorVisible && itsDualCursors &&
			 (pt.x >= itsXCursorPos2 - 3) &&
			 (pt.x <= itsXCursorPos2 + 3))
		{
		return kX2;
		}
	else if (itsYCursorVisible &&
			 (pt.y >= itsYCursorPos1 - 3) &&
			 (pt.y <= itsYCursorPos1 + 3))
		{
		return kY1;
		}
	else if (itsYCursorVisible && itsDualCursors &&
			 (pt.y >= itsYCursorPos2 - 3) &&
			 (pt.y <= itsYCursorPos2 + 3))
		{
		return kY2;
		}
	else
		{
		return kNoCursor;
		}
}

/******************************************************************************
 PWHandleMouseDown (protected)

 ******************************************************************************/

void
J2DPlotWidget::PWHandleMouseDown
	(
	const JPoint&	pt,
	const JSize 	clickCount
	)
{
	itsIsCursorDragging = kFalse;

	if (clickCount == 1)
		{
		itsSelectedCursor   = GetCursorIndex(pt);
		itsIsCursorDragging = JI2B( itsSelectedCursor != kNoCursor );

		JPainter* p = PWCreateDragInsidePainter();
		if (itsIsCursorDragging)
			{
			if ((itsSelectedCursor == kX1) || (itsSelectedCursor == kX2))
				{
				p->Line(pt.x, itsYAxisStart, pt.x, itsYAxisEnd);
				}
			else
				{
				p->Line(itsXAxisStart, pt.y, itsXAxisEnd, pt.y);
				}
			}
		else
			{
			p->Rect(JRect(pt, pt));
			}
		itsStartPt = itsPrevPt = pt;
		}
	else if (clickCount == 2)
		{
		JPainter* p = PWGetDragPainter();
		if (p != NULL)
			{
			PWDeleteDragPainter();
			}
		if (itsYLabelRect.Contains(pt))
			{
			ChangeLabels(kYLabel);
			}
		else if (itsXLabelRect.Contains(pt))
			{
			ChangeLabels(kXLabel);
			}
		else if (itsTitleRect.Contains(pt))
			{
			ChangeLabels(kTitle);
			}
		else if (itsXAxisRect.Contains(pt))
			{
			ChangeScale(kTrue);
			}
		else if (itsYAxisRect.Contains(pt))
			{
			ChangeScale(kFalse);
			}
		else if (itsShowLegend && itsLegendRect.Contains(pt))
			{
			JCoordinate yPos = itsLegendRect.top + 2 * itsCurrentStringHeight;
			JSize lines = itsLegendRect.height()/itsCurrentStringHeight - 2;
			JIndex index = 0;
			while ((yPos < itsLegendRect.bottom) && (index < lines))
				{
				index++;
				if (pt.y < yPos)
					{
					break;
					}
				yPos += itsCurrentStringHeight;
				}
			JIndex visIndex = 0;
			JSize items = itsCurves->GetElementCount();
			JIndex i;
			for (i = 1; i <= items; i++)
				{
				JCurveInfo info = itsCurvesInfo->GetElement(i);
				if (info.curve)
					{
					visIndex++;
					if (visIndex == index)
						{
						ChangeCurveOptions(i);
						break;
						}
					}
				}
			}
		}
}

/******************************************************************************
 PWHandleMouseDrag (protected)

 ******************************************************************************/

void
J2DPlotWidget::PWHandleMouseDrag
	(
	const JPoint& pt
	)
{
	JPainter* p = PWGetDragPainter();
	if (pt != itsPrevPt && p != NULL) // p is NULL for multiple click
		{
		const JPoint newPt =
			JPinInRect(pt, JRect(itsYAxisEnd,   itsXAxisStart,
								 itsYAxisStart, itsXAxisEnd));

		const JBoolean draggingX =
			JI2B((itsSelectedCursor == kX1) || (itsSelectedCursor == kX2));
		if (itsIsCursorDragging &&
			(( draggingX && newPt.x != itsPrevPt.x) ||
			 (!draggingX && newPt.y != itsPrevPt.y)))
			{
			if (draggingX)
				{
				p->Line(itsPrevPt.x, itsYAxisStart, itsPrevPt.x, itsYAxisEnd);
				p->Line(newPt.x, itsYAxisStart, newPt.x, itsYAxisEnd);
				}
			else
				{
				p->Line(itsXAxisStart, itsPrevPt.y, itsXAxisEnd, itsPrevPt.y);
				p->Line(itsXAxisStart, newPt.y, itsXAxisEnd, newPt.y);
				}

			if (itsSelectedCursor == kX1)
				{
				itsXCursorPos1 = newPt.x;
				}
			else if (itsSelectedCursor == kX2)
				{
				itsXCursorPos2 = newPt.x;
				}
			else if (itsSelectedCursor == kY1)
				{
				itsYCursorPos1 = newPt.y;
				}
			else if (itsSelectedCursor == kY2)
				{
				itsYCursorPos2 = newPt.y;
				}

			itsPrevPt = newPt;
			UpdateCursorInternals();
			PWRedrawRect(JRect(PWGetHeight() - itsCurrentCursorLabelHeight,
								0, PWGetHeight(), PWGetWidth()));
			}
		else if (!itsIsCursorDragging)
			{
			p->Rect(JRect(itsStartPt, itsPrevPt));
			p->Rect(JRect(itsStartPt, pt));
			itsPrevPt = pt;
			PWDisplayCursor(kZoomCursor);
			}
		}
}

/******************************************************************************
 PWHandleMouseUp (protected)

 ******************************************************************************/

void
J2DPlotWidget::PWHandleMouseUp
	(
	const JPoint&	pt,
	const JBoolean	cleanZoom
	)
{
	JPainter* p = PWGetDragPainter();
	if (p != NULL) // p is NULL for multiple click
		{
		if (itsIsCursorDragging)
			{
			if ((itsSelectedCursor == kX1) || (itsSelectedCursor == kX2))
				{
				p->Line(itsPrevPt.x, itsYAxisStart, itsPrevPt.x, itsYAxisEnd);
				}
			else
				{
				p->Line(itsXAxisStart, itsPrevPt.y, itsXAxisEnd, itsPrevPt.y);
				}
			PWDeleteDragPainter();
			PWRefresh();
			}
		else
			{
			JRect rect(itsStartPt, itsPrevPt);
			p->Rect(rect);
			PWDeleteDragPainter();

			if (itsPrevPt != itsStartPt)
				{
				Zoom(rect, cleanZoom);
				}
			}
		}
	itsIsCursorDragging = kFalse;
}

/******************************************************************************
 GetXMarkCount (public)

 ******************************************************************************/

JSize
J2DPlotWidget::GetXMarkCount()
	const
{
	return itsXMarks->GetElementCount();
}

/******************************************************************************
 GetYMarkCount (public)

 ******************************************************************************/

JSize
J2DPlotWidget::GetYMarkCount()
	const
{
	return itsYMarks->GetElementCount();
}

/******************************************************************************
 GetXMarkValue (public)

 ******************************************************************************/

JFloat
J2DPlotWidget::GetXMarkValue
	(
	const JIndex index
	)
	const
{
	assert(itsXMarks->IndexValid(index));
	return itsXMarks->GetElement(index);
}

/******************************************************************************
 GetYMarkValue (public)

 ******************************************************************************/

JFloat
J2DPlotWidget::GetYMarkValue
	(
	const JIndex index
	)
	const
{
	assert(itsYMarks->IndexValid(index));
	return itsYMarks->GetElement(index);
}

/******************************************************************************
 MarkCurrentCursor (public)

 ******************************************************************************/

void
J2DPlotWidget::MarkCurrentCursor()
{
	if (itsSelectedCursor == kX1)
		{
		itsXMarks->AppendElement(itsXCursorVal1);
		Broadcast(CursorMarked(kTrue, itsXCursorVal1));
		Broadcast(PlotChanged());
		}
	else if (itsSelectedCursor == kX2)
		{
		itsXMarks->AppendElement(itsXCursorVal2);
		Broadcast(CursorMarked(kTrue, itsXCursorVal2));
		Broadcast(PlotChanged());
		}
	else if (itsSelectedCursor == kY1)
		{
		itsYMarks->AppendElement(itsYCursorVal1);
		Broadcast(CursorMarked(kFalse, itsYCursorVal1));
		Broadcast(PlotChanged());
		}
	else if (itsSelectedCursor == kY2)
		{
		itsYMarks->AppendElement(itsYCursorVal2);
		Broadcast(CursorMarked(kFalse, itsYCursorVal2));
		Broadcast(PlotChanged());
		}
}

/******************************************************************************
 RemoveAllMarks (public)

 ******************************************************************************/

void
J2DPlotWidget::RemoveAllMarks()
{
	itsYMarks->RemoveAll();
	itsXMarks->RemoveAll();
	Broadcast(MarkRemoved(kFalse, 1));
	Broadcast(MarkRemoved(kTrue, 1));
	Broadcast(PlotChanged());
	PWRefresh();
}

/******************************************************************************
 RemoveXMark (public)

 ******************************************************************************/

void
J2DPlotWidget::RemoveXMark
	(
	const JIndex index
	)
{
	itsXMarks->RemoveElement(index);
	Broadcast(MarkRemoved(kTrue, index));
	Broadcast(PlotChanged());
}

/******************************************************************************
 RemoveYMark (public)

 ******************************************************************************/

void
J2DPlotWidget::RemoveYMark
	(
	const JIndex index
	)
{
	itsYMarks->RemoveElement(index);
	Broadcast(MarkRemoved(kFalse, index));
	Broadcast(PlotChanged());
}

/******************************************************************************
 ToggleXCursor (public)

 ******************************************************************************/

void
J2DPlotWidget::ToggleXCursor()
{
	if (itsXCursorVisible && (itsSelectedCursor == kX1 || itsSelectedCursor == kX2))
		{
		itsSelectedCursor = kNoCursor;
		}
	itsXCursorVisible = JNegate(itsXCursorVisible);
	UpdatePlot(kTrue);
	PWRefresh();
}

/******************************************************************************
 ToggleYCursor (public)

 ******************************************************************************/

void
J2DPlotWidget::ToggleYCursor()
{
	if (itsYCursorVisible && (itsSelectedCursor == kY1 || itsSelectedCursor == kY2))
		{
		itsSelectedCursor = kNoCursor;
		}
	itsYCursorVisible = JNegate(itsYCursorVisible);
	UpdatePlot(kTrue);
	PWRefresh();
}

/******************************************************************************
 DualCursorsVisible (public)

 ******************************************************************************/

JBoolean
J2DPlotWidget::DualCursorsVisible()
{
	return itsDualCursors;
}

/******************************************************************************
 ToggleDualCursors (public)

 ******************************************************************************/

void
J2DPlotWidget::ToggleDualCursors()
{
	if ((itsSelectedCursor == kX2) || (itsSelectedCursor == kY2))
		{
		itsSelectedCursor = kNoCursor;
		}
	itsDualCursors = JNegate(itsDualCursors);
	UpdatePlot(kTrue);
	PWRefresh();
}

/*******************************************************************************
 DrawCursors (private)

 ******************************************************************************/

void
J2DPlotWidget::DrawCursors
	(
	JPainter& p
	)
{
	JCoordinate xmin, xmax, ymin, ymax;
	GetFrameGeometry(&xmin, &xmax, &ymin, &ymax);

	if (itsXCursorVisible)
		{
		if (itsSelectedCursor == kX1)
			{
			p.SetPenColor(itsSelectionColor);
			}
		else
			{
			p.SetPenColor(itsBlackColor);
			}
		p.Line(itsXCursorPos1, ymin, itsXCursorPos1, ymax);
		if (itsDualCursors)
			{
			if (itsSelectedCursor == kX2)
				{
				p.SetPenColor(itsSelectionColor);
				}
			else
				{
				p.SetPenColor(itsBlackColor);
				}
			p.Line(itsXCursorPos2, ymin, itsXCursorPos2, ymax);
			}
		}
	if (itsYCursorVisible)
		{
		if (itsSelectedCursor == kY1)
			{
			p.SetPenColor(itsSelectionColor);
			}
		else
			{
			p.SetPenColor(itsBlackColor);
			}
		p.Line(xmin, itsYCursorPos1, xmax, itsYCursorPos1);
		if (itsDualCursors)
			{
			if (itsSelectedCursor == kY2)
				{
				p.SetPenColor(itsSelectionColor);
				}
			else
				{
				p.SetPenColor(itsBlackColor);
				}
			p.Line(xmin, itsYCursorPos2, xmax, itsYCursorPos2);
			}
		}
}

/*******************************************************************************
 DrawCursorLabels (private)

 ******************************************************************************/

void
J2DPlotWidget::DrawCursorLabels
	(
	JPainter& p
	)
{
	if (!itsXCursorVisible && !itsYCursorVisible)
		{
		return;
		}
	JSize labelHeight 	= p.GetLineHeight();
	JSize cursorHeight	= GetCursorLabelHeight(p);
	JSize strwidth		= p.GetStringWidth("x1 = 0000000000");
	JSize totalWidth 	= 3 * strwidth + 2 * kCursorValueBuffer;
	JSize axisWidth		= itsXAxisEnd - itsXAxisStart;
	JCoordinate startx	= itsXAxisStart;

	if (((itsXCursorVisible && !itsYCursorVisible) ||
		 (!itsXCursorVisible && itsYCursorVisible)) && !itsDualCursors)
		{
		totalWidth = strwidth;
		}
	else if (itsXCursorVisible && itsYCursorVisible && !itsDualCursors)
		{
		totalWidth = 2 * strwidth + kCursorValueBuffer;
		}

	if (axisWidth > totalWidth)
		{
		startx += (axisWidth - totalWidth)/2;
		}

	JCoordinate starty = PWGetHeight() - cursorHeight + kCursorValueBuffer;

	if (itsXCursorVisible)
		{
		const JString x1 = "x1 = " + JString(itsXCursorVal1);
		p.String(startx,
				starty, x1, strwidth, JPainter::kHAlignLeft, labelHeight,
				JPainter::kVAlignCenter);

		if (itsDualCursors)
			{
			const JString x2 = "x2 = " + JString(itsXCursorVal2);
			const JString dx = "dx = " + JString(itsXCursorVal2 - itsXCursorVal1);
			p.String(startx + kCursorValueBuffer + strwidth,
					starty, x2, strwidth, JPainter::kHAlignLeft, labelHeight,
					JPainter::kVAlignCenter);
			p.String(startx + 2 * kCursorValueBuffer + 2 * strwidth,
					starty, dx, strwidth, JPainter::kHAlignLeft, labelHeight,
					JPainter::kVAlignCenter);
			}
		}

	if (itsYCursorVisible)
		{
		const JString y1 = "y1 = " + JString(itsYCursorVal1);

		if (itsDualCursors)
			{
			if (itsXCursorVisible)
				{
				starty += labelHeight;
				}
			const JString y2 = "y2 = " + JString(itsYCursorVal2);
			const JString dy = "dy = " + JString(itsYCursorVal2 - itsYCursorVal1);
			p.String(startx,
					starty, y1, strwidth, JPainter::kHAlignLeft, labelHeight,
					JPainter::kVAlignCenter);
			p.String(startx + kCursorValueBuffer + strwidth,
					starty, y2, strwidth, JPainter::kHAlignLeft, labelHeight,
					JPainter::kVAlignCenter);
			p.String(startx + 2 * kCursorValueBuffer + 2 * strwidth,
					starty, dy, strwidth, JPainter::kHAlignLeft, labelHeight,
					JPainter::kVAlignCenter);
			}
		else if (itsXCursorVisible)
			{
			p.String(startx + kCursorValueBuffer + strwidth,
					starty, y1, strwidth, JPainter::kHAlignLeft, labelHeight,
					JPainter::kVAlignCenter);
			}
		else
			{
			p.String(startx,
					starty, y1, strwidth, JPainter::kHAlignLeft, labelHeight,
					JPainter::kVAlignCenter);
			}
		}

}

/*******************************************************************************
 DrawMarks (private)

 ******************************************************************************/

void
J2DPlotWidget::DrawMarks
	(
	JPainter& p
	)
{
	p.SetPenColor(itsGrayColor);
	JCoordinate xmin, xmax, ymin, ymax;
	GetFrameGeometry(&xmin, &xmax, &ymin, &ymax);
	JSize i;
	for (i = 1; i <= itsXMarks->GetElementCount(); i++)
		{
		JFloat value = itsXMarks->GetElement(i);
		JCoordinate x;
		if (GetFrameX(value, &x))
			{
			p.Line(x, ymin, x, ymax);
			}
		}
	for (i = 1; i <= itsYMarks->GetElementCount(); i++)
		{
		JFloat value = itsYMarks->GetElement(i);
		JCoordinate y;
		if (GetFrameY(value, &y))
			{
			p.Line(xmin, y, xmax, y);
			}
		}
	p.SetPenColor(itsBlackColor);
}

/******************************************************************************
 PWHandleKeyPress (protected)

 ******************************************************************************/

// x visible, y visible, dual visible, reverse, current cursor

static J2DPlotWidget::CursorIndex kNextCursor[2][2][2][2][5] =
{
	{
		{
			{	// x=0, y=0, dual=0
				{ J2DPlotWidget::kNoCursor, J2DPlotWidget::kNoCursor, J2DPlotWidget::kNoCursor, J2DPlotWidget::kNoCursor, J2DPlotWidget::kNoCursor },
				{ J2DPlotWidget::kNoCursor, J2DPlotWidget::kNoCursor, J2DPlotWidget::kNoCursor, J2DPlotWidget::kNoCursor, J2DPlotWidget::kNoCursor }
			},
			{	// x=0, y=0, dual=1
				{ J2DPlotWidget::kNoCursor, J2DPlotWidget::kNoCursor, J2DPlotWidget::kNoCursor, J2DPlotWidget::kNoCursor, J2DPlotWidget::kNoCursor },
				{ J2DPlotWidget::kNoCursor, J2DPlotWidget::kNoCursor, J2DPlotWidget::kNoCursor, J2DPlotWidget::kNoCursor, J2DPlotWidget::kNoCursor }
			}
		},
		{
			{	// x=0, y=1, dual=0
				{ J2DPlotWidget::kY1, J2DPlotWidget::kY1, J2DPlotWidget::kY1, J2DPlotWidget::kY1, J2DPlotWidget::kY1 },
				{ J2DPlotWidget::kY1, J2DPlotWidget::kY1, J2DPlotWidget::kY1, J2DPlotWidget::kY1, J2DPlotWidget::kY1 }
			},
			{	// x=0, y=1, dual=1
				{ J2DPlotWidget::kY1, J2DPlotWidget::kY1, J2DPlotWidget::kY1, J2DPlotWidget::kY2, J2DPlotWidget::kY1 },
				{ J2DPlotWidget::kY2, J2DPlotWidget::kY2, J2DPlotWidget::kY2, J2DPlotWidget::kY2, J2DPlotWidget::kY1 }
			}
		}
	},
	{
		{
			{	// x=1, y=0, dual=0
				{ J2DPlotWidget::kX1, J2DPlotWidget::kX1, J2DPlotWidget::kX1, J2DPlotWidget::kX1, J2DPlotWidget::kX1 },
				{ J2DPlotWidget::kX1, J2DPlotWidget::kX1, J2DPlotWidget::kX1, J2DPlotWidget::kX1, J2DPlotWidget::kX1 }
			},
			{	// x=1, y=0, dual=1
				{ J2DPlotWidget::kX1, J2DPlotWidget::kX2, J2DPlotWidget::kX1, J2DPlotWidget::kX1, J2DPlotWidget::kX1 },
				{ J2DPlotWidget::kX2, J2DPlotWidget::kX2, J2DPlotWidget::kX1, J2DPlotWidget::kX2, J2DPlotWidget::kX2 }
			}
		},
		{
			{	// x=1, y=1, dual=0
				{ J2DPlotWidget::kX1, J2DPlotWidget::kY1, J2DPlotWidget::kY1, J2DPlotWidget::kX1, J2DPlotWidget::kX1 },
				{ J2DPlotWidget::kY1, J2DPlotWidget::kY1, J2DPlotWidget::kY1, J2DPlotWidget::kX1, J2DPlotWidget::kX1 }
			},
			{	// x=1, y=1, dual=1
				{ J2DPlotWidget::kX1, J2DPlotWidget::kX2, J2DPlotWidget::kY1, J2DPlotWidget::kY2, J2DPlotWidget::kX1 },
				{ J2DPlotWidget::kY2, J2DPlotWidget::kY2, J2DPlotWidget::kX1, J2DPlotWidget::kX2, J2DPlotWidget::kY1 }
			}
		}
	}
};

void
J2DPlotWidget::PWHandleKeyPress
	(
	const int		key,
	const JBoolean 	reverse,
	const JBoolean 	interval,
	const JBoolean 	skip
	)
{
	if (key == kJTabKey)
		{
		const CursorIndex newCursor = kNextCursor
			[ itsXCursorVisible ? 1 : 0 ]
			[ itsYCursorVisible ? 1 : 0 ]
			[ itsDualCursors    ? 1 : 0 ]
			[ reverse           ? 1 : 0 ]
			[ itsSelectedCursor         ];
		if (newCursor != itsSelectedCursor)
			{
			itsSelectedCursor = newCursor;
			PWRefresh();
			}
		}

	else if (key == kJLeftArrow &&
			 (itsSelectedCursor == kX1 || itsSelectedCursor == kX2))
		{
		if (MoveCursor(itsXAxisStart, itsXAxisEnd,
					   -(interval ? 10 : 1),
					   (itsSelectedCursor == kX1 ?
							(&itsXCursorPos1) : (&itsXCursorPos2))))
			{
			UpdateCursorInternals();
			PWRefresh();
			}
		}
	else if (key == kJRightArrow &&
			 (itsSelectedCursor == kX1 || itsSelectedCursor == kX2))
		{
		if (MoveCursor(itsXAxisStart, itsXAxisEnd,
					   (interval ? 10 : 1),
					   (itsSelectedCursor == kX1 ?
							(&itsXCursorPos1) : (&itsXCursorPos2))))
			{
			UpdateCursorInternals();
			PWRefresh();
			}
		}

	else if (key == kJUpArrow &&
			 (itsSelectedCursor == kY1 || itsSelectedCursor == kY2))
		{
		if (MoveCursor(itsYAxisEnd, itsYAxisStart,
					   -(interval ? 10 : 1),
					   (itsSelectedCursor == kY1 ?
							(&itsYCursorPos1) : (&itsYCursorPos2))))
			{
			UpdateCursorInternals();
			PWRefresh();
			}
		}
	else if (key == kJDownArrow &&
			 (itsSelectedCursor == kY1 || itsSelectedCursor == kY2))
		{
		if (MoveCursor(itsYAxisEnd, itsYAxisStart,
					   (interval ? 10 : 1),
					   (itsSelectedCursor == kY1 ?
							(&itsYCursorPos1) : (&itsYCursorPos2))))
			{
			UpdateCursorInternals();
			PWRefresh();
			}
		}

	else if (key == kJEscapeKey)
		{
		itsSelectedCursor = kNoCursor;
		PWRefresh();
		}
}

/*******************************************************************************
 MoveCursor (private)

	Returns kTrue if *cursorPos changed.

 ******************************************************************************/

JBoolean
J2DPlotWidget::MoveCursor
	(
	const JCoordinate	min,
	const JCoordinate	max,
	const JCoordinate	delta,
	JCoordinate*		cursorPos
	)
	const
{
	JCoordinate newPos = *cursorPos + delta;
	if (newPos < min)
		{
		newPos = min;
		}
	else if (newPos > max)
		{
		newPos = max;
		}

	if (*cursorPos != newPos)
		{
		*cursorPos = newPos;
		return kTrue;
		}
	else
		{
		return kFalse;
		}
}

/*******************************************************************************
 AdjustCursors


 ******************************************************************************/

void
J2DPlotWidget::AdjustCursors()
{
	JCoordinate xmin, xmax, ymin, ymax;
	GetFrameGeometry(&xmin, &xmax, &ymin, &ymax);
	if ((xmin == xmax) || (ymin == ymax))
		{
		return;
		}
	JCoordinate pos;
	if (GetFrameX(itsXCursorVal1, &pos) && itsIsInited)
		{
		itsXCursorPos1 = pos;
		}
	else if ((itsXCursorPos1 < xmin) || (itsXCursorPos1 > xmax))
		{
		itsXCursorPos1 = xmin + 20;
		}
	if (GetFrameX(itsXCursorVal2, &pos) && itsIsInited)
		{
		itsXCursorPos2 = pos;
		}
	else if ((itsXCursorPos2 < xmin) || (itsXCursorPos2 > xmax))
		{
		itsXCursorPos2 = xmin + 40;
		}
	if (GetFrameY(itsYCursorVal1, &pos) && itsIsInited)
		{
		itsYCursorPos1 = pos;
		}
	else if ((itsYCursorPos1 < ymax) || (itsYCursorPos1 > ymin))
		{
		itsYCursorPos1 = ymin - 20;
		}
	if (GetFrameY(itsYCursorVal2, &pos) && itsIsInited)
		{
		itsYCursorPos2 = pos;
		}
	else if ((itsYCursorPos2 < ymax) || (itsYCursorPos2 > ymin))
		{
		itsYCursorPos2 = ymin - 40;
		}
	itsXCursorVal1 = GetRealX(itsXCursorPos1);
	itsXCursorVal2 = GetRealX(itsXCursorPos2);
	itsYCursorVal1 = GetRealY(itsYCursorPos1);
	itsYCursorVal2 = GetRealY(itsYCursorPos2);
	itsIsInited = kTrue;
}

/*******************************************************************************
 UpdateCursorInternals (private)

 ******************************************************************************/

void
J2DPlotWidget::UpdateCursorInternals()
{
	itsXCursorVal1 = GetRealX(itsXCursorPos1);
	itsXCursorVal2 = GetRealX(itsXCursorPos2);
	itsYCursorVal1 = GetRealY(itsYCursorPos1);
	itsYCursorVal2 = GetRealY(itsYCursorPos2);
	Broadcast(CursorsChanged(	itsXCursorVisible,
								itsYCursorVisible,
								itsDualCursors,
								itsXCursorVal1,
								itsXCursorVal2,
								itsYCursorVal1,
								itsYCursorVal2));
	Broadcast(PlotChanged());
}

/******************************************************************************
 GetCursorLabelHeight (private)

 ******************************************************************************/

JSize
J2DPlotWidget::GetCursorLabelHeight
	(
	JPainter& p
	)
{
	JSize labelHeight 			= p.GetLineHeight();
	itsCurrentCursorLabelHeight = 0;
	if (itsXCursorVisible || itsYCursorVisible)
		{
		if (itsDualCursors && itsXCursorVisible && itsYCursorVisible)
			{
			itsCurrentCursorLabelHeight = 2 * (labelHeight + kCursorValueBuffer);
			}
		else
			{
			itsCurrentCursorLabelHeight = labelHeight + 2 * kCursorValueBuffer;
			}
		}
	return itsCurrentCursorLabelHeight;
}

/******************************************************************************
 SetCurveInfoArray (protected)

 ******************************************************************************/

void
J2DPlotWidget::SetCurveInfoArray
	(
	const JArray<JCurveInfo>& infoArray
	)
{
	assert(infoArray.GetElementCount() == itsCurvesInfo->GetElementCount());
	const JSize count = itsCurvesInfo->GetElementCount();
	for (JSize i = 1; i <= count; i++)
		{
		JCurveInfo info = infoArray.GetElement(i);
		info.name = new JString(*info.name);
		assert(info.name != NULL);
		itsCurvesInfo->SetElement(i, info);
		}
	UpdatePlot(kTrue);
}

/******************************************************************************
 GetLogAxisString (private)

 ******************************************************************************/

void
J2DPlotWidget::GetLogAxisString
	(
	JFloat 		value,
	JString* 	str
	)
{
	JFloat exponent = log10(value);
	JBoolean nonint = kFalse;
	if (JRound(exponent)/exponent - 1 > 0.0001)
		{
		nonint = kTrue;
		}
	if ( (exponent > 3) || (exponent < -3 ) )
		{
		*str = JString(value, 0, JString::kForceExponent);
		}
	else
		{
		int prec;
		if (exponent < 0)
			{
			prec = (int)fabs(exponent);
			}
		else
			{
			prec = 0;
			}
		if (nonint)
			{
			prec++;
			}
		*str = JString(value, prec);
		}

}

/******************************************************************************
 GetLogTicks (private)

 ******************************************************************************/

void
J2DPlotWidget::GetLogTicks
	(
	JFloat 			min,
	JFloat 			max,
	JFloat 			inc,
	JArray<JFloat>* major,
	JArray<JFloat>* minor
	)
{
	JFloat rStart		= pow(10, min);
	JFloat rEnd			= pow(10, max);
	JFloat tStart		= floor(min);
	JFloat tEnd 		= ceil(max);

	for (JFloat i = tStart; i <= tEnd; i += inc)
		{
		JFloat pIndex = pow(10, i);
		for (JFloat index = 1; index <= 9; index++)
			{
			JFloat value = pIndex * index;
			if ((value >= rStart) &&
				(value <= rEnd))
				{
				if (index == 1)
					{
					major->AppendElement(value);
					}
				else if (inc == 1)
					{
					minor->AppendElement(value);
					}
				}
			}
		}
}

#define JTemplateType JCurveInfo
#include <JArray.tmpls>
#undef JTemplateType
