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

mv_graph.cpp (C-Munipack project)
Display class for graphs
Copyright (C) 2012 David Motl, dmotl@volny.cz

This program 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.

This program 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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

**************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <math.h>

#include "mv_graph.h"
#include "properties_dlg.h"
#include "ctxhelp.h"

#define PT_SIZE_MIN	1
#define PT_SIZE_MAX	5

enum {
	CMD_PROPERTIES = 100,
	CMD_COPY,
	CMD_CHANNEL_FIRST = 200,
	CMD_SIZE_FIRST = 300
};

#ifndef MAX
#define MAX(a, b) ((a)>(b) ? (a) : (b))
#endif

#ifndef MIN
#define MIN(a, b) ((a)<(b) ? (a) : (b))
#endif

static char *mv_strdup(const char *str)
{
	if (str) {
		size_t len = strlen(str)+1;
		char *buf = (char*)malloc(len*sizeof(char));
		if (buf) {
			strcpy_s(buf, len, str);
			return buf;
		}
	}
	return NULL;
}

#define NumTSteps		20

// This array enumerates allowed step of the scale in TIME mode
static const double TSteps[NumTSteps] = {1.0, 2.0, 5.0, 10.0, 15.0, 30.0, 60.0, 120.0, 300.0, 600.0, 
        900.0, 1800.0, 3600.0, 7200.0, 10800, 21600.0, 43200.0, 86400.0, 172800.0, 
        259200.0};

static int step_to_prec(double step)
{
	if (step<=0.0 || step>=1.0)
		return 0;
	return (int)ceil(-log10(step));
}

static void CheckLimits(double *a, double *b, double eps, double min, double max)
{
	double z, g;

	*a = LimitValue(*a, min, max);
	*b = LimitValue(*b, min, max);
	if ((*b-*a)<eps && (max-min)>=eps) {
		z = 0.5 * (*a + *b);
		g = z - (z - min)/(max - min)*eps;
		g = LimitValue(g, MAX(min, *b-eps), MIN(*a, max-eps));
		*a = g;
		*b = g + eps;
	}
}

static HBITMAP MakeDIBSection(HDC hDC, int width, int height, void **pBits, int *prowwidth)
{
	HBITMAP hBmp;
	int bitcount = 8, colors = 256;

	//create tile bitmap and device context
	BITMAPINFO *pbmi = (BITMAPINFO*)calloc(1, sizeof(BITMAPINFOHEADER)+colors*sizeof(RGBQUAD));
	pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	pbmi->bmiHeader.biBitCount = bitcount;
	pbmi->bmiHeader.biClrImportant = colors;
	pbmi->bmiHeader.biClrUsed = colors;
	pbmi->bmiHeader.biHeight = height;
	pbmi->bmiHeader.biPlanes = 1;
	pbmi->bmiHeader.biWidth = width;
	for (int i=0; i<colors; i++) {
		pbmi->bmiColors[i].rgbRed = i;
		pbmi->bmiColors[i].rgbGreen = i;
		pbmi->bmiColors[i].rgbBlue = i;
	}
	hBmp = CreateDIBSection(hDC, pbmi, DIB_RGB_COLORS, pBits, NULL, 0);
	if (prowwidth)
		*prowwidth = ((width*(bitcount>>3)+3)/4)*4;
	free(pbmi);
	return hBmp;
}

TCCD_Graph::TCCD_Graph(void):m_Tab(NULL), m_Type(CMPACK_TABLE_UNSPECIFIED), 
	m_Filter(NULL), m_Data(NULL), m_Count(0), m_Capacity(0), m_ChannelX(-1), 
	m_ChannelY(-1), m_PtSize(3), m_ClientWidth(0), m_ClientHeight(0), m_GraphWidth(0), 
	m_GraphHeight(0), m_ShowErrors(false), m_Title(NULL)
{
	m_ZoomBase = pow(100.0, 1.0/100.0);
	memset(&m_X, 0, sizeof(GraphAxis));
	memset(&m_Y, 0, sizeof(GraphAxis));
	memset(&m_CanvasRc, 0, sizeof(RECT));
	memset(&m_GraphRc, 0, sizeof(RECT));
	HDC hdc = CreateCompatibleDC(0);
	m_hFont = CreateFont(-MulDiv(10, GetDeviceCaps(hdc, LOGPIXELSY), 72), 
		0, 0, 0, 0, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
		CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH, "Lucida Console");
	DeleteDC(hdc);
}

TCCD_Graph::~TCCD_Graph(void)
{
	Clear();
	free(m_X.Caption);
	free(m_Y.Caption);
	free(m_Title);
	DeleteObject(m_hFont);
	if (m_Tab)
		cmpack_tab_destroy(m_Tab);
	free(m_Filter);
}

// Check if the buffer contains beginning of an image
bool TCCD_Graph::Test(const char *buf, int bytes)
{
	return cmpack_tab_test_buffer(buf, bytes, -1)!=0;
}

void TCCD_Graph::Clear(void)
{
	m_Type = CMPACK_TABLE_UNSPECIFIED;
	free(m_Filter);
	m_Filter = NULL;
	free(m_Data);
	m_Data = NULL;
	m_Count = m_Capacity = 0;
	if (m_Tab) {
		cmpack_tab_destroy(m_Tab);
		m_Tab = NULL;
	}
	m_ChannelsX.Clear();
	m_ChannelsY.Clear();
	m_ChannelX = m_ChannelY = -1;
}

bool TCCD_Graph::Load(const char *filename)
{
	Clear();
	if (cmpack_tab_load(&m_Tab, filename, 0)==0) {
		SetFilePath(filename);
		UpdateParams();
		SetScales(true, true);
		SetGrid(false, false);
		UpdateTitle();
		UpdateChannels();
		UpdateData();
	}
	return true;
}

// Update file properties
void TCCD_Graph::UpdateParams(void)
{
	if (m_Tab) {
		m_Type = cmpack_tab_get_type(m_Tab);
		free(m_Filter);
		m_Filter = mv_strdup(cmpack_tab_gkys(m_Tab, "Filter"));
	}
}

// Get format name
const char *TCCD_Graph::FormatName(void) const
{
	switch (m_Type)
	{
	case CMPACK_TABLE_LCURVE_INST:
	case CMPACK_TABLE_LCURVE_DIFF:
		return "C-Munipack light curve";
	case CMPACK_TABLE_TRACKLIST:
		return "C-Munipack track-list curve";
	case CMPACK_TABLE_AIRMASS:
		return "C-Munipack air-mass curve";
	case CMPACK_TABLE_MAGDEV:
		return "C-Munipack mag-dev curve";
	case CMPACK_TABLE_APERTURES:
		return "C-Munipack table of deviations";
	case CMPACK_TABLE_CCD_TEMP:
		return "C-Munipack CCD temperatures";
	case CMPACK_TABLE_OBJ_PROP:
		return "C-Munipack object properties";
	default:
		return "C-Munipack table";
	}
}

//
// Update caption
//
void TCCD_Graph::UpdateTitle(void)
{
	const char *filter;
	char buf[512];
	
	const char *typestr = FormatName();
	if (typestr) {
		if (m_Tab && (m_Type==CMPACK_TABLE_LCURVE_INST || m_Type==CMPACK_TABLE_LCURVE_DIFF))
			filter = cmpack_tab_gkys(m_Tab, "Filter");
		else
			filter = NULL;
		if (filter) {
			sprintf_s(buf, "%s | Filter: %s", typestr, filter);
			SetTitle(buf);
		} else 
			SetTitle(typestr);
	} else
		SetTitle(NULL);
}

//
// Update channels
//
void TCCD_Graph::UpdateChannels(void)
{
	const static int mask = CMPACK_TM_NAME | CMPACK_TM_TYPE_PREC;
	int i, ncols;
	CmpackTableType ttype;
	CmpackTabColumn col, col2;

	m_ChannelsX.Clear();
	m_ChannelsY.Clear();
	m_ChannelX = m_ChannelY = -1;
	if (!m_Tab)
		return;

	ncols = cmpack_tab_ncolumns(m_Tab);
	ttype = cmpack_tab_get_type(m_Tab);
	for (i=0; i<ncols; i++) {
		cmpack_tab_get_column(m_Tab, i, mask, &col);
		switch (ttype)
		{
		case CMPACK_TABLE_LCURVE_INST:
		case CMPACK_TABLE_LCURVE_DIFF:
		case CMPACK_TABLE_TRACKLIST:
		case CMPACK_TABLE_AIRMASS:
			if (col.dtype == CMPACK_TYPE_INT && strcmp(col.name, "FRAME")==0) {
				// Skip
			} else
			if (col.dtype==CMPACK_TYPE_DBL && (strcmp(col.name, "JD")==0 || strcmp(col.name, "HJD")==0 || 
				strcmp(col.name, "GJD")==0 || strcmp(col.name, "JDHEL")==0 || 
				strcmp(col.name, "JDGEO")==0)) {
					m_ChannelsX.Add(new CChannel(this, i, -1, DATA_JD));
			} else
			if (col.dtype==CMPACK_TYPE_DBL && strcmp(col.name, "AIRMASS")==0) {
					m_ChannelsY.Add(new CChannel(this, i, -1, DATA_AIRMASS));
			} else
			if (col.dtype==CMPACK_TYPE_DBL && strcmp(col.name, "ALTITUDE")==0) {
					m_ChannelsY.Add(new CChannel(this, i, -1, DATA_ALTITUDE));
			} else
			if (col.dtype==CMPACK_TYPE_DBL && (strcmp(col.name, "OFFSETX")==0 || strcmp(col.name, "OFFSETY")==0)) {
					m_ChannelsY.Add(new CChannel(this, i, -1, DATA_OFFSET));
			} else
			if (col.dtype==CMPACK_TYPE_DBL && strcmp(col.name, "HELCOR")==0) {
					m_ChannelsY.Add(new CChannel(this, i, -1, DATA_HELCOR));
			} else
			if (col.dtype==CMPACK_TYPE_DBL && (col.name[0]=='V' || col.name[0]=='C' || col.name[0]=='K')) {
				int err = -1;
				if (cmpack_tab_get_column(m_Tab, i+1, mask, &col2)==0 && col2.name[0]=='s') 
					err = i+1;
				m_ChannelsY.Add(new CChannel(this, i, err, DATA_MAGNITUDE));
				if (err>=0)
					i++;
			} else
			if (col.dtype==CMPACK_TYPE_DBL && strstr(col.name,"MAG")==col.name) {
				int err = -1;
				if (cmpack_tab_get_column(m_Tab, i+1, mask, &col2)==0 && strstr(col2.name, "ERR")==col2.name)
					err = i+1;
				m_ChannelsY.Add(new CChannel(this, i, err, DATA_MAGNITUDE));
				if (err>=0)
					i++;
			}
			break;

		case CMPACK_TABLE_MAGDEV:
			if (col.dtype==CMPACK_TYPE_DBL && strcmp(col.name, "MEAN_MAG")==0) {
				m_ChannelsX.Add(new CChannel(this, i, -1, DATA_MAGNITUDE));
			} else
			if (col.dtype==CMPACK_TYPE_DBL && strcmp(col.name, "STDEV")==0) {
				m_ChannelsY.Add(new CChannel(this, i, -1, DATA_DEVIATION));
			}
			break;

		case CMPACK_TABLE_APERTURES:
			if (strcmp(col.name, "APERTURE")==0) {
				m_ChannelsX.Add(new CChannel(this, i, -1, DATA_ANY));
			} else
			if (col.name[0]=='C' || col.name[0]=='K') {
				m_ChannelsY.Add(new CChannel(this, i, -1, DATA_DEVIATION));
			}
			break;

		default:
			break;
		}
	}
	m_ChannelX = LimitValue(m_ChannelX, 0, m_ChannelsX.Count()-1);
	m_ChannelY = LimitValue(m_ChannelY, 0, m_ChannelsY.Count()-1);
}

//
// Set graph view parameters
//
void TCCD_Graph::UpdateData(void)
{
	int		j, xcol, ycol, ucol;
	double	x, y, error, min, max;
	const char *name;

	m_Count = 0;
	if (!m_Tab)
		return;

	CChannel *ch_x = m_ChannelsX.Get(m_ChannelX);
	CChannel *ch_y = m_ChannelsY.Get(m_ChannelY);

	if (ch_x && ch_y) {
		min = ch_x->Min();
		max = ch_x->Max();
		name = ch_x->Name();
		switch (ch_x->Info())
		{
		case DATA_MAGNITUDE:
			CheckLimits(&min, &max, 0.05, -99.0, +99.0);
			SetAxisX(false, false, min, max, 0.05, FORMAT_FIXED, 1, 3, name);
			break;
		case DATA_JD:
			CheckLimits(&min, &max, 0.0005, 0.0, 1e7);
			SetAxisX(false, false, min, max, 0.005, FORMAT_FIXED, 0, 3, name);
			break;
		default:
			CheckLimits(&min, &max, 0.005, 1e-99, 1e99);
			SetAxisX(false, false, min, max, 0.005, FORMAT_FIXED, 0, 3, name);
			break;
		}

		min = ch_y->Min();
		max = ch_y->Max();
		if (m_ShowErrors && ch_y->ColumnU()>0) {
			min -= ch_y->MaxU();
			max += ch_y->MaxU();
		}
		name = ch_y->Name();
		switch (ch_y->Info())
		{
		case DATA_MAGNITUDE:
			CheckLimits(&min, &max, 0.05, -99.0, +99.0);
			SetAxisY(false, true, min, max, 0.05, FORMAT_FIXED, 1, 3, name);
			break;
		case DATA_DEVIATION:
			CheckLimits(&min, &max, 0.05, 0.0, 99.0);
			SetAxisY(false, false, min, max, 0.05, FORMAT_FIXED, 1, 3, name);
			break;
		case DATA_HELCOR:
			CheckLimits(&min, &max, 0.0001, -1.0, 1.0);
			SetAxisY(false, false, min, max, 0.0001, FORMAT_FIXED, 1, 4, name);
			break;
		case DATA_AIRMASS:
			CheckLimits(&min, &max, 0.01, 1.0, 99.0);
			SetAxisY(false, false, min, max, 0.01, FORMAT_FIXED, 1, 2, name);
			break;
		case DATA_OFFSET:
			CheckLimits(&min, &max, 0.1, -1e6, 1e6);
			SetAxisY(false, false, min, max, 0.1, FORMAT_FIXED, 0, 1, name);
			break;
		case DATA_ALTITUDE:
			CheckLimits(&min, &max, 1.0, -90.0, +90.0);
			SetAxisY(false, false, min, max, 1.0, FORMAT_FIXED, 0, 0, name);
			break;
		default:
			CheckLimits(&min, &max, 0.005, -1e99, 1e99);
			SetAxisY(false, false, min, max, 0.005, FORMAT_FIXED, 0, 3, name);
			break;
		}

		int row_count = cmpack_tab_nrows(m_Tab);
		if (row_count>0) {
			if (row_count > m_Capacity) {
				free(m_Data);
				m_Capacity = row_count;
				m_Data = (GraphItem*)malloc(m_Capacity*sizeof(GraphItem));
			}
			memset(m_Data, 0, row_count*sizeof(GraphItem));
			xcol = ch_x->Column();
			ycol = ch_y->Column();
			ucol = ch_y->ColumnU();
			cmpack_tab_rewind(m_Tab);
			j = 0;
			while (!cmpack_tab_eof(m_Tab)) {
				GraphItem *item = m_Data + j;
				if (cmpack_tab_gtdd(m_Tab, xcol, &x)==0 && cmpack_tab_gtdd(m_Tab, ycol, &y)==0) {
					if (x>=m_X.Min && x<=m_X.Max && y>=m_Y.Min && y<=m_Y.Max) {
						item->xproj = x_to_proj(x);
						item->yproj = y_to_proj(y);
						if (ucol>=0 && m_ShowErrors && cmpack_tab_gtdd(m_Tab, ucol, &error)==0)
							item->error = error;
						else
							item->error = 0;
						j++;
					}
				}
				cmpack_tab_next(m_Tab);
			}
			m_Count = j;
		}
	}

	set_auto_zoom_x();
	set_auto_zoom_y();
}

// Resize back-screen buffer, update rectangles
void TCCD_Graph::OnSizeChanged(void)
{
	update_rectangles();
	update_x_pxlsize();
	update_y_pxlsize();
	if (m_X.PxlSize>0 && m_Y.PxlSize>0) {
		restrict_x_to_limits();
		restrict_y_to_limits();
		update_hsb();
		update_vsb();
	}
}

void TCCD_Graph::OnRender(HDC hDC)
{
	RenderGraph(hDC, m_CanvasRc.left, m_CanvasRc.top, m_CanvasRc.right-m_CanvasRc.left, 
		m_CanvasRc.bottom-m_CanvasRc.top, false);
	if (m_Title) {
		HFONT oldfont = (HFONT)SelectObject(hDC, m_hFont);
		SetBkMode(hDC, TRANSPARENT);
		paint_title(hDC);
		SelectObject(hDC, oldfont);
	}
}

void TCCD_Graph::RenderGraph(HDC hDC, int left, int top, int width, int height, bool for_print)
{
	RECT	rc;

	// Background
	COLORREF bg_clr = (for_print ? RGB(255,255,255) : GetSysColor(COLOR_WINDOW));
	SetRect(&rc, 0, 0, m_ClientWidth, m_ClientHeight);
	HBRUSH bg = CreateSolidBrush(bg_clr);
	FillRect(hDC, &rc, bg);
	DeleteObject(bg);

	if (m_X.PxlSize<=0 || m_Y.PxlSize<=0) 
		return;

	// Margins
	if (!for_print) {
		HBRUSH br = GetSysColorBrush(COLOR_BTNFACE);
		if (top>0) {
			SetRect(&rc, 0, 0, left+width, top);
			FillRect(hDC, &rc, br);
		} 
		if (left>0) {
			SetRect(&rc, 0, top, left, m_ClientHeight);
			FillRect(hDC, &rc, br);
		}
		if (left+width < m_ClientWidth) {
			SetRect(&rc, left+width, 0, m_ClientWidth, top+height);
			FillRect(hDC, &rc, br);
		}
		if (top+height < m_ClientHeight) {
			SetRect(&rc, left, top+height, m_ClientWidth, m_ClientHeight);
			FillRect(hDC, &rc, br);
		}
	} else {
		HPEN hPen = CreatePen(PS_SOLID, 0, RGB(0, 0, 0));
		HPEN oldpen = (HPEN)SelectObject(hDC, hPen);
		MoveToEx(hDC, m_CanvasRc.left, m_CanvasRc.top, NULL);
		LineTo(hDC, m_CanvasRc.left, m_CanvasRc.bottom);
		LineTo(hDC, m_CanvasRc.right, m_CanvasRc.bottom);
		DeleteObject(SelectObject(hDC, oldpen));
	}

	HFONT oldfont = (HFONT)SelectObject(hDC, m_hFont);
	SetBkMode(hDC, TRANSPARENT);

	// Grid
	if (m_X.ShowGrid)
		paint_x_grid(hDC);
	if (m_Y.ShowGrid)
		paint_y_grid(hDC);

	// Data
	if (m_Data && m_Count>0)
		paint_data(hDC);
		
	// Labels
	if (m_X.ShowLabels)
		paint_x_scale(hDC);
	if (m_Y.ShowLabels)
		paint_y_scale(hDC);

	SelectObject(hDC, oldfont);
}

void TCCD_Graph::paint_data(HDC hDC)
{
	int		i, x, y, x1, x2, y1, y2;
	HPEN	hPen, oldpen;
	HBRUSH	oldbrush;
	HRGN	rgn;
	
	rgn = CreateRectRgn(m_CanvasRc.left, m_CanvasRc.top, m_CanvasRc.right, m_CanvasRc.bottom);
	SelectClipRgn(hDC, rgn);
	hPen = CreatePen(PS_SOLID, 0, GetSysColor(COLOR_BTNTEXT));
	oldpen = (HPEN) SelectObject(hDC, hPen);
	oldbrush = (HBRUSH)SelectObject(hDC, GetSysColorBrush(COLOR_BTNTEXT));
	
	for (i=0; i<m_Count; i++) {
		GraphItem *item = m_Data + i;
		x = RoundToInt(xproj_to_view(item->xproj));
		y = RoundToInt(yproj_to_view(item->yproj));
		if (x - m_PtSize >= m_CanvasRc.left && x + m_PtSize <= m_CanvasRc.right &&
			y - m_PtSize >= m_CanvasRc.top && y + m_PtSize <= m_CanvasRc.bottom) {
				if (item->error > 0) {
					x1 = x - m_PtSize;
					x2 = x + m_PtSize + 1;
					y1 = RoundToInt(yproj_to_view(item->yproj + item->error));
					y2 = RoundToInt(yproj_to_view(item->yproj - item->error));
					MoveToEx(hDC, x1, y1, NULL); LineTo(hDC, x2, y1);
					MoveToEx(hDC, x, y1, NULL); LineTo(hDC, x, y2);
					MoveToEx(hDC, x1, y2, NULL); LineTo(hDC, x2, y2);
				}
				Ellipse(hDC, x-m_PtSize, y-m_PtSize, x+m_PtSize+1, y+m_PtSize+1);
		}
	}

	SelectClipRgn(hDC, NULL);
	DeleteObject(rgn);
	SelectObject(hDC, oldbrush);
	SelectObject(hDC, oldpen);
	DeleteObject(hPen);
}

void TCCD_Graph::paint_title(HDC hDC)
{
	HRGN rgn = CreateRectRgn(m_CanvasRc.left, m_CanvasRc.top, m_CanvasRc.right, m_CanvasRc.bottom);
	SelectClipRgn(hDC, rgn);
	draw_text(hDC, m_CanvasRc.left + 4, m_CanvasRc.top + 2, m_Title, 0.0, 0.0);
	SelectClipRgn(hDC, NULL);
	DeleteObject(rgn);
}

void TCCD_Graph::text_extents(HDC hDC, const char *buf, SIZE *size)
{
	if (buf) 
		GetTextExtentPoint(hDC, buf, (int)strlen(buf), size);
	else
		size->cx = size->cy = 0;
}

int TCCD_Graph::text_width(HDC hDC, const char *buf)
{
	SIZE size;
	text_extents(hDC, buf, &size);
	return size.cx;
}

int TCCD_Graph::text_height(HDC hDC, const char *buf)
{
	SIZE size;
	text_extents(hDC, buf, &size);
	return size.cy;
}

void TCCD_Graph::update_rectangles(void)
{
	int		x_height, y_width;
	int		cm_left, cm_top, cm_right, cm_bottom;
	int		gm_left, gm_top, gm_right, gm_bottom;
	RECT	crc;
	SIZE	t, u, v;

	if (!GetClientRect(m_hWnd, &crc))
		return;
	m_ClientWidth = crc.right;
	m_ClientHeight = crc.bottom;

	HDC hDC = CreateCompatibleDC(0);
	HFONT oldfont = (HFONT)SelectObject(hDC, m_hFont);

	text_extents(hDC, m_Title, &t);
	t.cy += 4;

	x_height = compute_label_height(hDC, &m_X);
	text_extents(hDC, m_X.Caption, &u);
	u.cx += 8;
	x_height = MAX(x_height, u.cy) + 12;

	y_width = compute_label_width(hDC, &m_Y, m_Y.MaxPrec);
	text_extents(hDC, m_Y.Caption, &v);
	v.cy += 4;
	y_width = MAX(y_width, v.cx) + 12;
		
	cm_left = cm_top = cm_right = cm_bottom = 0;
	gm_left = gm_top = gm_right = gm_bottom = m_PtSize;
	gm_top += t.cy;
	
	if (m_X.ShowLabels) {
		if (!m_X.LabelsOpposite) {
			/* X axis is outside bottom */
			cm_bottom += x_height;
		} else {
			/* X axis is outside top */
			cm_top += x_height;
		}
	}
	if (m_Y.ShowLabels) {
		if (!m_Y.LabelsOpposite) {
			/* X axis is outside left */
			cm_left += y_width;
		} else {
			/* X axis is outside right */
			cm_right += y_width;
		}
	}

	m_CanvasRc.left = cm_left;
	m_CanvasRc.top = cm_top;
	m_CanvasRc.right = m_ClientWidth - cm_right;
	m_CanvasRc.bottom = m_ClientHeight - cm_bottom;

	m_GraphRc.left = m_CanvasRc.left + gm_left;
	m_GraphRc.top = m_CanvasRc.top + gm_top;
	m_GraphRc.right = m_CanvasRc.right - gm_right;
	m_GraphRc.bottom = m_CanvasRc.bottom - gm_bottom;

	m_GraphWidth = m_GraphRc.right - m_GraphRc.left;
	m_GraphHeight = m_GraphRc.bottom - m_GraphRc.top;

	memset(&m_X.ScaleRc, 0, sizeof(RECT));
	if (m_X.ShowLabels) {
		/* X scale is outside */
		m_X.ScaleRc.left = m_CanvasRc.left;
		m_X.ScaleRc.right = m_CanvasRc.right;
		if (!m_X.LabelsOpposite) {
			/* X axis is outside bottom */
			m_X.ScaleRc.top = m_CanvasRc.bottom;
		} else {
			/* X axis is outside top */
			m_X.ScaleRc.top = 0;
		}
		m_X.ScaleRc.bottom = m_X.ScaleRc.top + x_height;
	}

	memset(&m_Y.ScaleRc, 0, sizeof(RECT));
	if (m_Y.ShowLabels) {
		/* Y scale is outside */
		m_Y.ScaleRc.top = m_CanvasRc.top;
		m_Y.ScaleRc.bottom = m_CanvasRc.bottom;
		if (!m_Y.LabelsOpposite) {
			/* X axis is outside left */
			m_Y.ScaleRc.left = 0;
		} else {
			/* X axis is outside right */
			m_Y.ScaleRc.left = m_CanvasRc.right;
		}
		m_Y.ScaleRc.right = m_Y.ScaleRc.left + y_width;
	}

	memset(&m_X.LabelRc, 0, sizeof(RECT));
	if (m_X.ShowLabels && m_X.Caption) {
		if (m_Y.LabelsOpposite && m_Y.Caption) {
			/* Name is on the left */
			m_X.LabelRc.left = m_X.ScaleRc.left;
			m_X.ScaleRc.left += u.cx;
		} else {
			/* Name is on the right */
			m_X.LabelRc.left = m_X.ScaleRc.right - u.cx;
		}
		m_X.LabelRc.right = m_X.LabelRc.left + u.cx;
		m_X.LabelRc.top = m_X.ScaleRc.top;
		m_X.LabelRc.bottom = m_X.ScaleRc.bottom;
		m_X.ScaleRc.right -= u.cx;
	}

	memset(&m_Y.LabelRc, 0, sizeof(RECT));
	if (m_Y.ShowLabels && m_Y.Caption) {
		if (m_X.LabelsOpposite && m_X.Caption) {
			/* Name is on the bottom */
			m_Y.LabelRc.top = m_Y.ScaleRc.bottom - v.cy;
		} else {
			/* Name is on the top */
			m_Y.LabelRc.top = m_Y.ScaleRc.top;
			m_Y.ScaleRc.top += v.cy;
		}
		m_Y.LabelRc.bottom = m_Y.LabelRc.top + v.cy;
		m_Y.LabelRc.left = m_Y.ScaleRc.left;
		m_Y.LabelRc.right = m_Y.ScaleRc.right;
		m_Y.ScaleRc.bottom -= v.cy;

		m_X.Center = 0.5 * (m_GraphRc.left + m_GraphRc.right);
		m_Y.Center = 0.5 * (m_GraphRc.top + m_GraphRc.bottom);
	}

	SelectObject(hDC, oldfont);
	DeleteDC(hDC);
}

/* Update mapping coefficients */
void TCCD_Graph::update_x_pxlsize(void)
{	
	double XZoom = pow(m_ZoomBase, m_X.ZoomPos);
	if (m_GraphWidth>0) 
		m_X.PxlSize = (m_X.ProjMax - m_X.ProjMin) / (m_GraphWidth * XZoom);
	else
		m_X.PxlSize = 0.0;
}

/* Checks if the visible area is inside the limits */
void TCCD_Graph::restrict_x_to_limits(void)
{
	int left, right;
    double a, b;
	
	right = m_GraphRc.right;
    a = m_X.Center + (m_X.ProjMax - m_X.ProjPos)/m_X.PxlSize;
    if (a < right) {
        m_X.ProjPos = m_X.ProjMax - m_X.PxlSize*(right - m_X.Center);
    } else {
		left = m_GraphRc.left;
        b = m_X.Center + (m_X.ProjMin - m_X.ProjPos)/m_X.PxlSize;
        if (b > left)
            m_X.ProjPos = m_X.ProjMin - m_X.PxlSize*(left - m_X.Center);
    }
}

/* Physical units -> projection units */
double TCCD_Graph::x_to_proj(double x) const
{
	if (m_X.Log) 
		return (!m_X.Reverse ? 1.0 : -1.0) * log10(x);
	else
		return (!m_X.Reverse ? 1.0 : -1.0) * x;
}

/* Projection units -> physical units */
double TCCD_Graph::proj_to_x(double u) const
{
	if (m_X.Log) 
		return pow(10.0, (!m_X.Reverse ? 1.0 : -1.0) * u);
	else
		return (!m_X.Reverse ? 1.0 : -1.0) * u;
}

/* Projection units -> display units */
double TCCD_Graph::xproj_to_view(double x) const
{
	return m_X.Center + (x - m_X.ProjPos)/m_X.PxlSize;
}

/* Display units -> projection units */
double TCCD_Graph::view_to_xproj(double u) const
{
	return (u - m_X.Center)*m_X.PxlSize + m_X.ProjPos;
}

/* Physical units -> display units */
double TCCD_Graph::x_to_view(double x) const
{
	return xproj_to_view(x_to_proj(x));
}

/* Physical units -> projection units */
double TCCD_Graph::view_to_x(double u) const
{
	return proj_to_x(view_to_xproj(u));
}

/* Update range and position of horizontal scroll bar */
void TCCD_Graph::update_hsb(void) const
{
	/*int upper, pos;

	if (m_X.PxlSize>0) {
		upper = RoundToInt((m_X.ProjMax - m_X.ProjMin) / m_X.PxlSize);
		pos = RoundToInt((m_X.ProjPos - m_X.ProjMin)/m_X.PxlSize - m_X.Center + m_GraphRc.left);
		UpdateHSB(0, upper, m_GraphWidth, pos);
	}*/
}

/* Update mapping coefficients */
void TCCD_Graph::update_y_pxlsize(void)
{	
	double YZoom = pow(m_ZoomBase, m_Y.ZoomPos);
	if (m_GraphHeight>0)
		m_Y.PxlSize = (m_Y.ProjMax - m_Y.ProjMin) / 	(m_GraphHeight * YZoom);
	else
		m_Y.PxlSize = 0.0;
}

/* Checks if the visible area is inside the limits */
void TCCD_Graph::restrict_y_to_limits(void)
{
	int top, bottom;
    double a, b;

	top = m_GraphRc.top;
    a = m_Y.Center - (m_Y.ProjMax - m_Y.ProjPos)/m_Y.PxlSize;
    if (a > top) {
        m_Y.ProjPos = m_Y.ProjMax - m_Y.PxlSize*(m_Y.Center - top);
    } else {
		bottom = m_GraphRc.bottom;
        b = m_Y.Center - (m_Y.ProjMin - m_Y.ProjPos)/m_Y.PxlSize;
        if (b < bottom)
            m_Y.ProjPos = m_Y.ProjMin - m_Y.PxlSize*(m_Y.Center - bottom);
    }
}

/* Physical units -> projection units */
double TCCD_Graph::y_to_proj(double y) const
{
	if (m_Y.Log) 
		return (!m_Y.Reverse ? 1.0 : -1.0) * log10(y);
	else
		return (!m_Y.Reverse ? 1.0 : -1.0) * y;
}

/* Projection units -> physical units */
double TCCD_Graph::proj_to_y(double v) const
{
	if (m_Y.Log) 
		return pow(10.0, (!m_Y.Reverse ? 1.0 : -1.0) * v);
	else
		return (!m_Y.Reverse ? 1.0 : -1.0) * v;
}

/* Projection units -> display units */
double TCCD_Graph::yproj_to_view(double y) const
{
	return m_Y.Center - (y - m_Y.ProjPos)/m_Y.PxlSize;
}

/* Display units -> projection units */
double TCCD_Graph::view_to_yproj(double v) const
{
	return m_Y.ProjPos - (v - m_Y.Center)*m_Y.PxlSize;
}

/* Physical units -> display units */
double TCCD_Graph::y_to_view(double y) const
{
	return yproj_to_view(y_to_proj(y));
}

/* Physical units -> projection units */
double TCCD_Graph::view_to_y(double v) const
{
	return proj_to_y(view_to_yproj(v));
}

/* Update range and position of vertical scroll bar */
void TCCD_Graph::update_vsb(void) const
{
	/*int upper, pos;

	upper = RoundToInt((m_Y.ProjMax - m_Y.ProjMin) / m_Y.PxlSize);
	pos = RoundToInt((m_Y.ProjMax - m_Y.ProjPos) / m_Y.PxlSize + m_GraphRc.top - m_Y.Center);
	UpdateVSB(0, upper, m_GraphHeight, pos);*/
}

/*---------------------   SCALE LABELS & GRIDS   ---------------------------*/

/* Standard formatting function for scale labels */
void TCCD_Graph::format_label(char *buf, size_t buflen, double value, GraphFormat format, int prec)
{
	int time;

	buf[0] = '\0';
	switch(format) 
	{
	case FORMAT_EXP:
        // Always exponential form
		sprintf_s(buf, buflen, "%.*e", prec, value);
		if (buf[0]=='-' && strspn(buf, "-0.,e")==strlen(buf))
			sprintf_s(buf, buflen, "%.*e", prec, 0.0);
		break;

	case FORMAT_INT:
		// Integer number without decimal places
		sprintf_s(buf, buflen, "%.0f", value);
		if (buf[0]=='-' && strspn(buf, "-0.,e")==strlen(buf))
			sprintf_s(buf, buflen, "%.0f", 0.0);
		break;

	case FORMAT_FIXED:
        // Number with fixed number of decimal places
		sprintf_s(buf, buflen, "%.*f", prec, value);
		if (buf[0]=='-' && strspn(buf, "-0.,e")==strlen(buf))
			sprintf_s(buf, buflen, "%.*f", prec, 0.0);
		break;

	case FORMAT_TIME:
        // Time in the form hh:mm:ss
		time = RoundToInt((value - floor(value))*86400.0);
		sprintf_s(buf, buflen, "%d:%02d:%02d", time/3600, (time/60)%60, time%60);
		break;
	}
} 

/* Compute max. width of scale label */
int TCCD_Graph::compute_label_width(HDC hDC, GraphAxis *axis, int prec)
{
	char	buf[256];
    int	w1, w2;

	format_label(buf, 256, axis->Min, axis->Format, prec);
	w1 = text_width(hDC, buf);
	format_label(buf, 256, axis->Max, axis->Format, prec);
	w2 = text_width(hDC, buf);
	return MAX(w1, w2);
}

/* Compute height of scale label */
int TCCD_Graph::compute_label_height(HDC hDC, GraphAxis *axis)
{
    return text_height(hDC, "X");
}

/* Compute horizontal grid step and distance between two labels on horizontal scale */
void TCCD_Graph::compute_x_grid_step(HDC hDC, double minval, double maxval, double *step, int *minprec)
{
    int i, cx, nlines, xprec, oldprec;
	double xstep, dx, delta;

	xprec = 0;
	xstep = fabs(maxval - minval);

	switch (m_X.Format)
	{
	case FORMAT_TIME:
        // Time mode
		cx = compute_label_width(hDC, &m_X, 0);
		nlines = (int)(m_GraphWidth/((cx+1)*2.2)) - 1;
		if (nlines<2) nlines = 2;
		for (i=NumTSteps-1; i>0; i--) {
			if ((fabs(maxval-minval)/TSteps[i])>nlines)
				break;
		}
		xstep = TSteps[i];
		break;

	case FORMAT_INT:
		// Integer number without decimal places
		dx = (compute_label_width(hDC, &m_X, xprec)+40)*m_X.PxlSize;
		delta = pow(10.0, floor(log10(dx)));
		if (delta<1)
			delta = 1;
		if (delta>dx)
			xstep = delta;
		else if (2.0 * delta>dx) 
			xstep = 2.0 * delta;
		else if (5.0 * delta>dx) 
			xstep = 5.0 * delta;
		else
			xstep = 10.0 * delta;
		break;

	default:
        // Decimal mode
		xprec = m_X.MinPrec;
		xstep = fabs(maxval - minval);
		do {
			oldprec = xprec;
			dx = (compute_label_width(hDC, &m_X, xprec)+40)*m_X.PxlSize;
			delta = pow(10.0, floor(log10(dx)));
			if (delta>dx)
				xstep = delta;
			else if (2.0 * delta>dx)
				xstep = 2.0 * delta;
			else if (5.0 * delta>dx)
				xstep = 5.0 * delta;
			else
				xstep = 10.0 * delta;
			xprec = LimitValue(step_to_prec(xstep), m_X.MinPrec, m_X.MaxPrec);
		} while (xprec > oldprec);
		break;
	}

	if (step)
		*step = xstep;
	if (minprec)
		*minprec = xprec;
}

/* Compute horizontal grid step and distance between two labels on horizontal scale */
void TCCD_Graph::compute_y_grid_step(HDC hDC, double minval, double maxval, double *step, int *minprec)
{
    int	yprec;
	double dy, ystep, delta;
	
	dy = (compute_label_height(hDC, &m_Y)+32)*m_Y.PxlSize;

	switch (m_Y.Format)
	{
	case FORMAT_INT:
        // Integer mode without decimal places
		delta = pow(10.0, floor(log10(dy)));
		if (delta<1)
			delta = 1;
		if (delta>dy) 
			ystep = delta;
		else if (2.0 * delta>dy)
			ystep = 2.0 * delta;
		else if (5.0 * delta>dy)
			ystep = 5.0 * delta;
		else
			ystep = 10.0 * delta;
		yprec = 0;
		break;

	default:
        // Decimal mode
		delta = pow(10.0, floor(log10(dy)));
		if (delta>dy)
			ystep = delta;
		else if (2.0 * delta>dy)
			ystep = 2.0 * delta;
		else if (5.0 * delta>dy)
			ystep = 5.0 * delta;
		else
			ystep = 10.0 * delta;
		yprec = LimitValue(step_to_prec(ystep), m_Y.MinPrec, m_Y.MaxPrec);
		break;
	}

	if (step)
		*step = ystep;
	if (minprec) 
		*minprec = yprec;
}

//------------------------   PAINTING   -----------------------------------

void TCCD_Graph::draw_text(HDC hDC, int x, int y, const char *buf, double halign, double valign)
{
	SIZE logical_rect;

	if (halign>0 || valign>0) {
		text_extents(hDC, buf, &logical_rect);
		if (halign>0)
			x -= RoundToInt(logical_rect.cx*halign);
		if (valign>0)
			y -= RoundToInt(logical_rect.cy*valign);
	}
	TextOut(hDC, x, y, buf, (int)strlen(buf));
}

/* Paint labels on x-axis scale */
void TCCD_Graph::paint_x_scale(HDC hDC)
{
	char	buf[256];
	double	val, step, xmin, xmax;
	int		x, y, left, right, prec;
	HRGN	rgn;

	/* Visible region in physical units */
	if (!m_X.Reverse) {
		xmin = view_to_x(m_CanvasRc.left);
		xmax = view_to_x(m_CanvasRc.right);
	} else {
		xmin = view_to_x(m_CanvasRc.right);
		xmax = view_to_x(m_CanvasRc.left);
	}
	xmin = LimitValue(xmin, m_X.Min, m_X.Max);
	xmax = LimitValue(xmax, m_X.Min, m_X.Max);

	y = m_X.ScaleRc.top + 10;

	/* Print unit name */
	if (m_X.Caption) {
		rgn = CreateRectRgn(m_X.LabelRc.left, m_X.LabelRc.top, m_X.LabelRc.right, m_X.LabelRc.bottom);
		SelectClipRgn(hDC, rgn);
		draw_text(hDC, m_X.LabelRc.right-4, y, m_X.Caption, 1.0, 0.0);
		SelectClipRgn(hDC, NULL);
		DeleteObject(rgn);
	}

	/* Set clipping region */
	rgn = CreateRectRgn(m_X.ScaleRc.left, m_X.ScaleRc.top, m_X.ScaleRc.right, m_X.ScaleRc.bottom);
	SelectClipRgn(hDC, rgn);
	left = m_X.ScaleRc.left;
	right = m_X.ScaleRc.right;

	if (m_X.Log && log10(xmax/xmin)>3) {
		// Log scale if range is greater than three periods
		xmin = log10(MAX(1e-99, xmin));
		xmax = log10(MAX(1e-99, xmax));
		compute_x_grid_step(hDC, xmin, xmax, &step, &prec);
		xmin = ceil(xmin);
		while (xmin <= xmax) {
			val = pow(10, xmin);
			x = RoundToInt(x_to_view(val));
			if (x >= left && x < right) {
				MoveToEx(hDC, m_X.ScaleRc.top, y, NULL);
				LineTo(hDC, m_X.ScaleRc.top+8, y);
				format_label(buf, 256, val, m_X.Format, prec);
				draw_text(hDC, x, y, buf, 0.5, 0.0);
			}
			xmin += step;
		}
	} else
	if (m_X.Log && log10(xmax/xmin)>1) {
		// Log scale if range is greater than one period
		xmin = log10(MAX(1e-99, xmin));
		xmax = log10(MAX(1e-99, xmax));
		step = 1.0;
		prec = 1;
		xmin = floor(xmin);
		while (xmin <= xmax) {
			val = pow(10, xmin);
			x = RoundToInt(x_to_view(val));
			if (x >= left && x < right) {
				MoveToEx(hDC, x, m_X.ScaleRc.top, NULL);
				LineTo(hDC, x, m_X.ScaleRc.top+8);
				format_label(buf, 256, val, m_X.Format, prec);
				draw_text(hDC, x, y, buf, 0.5, 0.0);
			}
			x = RoundToInt(x_to_view(2.0*val));
			if (x >= left && x < right) {
				MoveToEx(hDC, x, m_X.ScaleRc.top, NULL);
				LineTo(hDC, x, m_X.ScaleRc.top+8);
				format_label(buf, 256, 2.0*val, m_X.Format, prec);
				draw_text(hDC, x, y, buf, 0.5, 0.0);
			}
			x = RoundToInt(x_to_view(5.0*val));
			if (x >= left && x < right) {
				MoveToEx(hDC, x, m_X.ScaleRc.top, NULL);
				LineTo(hDC, x, m_X.ScaleRc.top+8);
				format_label(buf, 256, 5.0*val, m_X.Format, prec);
				draw_text(hDC, x, y, buf, 0.5, 0.0);
			}
			xmin += step;
		}
	} else {
		// Lin scale or log scale if range is smaller than one period
		compute_x_grid_step(hDC, xmin, xmax, &step, &prec);
		xmin = ceil(xmin/step)*step;
		while (xmin <= xmax) {
			val = xmin;
			x = RoundToInt(x_to_view(val));
			if (x >= left && x < right) {
				MoveToEx(hDC, x, m_X.ScaleRc.top, NULL);
				LineTo(hDC, x, m_X.ScaleRc.top+8);
				format_label(buf, 256, val, m_X.Format, prec);
				draw_text(hDC, x, y, buf, 0.5, 0.0);
			}
			xmin += step;
		}
	}
	SelectClipRgn(hDC, NULL);
	DeleteObject(rgn);
}

/* Paint labels on y-axis scale */
void TCCD_Graph::paint_y_scale(HDC hDC)
{
	char	buf[256];
	double	val, step, ymin, ymax;
	int		x, y, top, bottom, prec;
	HRGN	rgn;

    /* Visible region in physical units */
	if (!m_Y.Reverse) {
		ymin = view_to_y(m_CanvasRc.bottom); 
		ymax = view_to_y(m_CanvasRc.top);
	} else {
		ymin = view_to_y(m_CanvasRc.top); 
		ymax = view_to_y(m_CanvasRc.bottom);
	}	
	ymin = LimitValue(ymin, m_Y.Min, m_Y.Max);
	ymax = LimitValue(ymax, m_Y.Min, m_Y.Max);

	x = m_Y.ScaleRc.right - 10;

	/* Print unit name */
	if (m_Y.Caption) {
		rgn = CreateRectRgn(m_Y.LabelRc.left, m_Y.LabelRc.top, m_Y.LabelRc.right, m_Y.LabelRc.bottom);
		SelectClipRgn(hDC, rgn);
		draw_text(hDC, x, m_Y.LabelRc.top + 2, m_Y.Caption, 1.0, 0.0);
		SelectClipRgn(hDC, NULL);
		DeleteObject(rgn);
	}

	/* Set clipping region */
	rgn = CreateRectRgn(m_Y.ScaleRc.left, m_Y.ScaleRc.top, m_Y.ScaleRc.right, m_Y.ScaleRc.bottom);
	SelectClipRgn(hDC, rgn);
	top = m_Y.ScaleRc.top;
	bottom = m_Y.ScaleRc.bottom;

	if (m_Y.Log && log10(ymax/ymin)>3) {
		// Log scale if range is greater than three periods
		ymin = log10(MAX(1e-99, ymin));
		ymax = log10(MAX(1e-99, ymax));
		compute_y_grid_step(hDC, ymin, ymax, &step, &prec);
		ymin = ceil(ymin);
		while (ymin <= ymax) {
			val = pow(10, ymin);
			y = RoundToInt(y_to_view(val));
			if (y >= top && y < bottom) {
				MoveToEx(hDC, m_Y.ScaleRc.right, y, NULL);
				LineTo(hDC, m_Y.ScaleRc.right-8, y);
				format_label(buf, 256, val, m_Y.Format, prec);
				draw_text(hDC, x, y, buf, 1.0, 0.5);
			}
			ymin += step;
		}
	} else
	if (m_Y.Log && log10(ymax/ymin)>1) {
		// Log scale if range is greater than one period
		ymin = log10(MAX(1e-99, ymin));
		ymax = log10(MAX(1e-99, ymax));
		step = 1.0;
		prec = 1;
		ymin = floor(ymin);
		while (ymin <= ymax) {
			val = pow(10, ymin);
			y = RoundToInt(y_to_view(val));
			if (y >= top && y < bottom) {
				MoveToEx(hDC, m_Y.ScaleRc.right, y, NULL);
				LineTo(hDC, m_Y.ScaleRc.right-8, y);
				format_label(buf, 256, val, m_Y.Format, prec);
				draw_text(hDC, x, y, buf, 1.0, 0.5);
			}
			y = RoundToInt(y_to_view(2.0*val));
			if (y >= top && y < bottom) {
				MoveToEx(hDC, m_Y.ScaleRc.right, y, NULL);
				LineTo(hDC, m_Y.ScaleRc.right-8, y);
				format_label(buf, 256, 2.0*val, m_Y.Format, prec);
				draw_text(hDC, x, y, buf, 1.0, 0.5);
			}
			y = RoundToInt(y_to_view(5.0*val));
			if (y >= top && y < bottom) {
				MoveToEx(hDC, m_Y.ScaleRc.right, y, NULL);
				LineTo(hDC, m_Y.ScaleRc.right-8, y);
				format_label(buf, 256, 5.0*val, m_Y.Format, prec);
				draw_text(hDC, x, y, buf, 1.0, 0.5);
			}
			ymin += step;
		}
	} else {
		// Lin scale or log scale if range is smaller than one period
		compute_y_grid_step(hDC, ymin, ymax, &step, &prec);
		ymin = ceil(ymin/step)*step;
		while (ymin <= ymax) {
			val = ymin;
			y = RoundToInt(y_to_view(val));
			if (y >= top && y < bottom) {
				MoveToEx(hDC, m_Y.ScaleRc.right, y, NULL);
				LineTo(hDC, m_Y.ScaleRc.right-8, y);
				format_label(buf, 256, val, m_Y.Format, prec);
				draw_text(hDC, x, y, buf, 1.0, 0.5);
			}
			ymin += step;
		}
	}
	SelectClipRgn(hDC, NULL);
	DeleteObject(rgn);
}

/* Paints x-axis grid */
void TCCD_Graph::paint_x_grid(HDC hDC)
{
	double	val, step, xmin, xmax;
	int	x, left, right, top, bottom;

    /* Visible region in physical units */
	if (!m_X.Reverse) {
		xmin = view_to_x(m_CanvasRc.left);
		xmax = view_to_x(m_CanvasRc.right);
	} else {
		xmin = view_to_x(m_CanvasRc.right);
		xmax = view_to_x(m_CanvasRc.left);
	}
	xmin = LimitValue(xmin, m_X.Min, m_X.Max);
	xmax = LimitValue(xmax, m_X.Min, m_X.Max);

	left = m_CanvasRc.left;
	right = m_CanvasRc.right;
	top = m_CanvasRc.top;
	bottom = m_CanvasRc.bottom;

	if (m_X.Log && log10(xmax/xmin)>3) {
		// Log scale if range is greater than three periods
		xmin = log10(MAX(1e-99, xmin));
		xmax = log10(MAX(1e-99, xmax));
		compute_x_grid_step(hDC, xmin, xmax, &step, NULL);
		xmin = ceil(xmin);
		while (xmin <= xmax) {
			val = pow(10, xmin);
			x = RoundToInt(x_to_view(val));
			if (x >= left && x < right) {
				MoveToEx(hDC, x, top, NULL);
				LineTo(hDC, x, bottom);
			}
			xmin += step;
		}
	} else
	if (m_X.Log && log10(xmax/xmin)>1) {
		// Log scale if range is greater than one period
		xmin = log10(MAX(1e-99, xmin));
		xmax = log10(MAX(1e-99, xmax));
		step = 1.0;
		xmin = floor(xmin);
		while (xmin <= xmax) {
			val = pow(10, xmin);
			x = RoundToInt(x_to_view(val));
			if (x >= left && x < right) {
				MoveToEx(hDC, x, top, NULL);
				LineTo(hDC, x, bottom);
			}
			x = RoundToInt(x_to_view(2.0*val));
			if (x >= left && x < right) {
				MoveToEx(hDC, x, top, NULL);
				LineTo(hDC, x, bottom);
			}
			x = RoundToInt(x_to_view(5.0*val));
			if (x >= left && x < right) {
				MoveToEx(hDC, x, top, NULL);
				LineTo(hDC, x, bottom);
			}
			xmin += step;
		}
	} else {
		// Lin scale or log scale if range is smaller than one period
		compute_x_grid_step(hDC, xmin, xmax, &step, NULL);
		xmin = ceil(xmin/step)*step;
		while (xmin <= xmax) {
			val = xmin;
			x = RoundToInt(x_to_view(val));
			if (x >= left && x < right) {
				MoveToEx(hDC, x, top, NULL);
				LineTo(hDC, x, bottom);
			}
			xmin += step;
		}
	}
}

/* Paint y-axis grid */
void TCCD_Graph::paint_y_grid(HDC hDC)
{
	double	val, step, ymin, ymax;
	int	y, left, right, top, bottom;

    /* Visible region in physical units */
	if (!m_Y.Reverse) {
		ymin = view_to_y(m_CanvasRc.bottom); 
		ymax = view_to_y(m_CanvasRc.top);
	} else {
		ymin = view_to_y(m_CanvasRc.top); 
		ymax = view_to_y(m_CanvasRc.bottom);
	}	
	ymin = LimitValue(ymin, m_Y.Min, m_Y.Max);
	ymax = LimitValue(ymax, m_Y.Min, m_Y.Max);

	/* Set clipping region */
	left = m_CanvasRc.left;
	right = m_CanvasRc.right;
	top = m_CanvasRc.top;
	bottom = m_CanvasRc.bottom;

	if (m_Y.Log && log10(ymax/ymin)>3) {
		// Log scale if range is greater than three periods
		ymin = log10(MAX(1e-99, ymin));
		ymax = log10(MAX(1e-99, ymax));
		compute_y_grid_step(hDC, ymin, ymax, &step, NULL);
		ymin = ceil(ymin);
		while (ymin <= ymax) {
			val = pow(10, ymin);
			y = RoundToInt(y_to_view(val));
			if (y >= top && y < bottom) {
				MoveToEx(hDC, left, y, NULL);
				LineTo(hDC, right, y);
			}
			ymin += step;
		}
	} else
	if (m_Y.Log && log10(ymax/ymin)>1) {
		// Log scale if range is greater than one period
		ymin = log10(MAX(1e-99, ymin));
		ymax = log10(MAX(1e-99, ymax));
		step = 1.0;
		ymin = floor(ymin);
		while (ymin <= ymax) {
			val = pow(10, ymin);
			y = RoundToInt(y_to_view(val));
			if (y >= top && y < bottom) {
				MoveToEx(hDC, left, y, NULL);
				LineTo(hDC, right, y);
			}
			y = RoundToInt(y_to_view(2.0*val));
			if (y >= top && y < bottom) {
				MoveToEx(hDC, left, y, NULL);
				LineTo(hDC, right, y);
			}
			y = RoundToInt(y_to_view(5.0*val));
			if (y >= top && y < bottom) {
				MoveToEx(hDC, left, y, NULL);
				LineTo(hDC, right, y);
			}
			ymin += step;
		}
	} else {
		// Lin scale or log scale if range is smaller than one period
		compute_y_grid_step(hDC, ymin, ymax, &step, NULL);
		ymin = ceil(ymin/step)*step;
		while (ymin <= ymax) {
			val = ymin;
			y = RoundToInt(y_to_view(val));
			if (y >= top && y < bottom) {
				MoveToEx(hDC, left, y, NULL);
				LineTo(hDC, right, y);
			}
			ymin += step;
		}
	}
}

// HSB changed
void TCCD_Graph::OnHSBChanged(int nPos)
{
	double pos = m_X.ProjMin + m_X.PxlSize * (nPos + m_X.Center - m_GraphRc.left);
	if (m_X.ProjPos != pos) {
		m_X.ProjPos = pos;
		restrict_x_to_limits();
		Invalidate();
	}
}

// VSB changed
void TCCD_Graph::OnVSBChanged(int nPos)
{
	double pos = m_Y.ProjMax - m_Y.PxlSize * (nPos + m_Y.Center - m_GraphRc.left);
	if (m_Y.ProjPos != pos) {
		m_Y.ProjPos = pos;
		restrict_y_to_limits();
		Invalidate();
	}
}

// Keyboard event handler
void TCCD_Graph::OnKeyDown(int vKey)
{
	switch (vKey) 
	{
	case VK_ADD:
		SelectChannel(m_ChannelY+1);
		break;
	case VK_SUBTRACT:
		SelectChannel(m_ChannelY-1);
		break;
	case VK_MULTIPLY:
		SetPointSize(m_PtSize+1);
		break;
	case VK_DIVIDE:
		SetPointSize(m_PtSize-1);
		break;
	default:
		TCCD_Interface::OnKeyDown(vKey);
		break;
	}
}

void TCCD_Graph::SetPointSize(int size)
{
	m_PtSize = LimitValue(size, PT_SIZE_MIN, PT_SIZE_MAX);
	UpdateData();
	Invalidate();
}

void TCCD_Graph::SelectChannel(int channel)
{
	m_ChannelY = LimitValue(channel, 0, m_ChannelsY.Count()-1);
	UpdateData();
	Invalidate();
}

bool TCCD_Graph::set_x_axis(double zoom, double center)
{
	zoom = LimitValue(zoom, 0.0, m_X.ZoomMax);
	center = LimitValue(center, m_X.ProjMin, m_X.ProjMax);
	if (zoom!=m_X.ZoomPos || center!=m_X.ProjPos) {
		m_X.ZoomPos = zoom;
		m_X.ProjPos = center;
		if (m_GraphWidth>0) {
			update_x_pxlsize();
			restrict_x_to_limits();
			update_hsb();
		}
		return true;
	}
	return false;
}

bool TCCD_Graph::set_auto_zoom_x(void)
{
	int i, first;
    double zoom, xmin, xmax, a, b;

	xmin = xmax = 0.0;
	first = 1;
	for (i=0; i<m_Count; i++) {
		GraphItem *item = m_Data + i;
		if (first) {
			xmin = xmax = item->xproj;
			first = 0;
		} else {
			if (item->xproj < xmin)
				xmin = item->xproj;
			if (item->xproj > xmax)
				xmax = item->xproj;
		}
	}
	if (first)
		return false;

	xmin = LimitValue(xmin, m_X.ProjMin, m_X.ProjMax);
	xmax = LimitValue(xmax, m_X.ProjMin, m_X.ProjMax);
	if ((xmax-xmin)<m_X.ProjEps) {
		a = (xmax + xmin - m_X.ProjEps)/2.0;
		b = (xmax + xmin + m_X.ProjEps)/2.0;
	} else {
		a = xmin;
		b = xmax;
    }
	zoom = log(fabs(m_X.ProjMax-m_X.ProjMin)/fabs(xmax-xmin))/log(m_ZoomBase);
	return set_x_axis(zoom, 0.5*(a+b));
}

bool TCCD_Graph::set_y_axis(double zoom, double center)
{
	zoom = LimitValue(zoom, 0.0, m_Y.ZoomMax);
	center = LimitValue(center, m_Y.ProjMin, m_Y.ProjMax);
	if (zoom!=m_Y.ZoomPos || center!=m_Y.ProjPos) {
		m_Y.ZoomPos = zoom;
		m_Y.ProjPos = center;
		if (m_GraphHeight>0) {
			update_y_pxlsize();
			restrict_y_to_limits();
			update_vsb();
		}
		return true;
	}
	return false;
}

bool TCCD_Graph::set_auto_zoom_y(void)
{
	int i, first;
	double f, a, b, zoom, xmin, xmax, ymin, ymax;

	ymin = ymax = 0.0;
	xmin = view_to_xproj(m_CanvasRc.left);
	xmax = view_to_xproj(m_CanvasRc.right);
	first = 1;
	for (i=0; i<m_Count; i++) {
		GraphItem *item = m_Data + i;
		if (m_Data->xproj>=xmin && m_Data->xproj<xmax) {
			if (first) {
				ymin = ymax = item->yproj;
				first = 0;
			} else {
				if (item->yproj - item->error < ymin)
					ymin = item->yproj - item->error;
				if (item->yproj + item->error > ymax)
					ymax = item->yproj + item->error;
			}
		}
	}
	if (first)
		return false;

	ymin = LimitValue(ymin, m_Y.ProjMin, m_Y.ProjMax);
	ymax = LimitValue(ymax, m_Y.ProjMin, m_Y.ProjMax);
	if ((ymax-ymin) < m_Y.ProjEps) {
		f = ((ymin+ymax)/2.0 - m_Y.ProjMin)/(m_Y.ProjMax-m_Y.ProjMin);
        a = ymin - f * (m_Y.ProjEps - fabs(ymax - ymin));
		b = ymax + (1.0-f) * (m_Y.ProjEps - fabs(ymax - ymin));
    } else {
        a = ymin;
        b = ymax;
    }
	zoom = log(fabs(m_Y.ProjMax-m_Y.ProjMin)/fabs(b-a))/log(m_ZoomBase);
	return set_y_axis(zoom, 0.5*(a+b));
}

// Set title
void TCCD_Graph::SetTitle(const char *caption)
{
	free(m_Title);
	m_Title = StrDup(caption);
	Invalidate();
}

/* Set mapping parameters */
void TCCD_Graph::SetAxisX(bool log_scale, bool reverse, double min, double max, double eps, 
		GraphFormat format, int minprec, int maxprec, const char *caption)
{
	m_X.Log = log_scale;
	m_X.Reverse = reverse;
	m_X.Format = format;
	m_X.MinPrec = LimitValue(minprec, 0, 16);
	m_X.MaxPrec = LimitValue(maxprec, minprec, 16);
	free(m_X.Caption);
	m_X.Caption = StrDup(caption);
	if (!m_X.Log) {
		m_X.Log = false;
		m_X.Min = min;
		m_X.Max = max;
		if (!m_X.Reverse) {
			m_X.ProjMin = min;
			m_X.ProjMax = max;
		} else {
			m_X.ProjMin = -max;
			m_X.ProjMax = -min;
		}
        m_X.ProjEps = eps;
	} else {
		m_X.Log = true;
		m_X.Min = MAX(1e-99, min);
		m_X.Max = MAX(1e-99, max);
		if (!m_X.Reverse) {
			m_X.ProjMin = log10(m_X.Min);
			m_X.ProjMax = log10(m_X.Max);
		} else {
			m_X.ProjMin = -log10(m_X.Max);
			m_X.ProjMax = -log10(m_X.Min);
		}
        m_X.ProjEps = log10(MAX(1e-99, eps));
	}
    m_X.ZoomMax = log((m_X.ProjMax-m_X.ProjMin)/m_X.ProjEps) / log(m_ZoomBase);
    m_X.ZoomMax = MAX(0.0, m_X.ZoomMax);
	m_X.ZoomPos = LimitValue(m_X.ZoomPos, 0.0, m_X.ZoomMax);

	if (m_GraphWidth>0 && m_GraphHeight>0) {
		update_rectangles();
		update_x_pxlsize();
		restrict_x_to_limits();
		update_y_pxlsize();
		restrict_y_to_limits();
		update_hsb();
		update_vsb();
		Invalidate();
	}
}

/* Set mapping parameters */
void TCCD_Graph::SetAxisY(bool log_scale, bool reverse, double min, double max, double eps, 
	GraphFormat format, int minprec, int maxprec, const char *caption)
{
	m_Y.Log = log_scale;
	m_Y.Reverse = reverse;
	m_Y.Format = format;
	m_Y.MinPrec = LimitValue(minprec, 0, 16);
	m_Y.MaxPrec = LimitValue(maxprec, minprec, 16);
	free(m_Y.Caption);
	m_Y.Caption = StrDup(caption);
	if (!m_Y.Log) {
		m_Y.Log = false;
		m_Y.Min = min;
		m_Y.Max = max;
		if (!m_Y.Reverse) {
			m_Y.ProjMin = min;
			m_Y.ProjMax = max;
		} else {
			m_Y.ProjMin = -max;
			m_Y.ProjMax = -min;
		}
        m_Y.ProjEps = eps;
	} else {
		m_Y.Log = true;
		m_Y.Min = MAX(1e-99, min);
		m_Y.Max = MAX(1e-99, max);
		if (!m_Y.Reverse) {
			m_Y.ProjMin = log10(m_Y.Min);
			m_Y.ProjMax = log10(m_Y.Max);
		} else {
			m_Y.ProjMin = -log10(m_Y.Max);
			m_Y.ProjMax = -log10(m_Y.Min);
		}
        m_Y.ProjEps = log10(MAX(1e-99, eps));
	}
    m_Y.ZoomMax = log((m_Y.ProjMax-m_Y.ProjMin)/m_Y.ProjEps) / log(m_ZoomBase);
    m_Y.ZoomMax = MAX(0.0, m_Y.ZoomMax);
	m_Y.ZoomPos = LimitValue(m_Y.ZoomPos, 0.0, m_Y.ZoomMax);

	if (m_GraphWidth>0 && m_GraphHeight>0) {
		update_rectangles();
		update_x_pxlsize();
		restrict_x_to_limits();
		update_y_pxlsize();
		restrict_y_to_limits();
		update_hsb();
		update_vsb();
		Invalidate();
	}
}

/* Set labels */
void TCCD_Graph::SetScales(bool x_axis, bool y_axis)
{
	if (m_X.ShowLabels!=x_axis || m_Y.ShowLabels!=y_axis) {
		m_X.ShowLabels = x_axis;
		m_Y.ShowLabels = y_axis;
		if (m_GraphWidth>0 && m_GraphHeight>0) {
			update_rectangles();
			update_x_pxlsize();
			restrict_x_to_limits();
			update_y_pxlsize();
			restrict_y_to_limits();
			update_hsb();
			update_vsb();
			Invalidate();
		}
	}
} 

void TCCD_Graph::SetGrid(bool x_axis, bool y_axis)
{
	if (m_X.ShowGrid != x_axis || m_Y.ShowGrid != y_axis) {
		m_X.ShowGrid = x_axis;
		m_Y.ShowGrid = y_axis;
		Invalidate();
	}
}

// Makes preview and returns the HBITMAP
HBITMAP TCCD_Graph::MakePreview(int max_width, int max_height)
{
	int		i, w, h, x, y, dst_width, dst_height, row_width;
	unsigned char *bits = NULL;

	if (!m_Tab || !m_Data)
		return NULL;

	// Compute thumbnail size
	CmpackTableType ttype = cmpack_tab_get_type(m_Tab);
	if (ttype!=CMPACK_TABLE_MAGDEV && ttype!=CMPACK_TABLE_UNSPECIFIED) {
		dst_height = MulDiv(max_width, 2, 3);
		if (dst_height <= max_height) {
			w = max_width;
			h = MAX(1, dst_height);
		} else {
			dst_width = MulDiv(max_height, max_width, dst_height);
			w = MAX(1, dst_width);
			h = max_height;
		}
	} else {
		w = max_width;
		h = max_height;
	}
	SetRect(&m_CanvasRc, 0, 0, w, h);
	SetRect(&m_GraphRc, 0, 0, w, h);
	m_GraphWidth = w;
	m_GraphHeight = h;
	update_x_pxlsize();
	restrict_x_to_limits();
	update_y_pxlsize();
	restrict_y_to_limits();
	if (m_X.PxlSize<=0 || m_Y.PxlSize<=0)
		return NULL;

	HDC dc_main = GetDC(GetDesktopWindow());
	HBITMAP bmp_thumbnail = MakeDIBSection(dc_main, w, h, (void**)&bits, &row_width);
	ReleaseDC(GetDesktopWindow(), dc_main);
	if (bmp_thumbnail && bits) {
		// Fill background
		memset(bits, 0xFF, row_width*h);
		// Draw data
		for (i=0; i<m_Count; i++) {
			GraphItem *item = m_Data + i;
			x = RoundToInt(xproj_to_view(item->xproj));
			y = RoundToInt(yproj_to_view(item->yproj));
			if (x>=m_CanvasRc.left && x<m_CanvasRc.right &&
				y>=m_CanvasRc.top && y<m_CanvasRc.bottom) 
					bits[(h-y-1)*row_width+x] = 0;
		}
	}
	return bmp_thumbnail;
}

// Create base menu
HMENU TCCD_Graph::CreateCtxMenu(void)
{
	int pos = 0;
	HMENU hMenu, hPopup, hSubMenu;

	hMenu = TCCD_Interface::CreateCtxMenu();
	hPopup = GetSubMenu(hMenu, 0);

	InsertMenu(hPopup, pos++, MF_BYPOSITION | MF_STRING, CMD_PROPERTIES, "Properties...");
	InsertMenu(hPopup, pos++, MF_BYPOSITION | MF_SEPARATOR, 0, NULL);

	if (m_ChannelsY.Count()>1) {
		hSubMenu = CreateMenu();
		for (int i=0; i<m_ChannelsY.Count(); i++) 
			InsertMenu(hSubMenu, i, MF_BYPOSITION | MF_STRING, CMD_CHANNEL_FIRST+i, m_ChannelsY.GetName(i));
		CheckMenuRadioItem(hSubMenu, 0, m_ChannelsY.Count()-1, m_ChannelY, MF_BYPOSITION);
		InsertMenu(hPopup, pos++, MF_BYPOSITION | MF_POPUP | MF_STRING, (UINT_PTR)hSubMenu, "Y axis");
	}

	hSubMenu = CreateMenu();
	InsertMenu(hSubMenu, 0, MF_BYPOSITION | MF_STRING, CMD_SIZE_FIRST+0, "Large");
	InsertMenu(hSubMenu, 1, MF_BYPOSITION | MF_STRING, CMD_SIZE_FIRST+1, "Larger");
	InsertMenu(hSubMenu, 2, MF_BYPOSITION | MF_STRING, CMD_SIZE_FIRST+2, "Default");
	InsertMenu(hSubMenu, 3, MF_BYPOSITION | MF_STRING, CMD_SIZE_FIRST+3, "Smaller");
	InsertMenu(hSubMenu, 4, MF_BYPOSITION | MF_STRING, CMD_SIZE_FIRST+4, "Small");
	CheckMenuRadioItem(hSubMenu, 0, 6, PT_SIZE_MAX-m_PtSize, MF_BYPOSITION);
	InsertMenu(hPopup, pos++, MF_BYPOSITION | MF_POPUP | MF_STRING, (UINT_PTR)hSubMenu, "Symbol size");

	InsertMenu(hPopup, pos++, MF_BYPOSITION | MF_SEPARATOR, 0, NULL);

	InsertMenu(hPopup, pos++, MF_BYPOSITION | MF_STRING, CMD_COPY, "Copy to clipboard");
	InsertMenu(hPopup, pos++, MF_BYPOSITION | MF_SEPARATOR, 0, NULL);

	return hMenu;
}

// Context menu commands
void TCCD_Graph::OnMenuCommand(int iCmd)
{
	if (iCmd>=CMD_SIZE_FIRST && iCmd<=CMD_SIZE_FIRST+6) {
		SetPointSize(PT_SIZE_MAX-(iCmd-CMD_SIZE_FIRST));
		return;
	}
	if (iCmd>=CMD_CHANNEL_FIRST && iCmd<CMD_CHANNEL_FIRST+m_ChannelsY.Count()) {
		SelectChannel(iCmd-CMD_CHANNEL_FIRST);
		return;
	}
	if (iCmd==CMD_PROPERTIES) {
		ShowProperties();
		return;
	}
	if (iCmd==CMD_COPY) {
		CopyToClipboard();
		return;
	}
	TCCD_Interface::OnMenuCommand(iCmd);
}

// Context menu commands
void TCCD_Graph::ShowProperties(void)
{
	CTableProperties dlg(this);
	dlg.ShowModal(m_hWnd);
}

// Copy image to clipboard
void TCCD_Graph::CopyToClipboard(void)
{
	int cvs_w, cvs_h;

	cvs_w = m_ClientWidth;
	cvs_h = m_ClientHeight;
	if (OpenClipboard(GetParent(m_hWnd))) {
		EmptyClipboard();
		HDC wnd_dc = GetDC(m_hWnd);
		HDC hDC = CreateCompatibleDC(wnd_dc);
		HBITMAP hBmp = CreateCompatibleBitmap(wnd_dc, cvs_w, cvs_h);
		ReleaseDC(m_hWnd, wnd_dc);
		HBITMAP oldbmp = (HBITMAP)SelectObject(hDC, hBmp);
		RenderGraph(hDC, 0, 0, cvs_w, cvs_h, true);
		SelectObject(hDC, oldbmp);
		DeleteDC(hDC);
		SetClipboardData(CF_BITMAP, hBmp);
		CloseClipboard();
	}
}

// Get topic id (help page opened)
int TCCD_Graph::GetTopicId(void)
{
	return IDH_GRAPH_VIEW;
}
