
///////////////////////////////////////////////////////////
//                                                       //
//                         SAGA                          //
//                                                       //
//      System for Automated Geoscientific Analyses      //
//                                                       //
//                    User Interface                     //
//                                                       //
//                    Program: SAGA                      //
//                                                       //
//-------------------------------------------------------//
//                                                       //
//                 VIEW_ScatterPlot.cpp                  //
//                                                       //
//          Copyright (C) 2005 by Olaf Conrad            //
//                                                       //
//-------------------------------------------------------//
//                                                       //
// This file is part of 'SAGA - System for Automated     //
// Geoscientific Analyses'. SAGA is free software; you   //
// can redistribute it and/or modify it under the terms  //
// of the GNU General Public License as published by the //
// Free Software Foundation, either version 2 of the     //
// License, or (at your option) any later version.       //
//                                                       //
// SAGA is distributed in the hope that it will be       //
// useful, but WITHOUT ANY WARRANTY; without even the    //
// implied warranty of MERCHANTABILITY or FITNESS FOR A  //
// PARTICULAR PURPOSE. See the GNU General Public        //
// License for more details.                             //
//                                                       //
// You should have received a copy of the GNU General    //
// Public License along with this program; if not, see   //
// <http://www.gnu.org/licenses/>.                       //
//                                                       //
//-------------------------------------------------------//
//                                                       //
//    contact:    Olaf Conrad                            //
//                Institute of Geography                 //
//                University of Goettingen               //
//                Goldschmidtstr. 5                      //
//                37077 Goettingen                       //
//                Germany                                //
//                                                       //
//    e-mail:     oconrad@saga-gis.org                   //
//                                                       //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
#include <wx/window.h>

#include <wx/clipbrd.h>

#include <saga_api/saga_api.h>
#include <saga_gdi/sgdi_helper.h>

#include "res_commands.h"
#include "res_controls.h"
#include "res_images.h"
#include "res_dialogs.h"

#include "helper.h"

#include "wksp.h"
#include "wksp_data_manager.h"

#include "wksp_grid.h"
#include "wksp_shapes.h"

#include "view_scatterplot.h"


///////////////////////////////////////////////////////////
//														 //
//														 //
//														 //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
#define CHECK_DATA(pData)	if( pData != NULL && !SG_Get_Data_Manager().Exists(pData) )	{	pData	= NULL;	}


///////////////////////////////////////////////////////////
//														 //
//														 //
//														 //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
int Scatter_Plot_On_Parameter_Changed(CSG_Parameter *pParameter, int Flags)
{
	CSG_Parameters *pParameters = pParameter ? pParameter->Get_Parameters() : NULL;

	if( pParameters )
	{
		if( Flags & PARAMETER_CHECK_ENABLE )
		{
			if( pParameter->Cmp_Identifier("CMP_WITH") )
			{
				pParameters->Get_Parameter("GRID")->Get_Parent()->Set_Enabled(pParameter->asInt() == 0);
				pParameters->Set_Enabled("GRID"       , pParameter->asInt() == 0);
				pParameters->Set_Enabled("POINTS"     , pParameter->asInt() == 1);
			}

			if( pParameter->Cmp_Identifier("REG_SHOW") )
			{
				pParameters->Set_Enabled("REG_FORMULA", pParameter->asBool());
				pParameters->Set_Enabled("REG_COLOR"  , pParameter->asBool());
				pParameters->Set_Enabled("REG_SIZE"   , pParameter->asBool());
				pParameters->Set_Enabled("REG_INFO"   , pParameter->asBool());
			}

			if( pParameter->Cmp_Identifier("DISPLAY") )
			{
				pParameters->Set_Enabled("DENSITY_RES", pParameter->asInt() == 1);
				pParameters->Set_Enabled("DENSITY_PAL", pParameter->asInt() == 1);
				pParameters->Set_Enabled("DENSITY_LEG", pParameter->asInt() == 1);
			}

			if( pParameter->Cmp_Identifier("X_ADJUST") )
			{
				pParameters->Set_Enabled("X_RANGE", pParameter->asInt() == 1);
			}

			if( pParameter->Cmp_Identifier("Y_ADJUST") )
			{
				pParameters->Set_Enabled("Y_RANGE", pParameter->asInt() == 1);
			}

			if( pParameter->Cmp_Identifier("Z_ADJUST") )
			{
				pParameters->Set_Enabled("Z_RANGE", pParameter->asInt() == 1);
			}
		}

		if( Flags & PARAMETER_CHECK_VALUES )
		{
			if( pParameter->Cmp_Identifier("CMP_WITH") )
			{
			//	pParameters->Get_Parameter("OPTIONS")->asParameters()->
			//	             Get_Parameter("DISPLAY")->Set_Value(pParameter->asInt() == 0 ? 1 : 0);
				pParameters->Get_Parameter("DISPLAY")->Set_Value(pParameter->asInt() == 0 ? 1 : 0);

				pParameters->Set_Enabled("DENSITY_RES", pParameter->asInt() == 0);
				pParameters->Set_Enabled("DENSITY_PAL", pParameter->asInt() == 0);
				pParameters->Set_Enabled("DENSITY_LEG", pParameter->asInt() == 0);
				pParameters->Set_Enabled("Z_RANGE"    , pParameter->asInt() == 0);
			}

			if( pParameter->Cmp_Identifier("GRID") && pParameter->asGrid() )
			{
				pParameters->Set_Parameter("Y_RANGE.MIN", pParameter->asGrid()->Get_Min());
				pParameters->Set_Parameter("Y_RANGE.MAX", pParameter->asGrid()->Get_Max());
			}

			if( pParameter->Cmp_Identifier("POINTS") || pParameter->Cmp_Identifier("FIELD") )
			{
				CSG_Table *pTable = (*pParameters)("POINTS")->asTable(); int Field = (*pParameters)("FIELD")->asInt();

				if( pTable && Field >= 0 )
				{
					pParameters->Set_Parameter("Y_RANGE.MIN", pTable->Get_Minimum(Field));
					pParameters->Set_Parameter("Y_RANGE.MAX", pTable->Get_Maximum(Field));
				}
			}
		}

		return( 1 );
	}

	return( 0 );
}


///////////////////////////////////////////////////////////
//														 //
//														 //
//														 //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
IMPLEMENT_CLASS(CVIEW_ScatterPlot, CVIEW_Base);

//---------------------------------------------------------
BEGIN_EVENT_TABLE(CVIEW_ScatterPlot, CVIEW_Base)
	EVT_PAINT(CVIEW_ScatterPlot::On_Paint)
	EVT_SIZE (CVIEW_ScatterPlot::On_Size)

	EVT_MENU (ID_CMD_SCATTERPLOT_PARAMETERS, CVIEW_ScatterPlot::On_Parameters)
	EVT_MENU (ID_CMD_SCATTERPLOT_UPDATE    , CVIEW_ScatterPlot::On_Update)
	EVT_MENU (ID_CMD_SCATTERPLOT_AS_TABLE  , CVIEW_ScatterPlot::On_AsTable)
	EVT_MENU (ID_CMD_VIEW_CLIPBOARD_COPY   , CVIEW_ScatterPlot::On_ToClipboard)
END_EVENT_TABLE()


///////////////////////////////////////////////////////////
//														 //
//														 //
//														 //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
CVIEW_ScatterPlot::CVIEW_ScatterPlot(CWKSP_Data_Item *pItem)
	: CVIEW_Base(pItem, ID_VIEW_SCATTERPLOT, wxString::Format("%s | %s", _TL("Scatterplot"), pItem->Get_Object()->Get_Name()), ID_IMG_WND_SCATTERPLOT, false)
{
	m_pItem  = pItem;

	m_pGrid  = NULL;
	m_pGrids = NULL;
	m_pTable = NULL;

	switch( m_pItem->Get_Type() )
	{
	case WKSP_ITEM_Grid      : m_pGrid  = pItem->Get_Object()->asGrid      (); break;
	case WKSP_ITEM_Grids     : m_pGrids = pItem->Get_Object()->asGrids     (); break;
	case WKSP_ITEM_Shapes    : m_pTable = pItem->Get_Object()->asShapes    (); break;
	case WKSP_ITEM_PointCloud: m_pTable = pItem->Get_Object()->asPointCloud(); break;
	default                  : m_pTable = pItem->Get_Object()->asTable     (); break;
	}

	m_Parameters.Set_Name(CSG_String::Format("%s: %s", _TL("Scatterplot"), m_pItem->Get_Object()->Get_Name()));

	_On_Construction();

	if( DLG_Parameters(&m_Parameters) )
	{
        SG_UI_Process_Set_Busy(true);

		_Update_Data();

		Do_Show();

        SG_UI_Process_Set_Busy(false);
	}
	else
	{
		Destroy();
	}
}


///////////////////////////////////////////////////////////
//														 //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
void CVIEW_ScatterPlot::Do_Update(void)
{
	_Update_Data();
}


///////////////////////////////////////////////////////////
//														 //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
wxMenu * CVIEW_ScatterPlot::_Create_Menu(void)
{
	wxMenu *pMenu = new wxMenu;

	CMD_Menu_Add_Item(pMenu, false, ID_CMD_SCATTERPLOT_PARAMETERS);
	CMD_Menu_Add_Item(pMenu, false, ID_CMD_SCATTERPLOT_UPDATE);
	CMD_Menu_Add_Item(pMenu, false, ID_CMD_SCATTERPLOT_AS_TABLE);
	CMD_Menu_Add_Item(pMenu, false, ID_CMD_VIEW_CLIPBOARD_COPY);

	return( pMenu );
}

//---------------------------------------------------------
wxToolBarBase * CVIEW_ScatterPlot::_Create_ToolBar(void)
{
	wxToolBarBase *pToolBar = CMD_ToolBar_Create(ID_TB_VIEW_SCATTERPLOT);

	CMD_ToolBar_Add_Item(pToolBar, false, ID_CMD_SCATTERPLOT_PARAMETERS);
	CMD_ToolBar_Add_Item(pToolBar, false, ID_CMD_SCATTERPLOT_UPDATE);
	CMD_ToolBar_Add_Item(pToolBar, false, ID_CMD_SCATTERPLOT_AS_TABLE);
	CMD_ToolBar_Add_Item(pToolBar, false, ID_CMD_VIEW_CLIPBOARD_COPY);

	CMD_ToolBar_Add(pToolBar, _TL("Scatterplot"));

	return( pToolBar );
}


///////////////////////////////////////////////////////////
//														 //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
//#define m_Options (*m_Parameters("OPTIONS")->asParameters())
#define m_Options m_Parameters

//---------------------------------------------------------
void CVIEW_ScatterPlot::_On_Construction(void)
{
	double Range[2] = { 0., 0. };

	if( m_pGrid )
	{
		m_Parameters.Add_Grid("",
			"GRID"      , _TL("Grid"),
			_TL(""),
			PARAMETER_INPUT, false
		);

		m_Parameters.Add_Shapes("",
			"POINTS"    , _TL("Points"),
			_TL(""),
			PARAMETER_INPUT, SHAPE_TYPE_Point
		);

		m_Parameters.Add_Table_Field("POINTS",
			"FIELD"     , _TL("Attribute"),
			_TL("")
		);

		m_Parameters.Add_Choice("",
			"CMP_WITH"  , _TL("Compare with..."),
			_TL(""),
			CSG_String::Format("%s|%s|",
				_TL("another grid"),
				_TL("points")
			), 0
		);

		m_Parameters.Add_Choice("POINTS",
			"RESAMPLING", _TL("Resampling"),
			_TL(""),
			CSG_String::Format("%s|%s|%s|%s",
				_TL("Nearest Neighbour"),
				_TL("Bilinear Interpolation"),
				_TL("Bicubic Spline Interpolation"),
				_TL("B-Spline Interpolation")
			), 3
		);

		Range[0] = m_pGrid->Get_Min(); Range[1] = m_pGrid->Get_Max();
	}
	else if( m_pGrids )
	{
		CSG_String sChoices;

		for(int i=0; i<m_pGrids->Get_Grid_Count(); i++)
		{
			sChoices.Append(m_pGrids->Get_Grid_Name(i, SG_GRIDS_NAME_GRID) + "|");
		}

		m_Parameters.Add_Choice("", "BAND_X", "X", _TL(""), sChoices, 0);
		m_Parameters.Add_Choice("", "BAND_Y", "Y", _TL(""), sChoices, 1);

		Range[0] = m_pGrids->Get_Min(); Range[1] = m_pGrids->Get_Max();
	}
	else if( m_pTable )
	{
		CSG_String sChoices;

		for(int i=0; i<m_pTable->Get_Field_Count(); i++)
		{
			sChoices.Append(CSG_String::Format("%s|", m_pTable->Get_Field_Name(i)));
		}

		m_Parameters.Add_Choice("", "FIELD_X", "X", _TL(""), sChoices, 0);
		m_Parameters.Add_Choice("", "FIELD_Y", "Y", _TL(""), sChoices, 1);
	}

	//-----------------------------------------------------
//	m_Parameters.Add_Parameters("", "OPTIONS", _TL("Options"), _TL(""));

	m_Options.Add_Int("",
		"SAMPLES_MAX", _TL("Maximum Number of Samples"),
		_TL(""),
		100000, 0, true
	);

	m_Options.Add_Choice("",
		"X_ADJUST"   , _TL("X Range"),
		_TL(""),
		CSG_String::Format("%s|%s",
			_TL("auto-adjust"),
			_TL("adjust manually")
		), 0
	);

	m_Options.Add_Range("X_ADJUST",
		"X_RANGE"    , _TL("Value Bounds"),
		_TL(""),
		Range[0], Range[1]
	);

	m_Options.Add_Choice("",
		"Y_ADJUST"   , _TL("Y Range"),
		_TL(""),
		CSG_String::Format("%s|%s",
			_TL("auto-adjust"),
			_TL("adjust manually")
		), 0
	);

	m_Options.Add_Range("Y_ADJUST",
		"Y_RANGE"    , _TL("Value Bounds"),
		_TL(""),
		Range[0], Range[1]
	);

	//-----------------------------------------------------
	m_Options.Add_Bool("",
		"REG_SHOW"   , _TL("Show Regression"),
		_TL(""),
		true
	);

	m_Options.Add_String("REG_SHOW",
		"REG_FORMULA" , _TL("Regression Formula"),
		_TL(""),
		"a + b * x"
	);

	m_Options.Add_Color("REG_SHOW",
		"REG_COLOR"   , _TL("Line Colour"),
		_TL(""),
		SG_COLOR_RED
	);

	m_Options.Add_Int("REG_SHOW",
		"REG_SIZE"    , _TL("Line Size"),
		_TL(""),
		0, 0, true
	);

	m_Options.Add_Info_String("REG_SHOW",
		"REG_INFO"    , _TL("Regression Details"),
		_TL(""),
		_TL(""), true
	);

	m_Options.Add_Choice("",
		"DISPLAY"     , _TL("Display Type"),
		_TL(""),
		CSG_String::Format("%s|%s",
			_TL("Points"),
			_TL("Density")
		), m_pGrid || m_pGrids ? 1 : 0
	);

	m_Options.Add_Int("DISPLAY",
		"DENSITY_RES", _TL("Display Resolution"),
		_TL(""),
		50, 10, true
	);

	m_Options.Add_Colors("DISPLAY",
		"DENSITY_PAL", _TL("Colors"),
		_TL(""),
		SG_COLORS_RAINBOW, 7
	);

	m_Options.Add_Bool("DISPLAY",
		"DENSITY_LEG", _TL("Show Legend"),
		_TL(""),
		true
	);

	m_Options.Add_Choice("DISPLAY",
		"Z_ADJUST"   , _TL("Range"),
		_TL(""),
		CSG_String::Format("%s|%s",
			_TL("auto-adjust"),
			_TL("adjust manually")
		), 0
	);

	m_Options.Add_Range("Z_ADJUST",
		"Z_RANGE"    , _TL("Value Bounds"),
		_TL(""),
		0, 100, 0., true
	);

	m_Options.Add_Choice("",
		"COLOR_MODE"  , _TL("Color Mode"),
		_TL(""),
		CSG_String::Format("%s|%s|%s", _TL("system"), _TL("light"), _TL("dark"))
	);

	//-----------------------------------------------------
	m_Parameters.Set_Callback_On_Parameter_Changed(&Scatter_Plot_On_Parameter_Changed);
}


///////////////////////////////////////////////////////////
//														 //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
void CVIEW_ScatterPlot::On_Parameters(wxCommandEvent &event)
{
	if( DLG_Parameters(&m_Parameters) )
	{
		SetBackgroundColour(SYS_Get_Color_Background(m_Options("COLOR_MODE")->asInt()));

		_Update_Data();
	}
}

//---------------------------------------------------------
void CVIEW_ScatterPlot::On_Update(wxCommandEvent &event)
{
	_Update_Data();
}

//---------------------------------------------------------
void CVIEW_ScatterPlot::On_AsTable(wxCommandEvent &event)
{
	if( m_Trend.Get_Data_Count() > 1 )
	{
		CSG_Table *pTable = SG_Create_Table();

		pTable->Fmt_Name("%s: [%s]-[%s]", _TL("Scatterplot"), m_sX.c_str(), m_sY.c_str());

		pTable->Add_Field("ID", SG_DATATYPE_Int   );
		pTable->Add_Field(m_sX, SG_DATATYPE_Double);
		pTable->Add_Field(m_sY, SG_DATATYPE_Double);

		for(int i=0; i<m_Trend.Get_Data_Count() && PROGRESSBAR_Set_Position(i, m_Trend.Get_Data_Count()); i++)
		{
			CSG_Table_Record *pRecord = pTable->Add_Record();

			pRecord->Set_Value(0, i + 1);
			pRecord->Set_Value(1, m_Trend.Get_Data_X(i));
			pRecord->Set_Value(2, m_Trend.Get_Data_Y(i));
		}

		PROGRESSBAR_Set_Ready();

		g_pData->Add(pTable);
	}
}

//---------------------------------------------------------
void CVIEW_ScatterPlot::On_ToClipboard(wxCommandEvent &event)
{
	wxBitmap BMP(GetSize()); wxMemoryDC dc;
	
	dc.SelectObject(BMP);
	dc.SetBackground(SYS_Get_Color_Background(m_Options("COLOR_MODE")->asInt()));
	dc.Clear();

	_Draw(dc, wxRect(BMP.GetSize()));

	dc.SelectObject(wxNullBitmap);

	if( wxTheClipboard->Open() )
	{
		wxBitmapDataObject *pBMP = new wxBitmapDataObject;
		pBMP->SetBitmap(BMP);
		wxTheClipboard->SetData(pBMP);
		wxTheClipboard->Close();
	}
}


///////////////////////////////////////////////////////////
//														 //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
void CVIEW_ScatterPlot::On_Size(wxSizeEvent &event)
{
	Refresh();

	event.Skip();
}


///////////////////////////////////////////////////////////
//														 //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
#define GET_DC_X(x)	(r.GetLeft  () + (int)(0.5 + dx * ((x) - m_xRange[0])))
#define GET_DC_Y(y)	(r.GetBottom() - (int)(0.5 + dy * ((y) - m_yRange[0])))

//---------------------------------------------------------
void CVIEW_ScatterPlot::On_Paint(wxPaintEvent &event)
{
	wxPaintDC dc(this); wxRect r(wxPoint(0, 0), GetClientSize());

	dc.SetBackground(SYS_Get_Color_Background(m_Options("COLOR_MODE")->asInt())); dc.Clear();

	Draw_Edge(dc, EDGE_STYLE_SUNKEN, r);

	_Draw(dc, r);
}

//---------------------------------------------------------
void CVIEW_ScatterPlot::_Draw(wxDC &dc, wxRect r)
{
	dc.SetPen           (SYS_Get_Color_Foreground(m_Options("COLOR_MODE")->asInt()));
	dc.SetTextForeground(SYS_Get_Color_Foreground(m_Options("COLOR_MODE")->asInt()));

	r = _Draw_Get_rDiagram(r);

	if( m_Trend.Get_Data_Count() > 1 )
	{
		//-------------------------------------------------
		if( m_Options("DISPLAY")->asInt() == 1 )
		{
			if( m_Options("DENSITY_LEG")->asBool() )
			{
				r.SetRight(r.GetRight() - 40);

				_Draw_Legend(dc, wxRect(r.GetRight() + 10, r.GetTop(), 40 - 20, r.GetHeight()));
			}

			_Draw_Image (dc, r);
		}
		else
		{
			_Draw_Points(dc, r);
		}

		//-------------------------------------------------
		_Draw_Frame(dc, r);
	}
	else
	{
		Draw_Text(dc, TEXTALIGN_CENTER, r.GetLeft() + r.GetWidth() / 2, r.GetTop() + r.GetHeight() / 2, _TL("Invalid data!"));
	}
}

//---------------------------------------------------------
CSG_Colors CVIEW_ScatterPlot::_Get_Colors(int nColors)
{
	CSG_Colors Colors, *pColors = m_Options("DENSITY_PAL")->asColors();

	wxColor Background(SYS_Get_Color_Background(m_Options("COLOR_MODE")->asInt()));

	Colors.Set_Count(pColors->Get_Count() + 1);

	Colors.Set_Color(0, Background.GetRed(), Background.GetGreen(), Background.GetBlue());

	for(int i=0; i<pColors->Get_Count(); i++)
	{
		Colors[i + 1] = pColors->Get_Color(i);
	}

	Colors.Set_Count(nColors);

	return( Colors );
}

//---------------------------------------------------------
wxRect CVIEW_ScatterPlot::_Draw_Get_rDiagram(wxRect r)
{
	return(	wxRect(
		wxPoint(r.GetLeft () + 45, r.GetTop   () + 20),
		wxPoint(r.GetRight() - 20, r.GetBottom() - 40)
	));
}

//---------------------------------------------------------
void CVIEW_ScatterPlot::_Draw_Regression(wxDC &dc, wxRect r)
{
	wxPen oldPen = dc.GetPen();

	dc.SetPen(wxPen(
		m_Options("REG_COLOR")->asColor(),
		m_Options("REG_SIZE" )->asInt()
	));

	//-----------------------------------------------------
	double dx = (r.GetWidth () - 1.) / m_xRange[2]; // m_Trend.Get_Data_XStats().Get_Range();
	double dy = (r.GetHeight() - 1.) / m_yRange[2]; // m_Trend.Get_Data_YStats().Get_Range();

	//-----------------------------------------------------
	dc.DrawCircle(
		GET_DC_X(m_Trend.Get_Data_XStats().Get_Mean()),
		GET_DC_Y(m_Trend.Get_Data_YStats().Get_Mean()), 2
	);

	double ex = m_xRange[2] / (double)r.GetWidth(); // m_Trend.Get_Data_XStats().Get_Range() / (double)r.GetWidth();
	double  x = m_xRange[0];

	for(int ix=0, ay, by=0; ix<r.GetWidth(); ix++, x+=ex)
	{
		double y = m_Trend.Get_Value(x);

		ay = by; by = r.GetBottom() - (int)(dy * (y - m_yRange[0]));

		if( ix > 0 && r.GetTop() < ay && ay < r.GetBottom() && r.GetTop() < by && by < r.GetBottom() )
		{
			dc.DrawLine(r.GetLeft() + ix - 1, ay, r.GetLeft() + ix, by);
		}
	}

	dc.SetPen(oldPen);

	//-----------------------------------------------------
	Draw_Text(dc, TEXTALIGN_BOTTOMCENTER, r.GetLeft() + r.GetWidth() / 2, r.GetTop(),
		m_Trend.Get_Formula(SG_TREND_STRING_Compact).c_str()
	);
}

//---------------------------------------------------------
void CVIEW_ScatterPlot::_Draw_Legend(wxDC &dc, wxRect r)
{
	CSG_Colors Colors(_Get_Colors(r.GetHeight()));

	for(int i=0, y=r.GetBottom(); i<Colors.Get_Count(); i++, y--)
	{
		Draw_FillRect(dc, Get_Color_asWX(Colors.Get_Color(i)), r.GetLeft(), y, r.GetRight(), y + 1);
	}

//	r.Offset(0, -r.GetHeight());

	Draw_Edge(dc, EDGE_STYLE_SIMPLE, r);

	int Min = m_Options("Z_ADJUST")->asInt() ? m_Options("Z_RANGE.MIN")->asDouble() : 0;
	int Max = m_Options("Z_ADJUST")->asInt() ? m_Options("Z_RANGE.MAX")->asDouble() : (int)m_Count.Get_Max();

	Draw_Text(dc, TEXTALIGN_BOTTOMLEFT, 2 + r.GetRight(), r.GetBottom(), wxString::Format("%d", Min));
	Draw_Text(dc, TEXTALIGN_TOPLEFT   , 2 + r.GetRight(), r.GetTop   (), wxString::Format("%d", Max));
}

//---------------------------------------------------------
void CVIEW_ScatterPlot::_Draw_Image(wxDC &dc, wxRect r)
{
	CSG_Colors Colors(_Get_Colors());

	wxImage	Image(r.GetWidth(), r.GetHeight());

	int Min = m_Options("Z_ADJUST")->asInt() ? m_Options("Z_RANGE.MIN")->asDouble() : 0;
	int Max = m_Options("Z_ADJUST")->asInt() ? m_Options("Z_RANGE.MAX")->asDouble() : (int)m_Count.Get_Max();
	int Range = Max - Min;

	double dCount = (Colors.Get_Count() - 2.) / log(1. + Range);

	double dx = (m_Count.Get_NX() - 1.) / (double)r.GetWidth ();
	double dy = (m_Count.Get_NY() - 1.) / (double)r.GetHeight();

	//-----------------------------------------------------
	#pragma omp parallel for
	for(int y=Image.GetHeight()-1; y>=0; y--)
	{
		double Count, ix = 0., iy = m_Count.Get_NY() - 1 - y * dy;

		for(int x=0; x<Image.GetWidth(); x++, ix+=dx)
		{
			Count = !m_Count.Get_Value(ix, iy, Count) || Count < Min ? 0. : Count - Min; if( Count > Range ) { Count = Range; }
			int i = (int)(log(1. + Count) * dCount);
			Image.SetRGB(x, y, Colors.Get_Red(i), Colors.Get_Green(i), Colors.Get_Blue(i));
		}
	}

	dc.DrawBitmap(wxBitmap(Image), r.GetTopLeft());
}

//---------------------------------------------------------
void CVIEW_ScatterPlot::_Draw_Points(wxDC &dc, wxRect r)
{
	double dx = (r.GetWidth () - 1.) / m_xRange[2]; // m_Trend.Get_Data_XStats().Get_Range();
	double dy = (r.GetHeight() - 1.) / m_yRange[2]; // m_Trend.Get_Data_YStats().Get_Range();

	for(int i=0; i<m_Trend.Get_Data_Count(); i++)
	{
		dc.DrawCircle(
			GET_DC_X(m_Trend.Get_Data_X(i)),
			GET_DC_Y(m_Trend.Get_Data_Y(i)), 2
		);
	}
}

//---------------------------------------------------------
void CVIEW_ScatterPlot::_Draw_Frame(wxDC &dc, wxRect r)
{
	//-----------------------------------------------------
	Draw_Edge(dc, EDGE_STYLE_SIMPLE, r);

	//-------------------------------------------------
	Draw_Scale(dc, wxRect(r.GetLeft(), r.GetBottom(), r.GetWidth(), 20),
		m_xRange[0], m_xRange[1], // m_Trend.Get_Data_XMin(), m_Trend.Get_Data_XMax(),
		true , true , true
	);

	Draw_Scale(dc, wxRect(r.GetLeft() - 20, r.GetTop(), 20, r.GetHeight()),
		m_yRange[0], m_yRange[1], // m_Trend.Get_Data_YMin(), m_Trend.Get_Data_YMax(),
		false, false, false
	);

	Draw_Text(dc, TEXTALIGN_BOTTOMCENTER,
		r.GetLeft() - 25, r.GetTop() + r.GetHeight() / 2, 90.,
		m_sY.c_str()
	);

	Draw_Text(dc, TEXTALIGN_TOPCENTER,
		r.GetLeft() + r.GetWidth() / 2, r.GetBottom() + 20,
		m_sX.c_str()
	);

	//-------------------------------------------------
	if( m_Options("REG_SHOW")->asBool() )
	{
		_Draw_Regression(dc, r);
	}
}


///////////////////////////////////////////////////////////
//														 //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CVIEW_ScatterPlot::_Update_Data(void)
{
	bool bResult;

	m_Trend.Clr_Data();

	m_Parameters.DataObjects_Check();

	if( m_pGrid )
	{
		bResult = m_Parameters("CMP_WITH")->asInt() == 0
			? _Initialize_Grids(m_pGrid, m_Parameters("GRID")->asGrid())
			: _Initialize_Shapes();
	}
	else if( m_pGrids )
	{
		bResult = _Initialize_Grids(
			m_pGrids->Get_Grid_Ptr(m_Parameters("BAND_X")->asInt()),
			m_pGrids->Get_Grid_Ptr(m_Parameters("BAND_Y")->asInt())
		);
	}
	else // if( m_pTable )
	{
		bResult = _Initialize_Table();
	}

	PROCESS_Set_Okay();

	//-----------------------------------------------------
	if( bResult )
	{
		m_xRange[0] = m_Options("X_ADJUST")->asInt() ? m_Options("X_RANGE.MIN")->asDouble() : m_Trend.Get_Data_XMin();
		m_xRange[1] = m_Options("X_ADJUST")->asInt() ? m_Options("X_RANGE.MAX")->asDouble() : m_Trend.Get_Data_XMax();
		m_xRange[2] = m_xRange[1] - m_xRange[0];

		m_yRange[0] = m_Options("Y_ADJUST")->asInt() ? m_Options("Y_RANGE.MIN")->asDouble() : m_Trend.Get_Data_YMin();
		m_yRange[1] = m_Options("Y_ADJUST")->asInt() ? m_Options("Y_RANGE.MAX")->asDouble() : m_Trend.Get_Data_YMax();
		m_yRange[2] = m_yRange[1] - m_yRange[0];

		bResult = m_xRange[2] && m_yRange[2];
	}

	//-----------------------------------------------------
	if( bResult )
	{
		CSG_String Info(_TL("Regression"));

		if( !m_Trend.Set_Formula(m_Options("REG_FORMULA")->asString()) || !m_Trend.Get_Trend() )
		{
			Info += CSG_String::Format(" %s!\n", _TL("failed"));
			Info += m_Trend.Get_Error();
		}
		else
		{
			Info += CSG_String::Format(" %s\n", _TL("Details"));
			Info += m_Trend.Get_Formula(SG_TREND_STRING_Complete);
		}

		m_Options("REG_INFO")->Set_Value(Info);

		_Initialize_Count();

		Refresh();

		return( true );
	}

	Refresh();

	return( false );
}

//---------------------------------------------------------
bool CVIEW_ScatterPlot::_Initialize_Count(void)
{
	int Resolution = m_Options("DENSITY_RES")->asInt();

	m_Count.Create(SG_DATATYPE_Int, Resolution, Resolution, 1.);

	double dx = (m_Count.Get_NX() - 1.) / m_xRange[2];
	double dy = (m_Count.Get_NY() - 1.) / m_yRange[2];

	for(int i=0; i<m_Trend.Get_Data_Count(); i++)
	{
		int x = (int)(0.5 + dx * (m_Trend.Get_Data_X(i) - m_xRange[0])); // m_Trend.Get_Data_XMin()));
		int y = (int)(0.5 + dy * (m_Trend.Get_Data_Y(i) - m_yRange[0])); // m_Trend.Get_Data_YMin()));

		if( m_Count.is_InGrid(x, y, false) )
		{
			m_Count.Add_Value(x, y, 1);
		}
	}

	return( true );
}


///////////////////////////////////////////////////////////
//														 //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CVIEW_ScatterPlot::_Initialize_Grids(CSG_Grid *pGrid_X, CSG_Grid *pGrid_Y)
{
	CHECK_DATA(pGrid_X);
	CHECK_DATA(pGrid_Y);

	if( !pGrid_X || !pGrid_Y )
	{
		return( false );
	}

	//-----------------------------------------------------
	m_sTitle.Printf("%s: [%s/%s]", _TL("Scatterplot"), pGrid_X->Get_Name(), pGrid_Y->Get_Name());

	m_sX.Printf("%s", pGrid_X->Get_Name());
	m_sY.Printf("%s", pGrid_Y->Get_Name());

	bool    bEqual = pGrid_X->Get_System() == pGrid_Y->Get_System();
	int maxSamples = m_Options("SAMPLES_MAX")->asInt();
	double	  Step = maxSamples > 0 && pGrid_X->Get_NCells() > maxSamples ? pGrid_X->Get_NCells() / maxSamples : 1.0;

	for(double dCell=0; dCell<pGrid_X->Get_NCells() && PROGRESSBAR_Set_Position(dCell, pGrid_X->Get_NCells()); dCell+=Step)
	{
		sLong iCell = (sLong)dCell;

		if( !pGrid_X->is_NoData(iCell) )
		{
			if( bEqual )
			{
				if( !pGrid_Y->is_NoData(iCell) )
				{
					m_Trend.Add_Data(pGrid_X->asDouble(iCell), pGrid_Y->asDouble(iCell));
				}
			}
			else
			{
				TSG_Point p = pGrid_X->Get_System().Get_Grid_to_World(
					(int)(iCell % pGrid_X->Get_NX()),
					(int)(iCell / pGrid_X->Get_NX())
				);

				double y;

				if(	pGrid_Y->Get_Value(p, y) )
				{
					m_Trend.Add_Data(pGrid_X->asDouble(iCell), y);
				}
			}
		}
	}

	return( true );
}


///////////////////////////////////////////////////////////
//														 //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CVIEW_ScatterPlot::_Initialize_Shapes(void)
{
	CSG_Shapes *pPoints = m_Parameters("POINTS")->asShapes();
	int           Field = m_Parameters("FIELD" )->asInt();

	TSG_Grid_Resampling	Resampling;

	switch( m_Parameters("RESAMPLING")->asInt() )
	{
	default: Resampling = GRID_RESAMPLING_NearestNeighbour; break;
	case  1: Resampling = GRID_RESAMPLING_Bilinear        ; break;
	case  2: Resampling = GRID_RESAMPLING_BicubicSpline   ; break;
	case  3: Resampling = GRID_RESAMPLING_BSpline         ; break;
	}

	CHECK_DATA(m_pGrid);
	CHECK_DATA(pPoints);

	if( !m_pGrid || !pPoints || Field < 0 || Field >= pPoints->Get_Field_Count() )
	{
		return( false );
	}

	m_sTitle.Printf("%s: [%s/%s]", _TL("Scatterplot"), m_pGrid->Get_Name(), pPoints->Get_Name());

	m_sX.Printf("%s", m_pGrid->Get_Name());
	m_sY.Printf("%s", pPoints->Get_Field_Name(Field));

	int maxSamples = m_Options("SAMPLES_MAX")->asInt();
	double x, Step = maxSamples > 0 && pPoints->Get_Count() > maxSamples ? pPoints->Get_Count() / maxSamples : 1.0;

	for(double i=0; i<pPoints->Get_Count() && PROGRESSBAR_Set_Position(i, pPoints->Get_Count()); i+=Step)
	{
		CSG_Shape *pShape = pPoints->Get_Shape((int)i);

		if( !pShape->is_NoData(Field) && m_pGrid->Get_Value(pShape->Get_Point(), x, Resampling) )
		{
			m_Trend.Add_Data(x, pShape->asDouble(Field));
		}
	}

	return( true );
}


///////////////////////////////////////////////////////////
//														 //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
bool CVIEW_ScatterPlot::_Initialize_Table(void)
{
	int xField = m_Parameters("FIELD_X")->asInt();
	int yField = m_Parameters("FIELD_Y")->asInt();

	CHECK_DATA(m_pTable);

	if( !m_pTable || xField < 0 || xField >= m_pTable->Get_Field_Count() || yField < 0 || yField >= m_pTable->Get_Field_Count() )
	{
		return( false );
	}

	int maxSamples = m_Options("SAMPLES_MAX")->asInt();
	double    Step = maxSamples > 0 && m_pTable->Get_Count() > maxSamples ? m_pTable->Get_Count() / maxSamples : 1.;

	m_sTitle.Printf("%s: [%s]", _TL("Scatterplot"), m_pTable->Get_Name());

	m_sX.Printf("%s", m_pTable->Get_Field_Name(xField));
	m_sY.Printf("%s", m_pTable->Get_Field_Name(yField));

	for(double i=0; i<m_pTable->Get_Count() && PROGRESSBAR_Set_Position(i, m_pTable->Get_Count()); i+=Step)
	{
		CSG_Table_Record *pRecord = m_pTable->Get_Record((int)i);

		if( !pRecord->is_NoData(xField) && !pRecord->is_NoData(yField) )
		{
			m_Trend.Add_Data(pRecord->asDouble(xField), pRecord->asDouble(yField));
		}
	}

	return( true );
}


///////////////////////////////////////////////////////////
//														 //
//														 //
//														 //
///////////////////////////////////////////////////////////

//---------------------------------------------------------
