Click here to Skip to main content
15,881,803 members
Articles / Web Development / HTML

2D Graph ActiveX Control in C++ with ATL (No MFC Dependency)

Rate me:
Please Sign up or sign in to vote.
4.92/5 (25 votes)
8 Mar 2012CPOL7 min read 122.8K   11.9K   93  
Plots multiple data sets, interactive tooltip info, zoom/pan, edit color/width/format, annotations, print/save
// (c) Copyright The Code Project Open License (CPOL)
//  http://www.codeproject.com/info/cpol10.aspx
//
//  Marius Samoila, Nikolai Teofilov, 2011
//
// FILE NAME
//		GraphElement.cpp: implementation for the CGraphElement class
//
// CLASS NAME
//		CGraphElement
//
// DESCRIPTION
// 
// MODIFICATIONS
//		01-Dec-2011 MSamoila major re-design of old CGraphElement
//
#include "stdafx.h"
#include "DMGraph.h"
#include "GraphElement.h"
#include "DMGraphCtrl.h"

static HRESULT WINAPI GetArrayFromVariant(VARIANT* pVar, SAFEARRAY** pparray);
static HRESULT WINAPI GetDoubleElement(VARTYPE type, SAFEARRAY* pSA, long idx, double* pRetVal);

CGraphElement::CGraphElement()
{
	m_LineColor = m_PointColor = RGB(255, 255, 255);
	m_nType			= Solid;
	m_nLineWidth	= 0 ;
	m_nSymbol		= Nosym;
	m_bSolid		= VARIANT_TRUE;
	m_nPointSize	= 0 ;
	m_bShow			= VARIANT_TRUE;      // Added by A. Hofmann

	min = max = CElementPoint(0,0);		
}

void CGraphElement::AttachToGraph(CDMGraphCtrl* pCtrl, int id)
{
	CGraphItem::AttachToGraph(pCtrl, id);

	const int TXT_LEN = 128;
	OLECHAR szText[ TXT_LEN ];
	_snwprintf(szText, TXT_LEN, L"Element-%d", m_nID);
	m_strName = szText;
}

void CGraphElement::DetachFromGraph()
{
	ATLASSERT(m_pCtrl);

	if(m_PointList.GetSize())
		m_pCtrl->OnPlotDeleted();

	CGraphItem::DetachFromGraph();
}

BOOL CGraphElement::FindPoint(double x, double y, CElementPoint* pResult)
{
	ATLASSERT(pResult);
	int i, index = -1;

	double dx, dy, dr;

	for(i=0; i<m_PointList.GetSize(); i++)
	{
	    CElementPoint point = m_PointList[i];

		dx = fabs(x - point.x);
		dy = fabs(y - point.y);

		if(i == 0)
			dr = sqrt(dx + dy);
		else
		{
			if (sqrt(dx+dy) < dr )
			{
				dr = sqrt(dx+dy);
				index = i;
			}
		}
	}
	if(index<0)
		return FALSE;	//not found

	pResult->x = m_PointList[index].x;
	pResult->y = m_PointList[index].y;
	return TRUE;
}

STDMETHODIMP CGraphElement::get_LineColor(OLE_COLOR *pVal)
{
	if(pVal == NULL)
		return E_POINTER;
	
	*pVal = m_LineColor;
	return S_OK;
}

STDMETHODIMP CGraphElement::put_LineColor(OLE_COLOR newVal)
{
	HRESULT hr;
	COLORREF col;
	hr = OleTranslateColor(newVal, NULL, &col);

	if(m_LineColor == col)
		return S_FALSE;

	m_LineColor = col;

	if(m_pCtrl)
		m_pCtrl->Refresh(TRUE);

	return S_OK;
}

STDMETHODIMP CGraphElement::get_PointColor(OLE_COLOR *pVal)
{
	if(pVal == NULL)
		return E_POINTER;
	
	*pVal = m_PointColor;
	return S_OK;
}

STDMETHODIMP CGraphElement::put_PointColor(OLE_COLOR newVal)
{

	HRESULT hr;
	COLORREF col;
	hr = OleTranslateColor(newVal, NULL, &col);

	if(m_PointColor == col)
		return S_FALSE;

	m_PointColor = col;

	if(m_pCtrl)
		m_pCtrl->Refresh(TRUE);
	return S_OK;
}

STDMETHODIMP CGraphElement::get_Linetype(LineType *pVal)
{
	if(pVal == NULL)
		return E_POINTER;
	
	*pVal = m_nType;
	return S_OK;
}

STDMETHODIMP CGraphElement::put_Linetype(LineType newVal)
{
	if(m_nType == newVal)
		return S_FALSE;

	m_nType = newVal;

	if(m_pCtrl)
		m_pCtrl->Refresh(TRUE);
	return S_OK;
}

STDMETHODIMP CGraphElement::get_LineWidth(long *pVal)
{
	if(pVal == NULL)
		return E_POINTER;
	
	*pVal = m_nLineWidth;
	return S_OK;
}

STDMETHODIMP CGraphElement::put_LineWidth(long newVal)
{
	if(m_nLineWidth == newVal)
		return S_FALSE;

	if(newVal > 5)
		newVal = 5;
	if(newVal < 0)
		newVal = 0;

	m_nLineWidth = newVal;

	if(m_pCtrl)
		m_pCtrl->Refresh(TRUE);
	return S_OK;
}

STDMETHODIMP CGraphElement::get_PointSymbol(SymbolType *pVal)
{
	if(pVal == NULL)
		return E_POINTER;

	*pVal = m_nSymbol;
	return S_OK;
}

STDMETHODIMP CGraphElement::put_PointSymbol(SymbolType newVal)
{
	if(m_nSymbol == newVal)
		return S_FALSE;

	m_nSymbol = newVal;

	if(m_pCtrl)
		m_pCtrl->Refresh(TRUE);
	return S_OK;
}

STDMETHODIMP CGraphElement::get_SolidPoint(VARIANT_BOOL *pVal)
{
	if(pVal == NULL)
		return E_POINTER;

	*pVal = m_bSolid;
	return S_OK;
}

STDMETHODIMP CGraphElement::put_SolidPoint(VARIANT_BOOL newVal)
{
	if(m_bSolid == newVal)
		return S_FALSE;

	m_bSolid = newVal;

	if(m_pCtrl)
		m_pCtrl->Refresh(TRUE);
	return S_OK;
}

STDMETHODIMP CGraphElement::get_PointSize(long *pVal)
{
	if(pVal == NULL)
		return E_POINTER;
	
	*pVal = m_nPointSize;
	return S_OK;
}

STDMETHODIMP CGraphElement::put_PointSize(long newVal)
{
	if(m_nPointSize == newVal)
		return S_FALSE;

	if(newVal > 5)
		newVal = 5;
	if(newVal < 0)
		newVal = 0;

	m_nPointSize = newVal;

	if(m_pCtrl)
		m_pCtrl->Refresh(TRUE);
	return S_OK;
}

STDMETHODIMP CGraphElement::get_Show(VARIANT_BOOL *pVal)
{
	if(pVal == NULL)
		return E_POINTER;
	
	*pVal = m_bShow;
	return S_OK;
}

STDMETHODIMP CGraphElement::put_Show(VARIANT_BOOL newVal)
{
	if(m_bShow == newVal)
		return S_FALSE;

	m_bShow = newVal;

	if(m_pCtrl)
		m_pCtrl->Refresh(TRUE);
	return S_OK;
}


STDMETHODIMP CGraphElement::get_Name(BSTR *pVal)
{
	return m_strName.CopyTo(pVal);
}

STDMETHODIMP CGraphElement::put_Name(BSTR newVal)
{
	if(wcscmp(m_strName, newVal) == 0)
		return S_FALSE;

	m_strName = newVal;

	if(m_pCtrl)
		m_pCtrl->Refresh(TRUE);
	return S_OK;
}

STDMETHODIMP CGraphElement::get_Count(/*[out, retval]*/ long *pVal)
{
	if(pVal == NULL)
		return E_POINTER;
	*pVal = m_PointList.GetSize();
	return S_OK;
}

///////////////////////////////////////////////////
// Get X point value at point index 
STDMETHODIMP CGraphElement::get_XValue(/*[in]*/ long index, /*[out, retval]*/ double *pVal)
{
	if(index < 0 || index > m_PointList.GetSize())
		return AtlReportError(CLSID_NULL, L"Point not found!", IID_NULL, E_INVALIDARG);
	if(pVal == NULL)
		return E_POINTER;
	*pVal = m_PointList[index].x;
	return S_OK;
}
//////////////////////////////////////////////////
// Set X point value at point index 
STDMETHODIMP CGraphElement::put_XValue(/*[in]*/ long index, /*[in]*/ double newVal)
{
	if(index < 0 || index > m_PointList.GetSize())
		return AtlReportError(CLSID_NULL, L"Point not found!", IID_NULL, E_INVALIDARG);
	m_PointList[index].x = newVal;
	if(m_pCtrl)
		m_pCtrl->Refresh(FALSE);
	return S_OK;
}

////////////////////////////////////////////////////
// Get Y point value at point index
STDMETHODIMP CGraphElement::get_YValue(/*[in]*/ long index, /*[out, retval]*/ double *pVal)
{
	if(index < 0 || index > m_PointList.GetSize())
		return AtlReportError(CLSID_NULL, L"Point not found!", IID_NULL, E_INVALIDARG);
	if(pVal == NULL)
		return E_POINTER;
	*pVal = m_PointList[index].y;
	return S_OK;
}
///////////////////////////////////////////////////////
// Get Y point value at point index 
STDMETHODIMP CGraphElement::put_YValue(/*[in]*/ long index, /*[in]*/ double newVal)
{
	if(index < 0 || index > m_PointList.GetSize())
		return E_INVALIDARG;
	m_PointList[index].y = newVal;
	if(m_pCtrl)
		m_pCtrl->Refresh(FALSE);
	return S_OK;
}

///////////////////////////
// Set element XY data
STDMETHODIMP CGraphElement::PlotXY(double X, double Y) 
{

	CElementPoint point(X, Y);

	m_PointList.Add(point);
	if(min.x > point.x)
		 min.x = point.x ;
    if(min.y > point.y)
		 min.y = point.y ;
	if(max.x < point.x)
		 max.x = point.x ;
	if(max.y < point.y)
		 max.y = point.y ;

	if(m_pCtrl)
	{
		m_pCtrl->UpdateAutoRange(X, Y, 
			m_PointList.GetSize() == 1 );	//TRUE only for first added point
		
		m_pCtrl->Refresh(FALSE);
	}
	return S_OK;
}

////////////////////////////
// Set element Y data 
STDMETHODIMP CGraphElement::PlotY(double Y) 
{
	int X;
	X = m_PointList.GetSize();

	return PlotXY(X, Y);
}


STDMETHODIMP CGraphElement::Plot(VARIANT newXVals, VARIANT newYVals)
{
	LONG lXBound, uXBound, lYBound, uYBound;
	SAFEARRAY* psaXVals, *psaYVals;
	HRESULT hr;

	//usually from VBScript the arrays are coming packed in by ref variants
	hr = GetArrayFromVariant(&newXVals, &psaXVals);
	if(FAILED(hr))
		return hr;
	hr = GetArrayFromVariant(&newYVals, &psaYVals);
	if(FAILED(hr))
		return hr;

	hr = SafeArrayGetLBound(psaXVals, 1, &lXBound);
	if(FAILED(hr))
		return hr;
	hr = SafeArrayGetUBound(psaYVals, 1, &uXBound);
	if(FAILED(hr))
		return hr;
	hr = SafeArrayGetLBound(psaXVals, 1, &lYBound);
	if(FAILED(hr))
		return hr;
	hr = SafeArrayGetUBound(psaYVals, 1, &uYBound);
	if(FAILED(hr))
		return hr;
	if(uXBound - lXBound != uYBound - lYBound)
		return AtlReportError(GUID_NULL, L"Array with same size expected", IID_NULL, E_INVALIDARG);

	int i, nCount;

	min = max = CElementPoint(0,0);		

	nCount = m_PointList.GetSize();
	m_PointList.RemoveAll();

	if(m_pCtrl && nCount)
		m_pCtrl->OnPlotDeleted(); //current element does not have any points anymore

	nCount = uXBound - lXBound + 1;

	VARTYPE	typeX, typeY;
	SafeArrayGetVartype(psaXVals, &typeX);
	SafeArrayGetVartype(psaYVals, &typeY);

	for(i=0; i< nCount; i++)
	{
		double x ,y;
		hr = GetDoubleElement(typeX, psaXVals, i, &x);
		if(FAILED(hr))
			break;
		hr = GetDoubleElement(typeY, psaYVals, i, &y);
		if(FAILED(hr))
			break;

		CElementPoint point(x, y);
		m_PointList.Add(point);

		if(min.x > point.x)
			 min.x = point.x ;
		if(min.y > point.y)
			 min.y = point.y ;
		if(max.x < point.x)
			 max.x = point.x ;
		if(max.y < point.y)
			 max.y = point.y ;
		
		if(m_pCtrl)
		{
			m_pCtrl->UpdateAutoRange(x, y, 
				i == 0);	//TRUE only for first added point
		}
	}
	if(m_pCtrl)	//redraw once for all added points
		m_pCtrl->Refresh(FALSE);

	return hr;
}

///////////////////////////////////////////////
// helpers

static HRESULT WINAPI GetArrayFromVariant(VARIANT* pVar, SAFEARRAY** pparray)
{
	ATLASSERT(pVar);
	ATLASSERT(pparray);
	
	*pparray = NULL;
	VARIANT* pVarTmp;
	pVarTmp = pVar->vt == (VT_VARIANT|VT_BYREF) ? pVar->pvarVal : pVar; //typical for script clients
	if(pVarTmp == NULL)
		return E_INVALIDARG;
	
	if( (pVarTmp->vt & VT_ARRAY) == 0 )
		return AtlReportError(GUID_NULL, L"Array expected", IID_NULL, E_INVALIDARG);

	*pparray = (pVarTmp->vt & VT_BYREF) ? *(pVarTmp->pparray) : pVarTmp->parray;
	if(*pparray == NULL)
		return E_POINTER;

	if(SafeArrayGetDim(*pparray) != 1)
		return AtlReportError(GUID_NULL, L"Unidimensional array expected", IID_NULL, E_INVALIDARG);

	return S_OK;
}

static HRESULT WINAPI GetDoubleElement(VARTYPE type, SAFEARRAY* pSA, long idx, double* pRetVal)
{
	ATLASSERT(pRetVal);
	ATLASSERT(pSA);

	*pRetVal = 0.;

	HRESULT hr;
	CComVariant v;
	switch(type)
	{
	case VT_R8:
		return SafeArrayGetElement(pSA, &idx, pRetVal);
	case VT_VARIANT:
		hr = SafeArrayGetElement(pSA, &idx, &v);
		if(FAILED(hr))
			return hr;
		hr = v.ChangeType(VT_R8);
		if(FAILED(hr))
			return hr;
		*pRetVal = v.dblVal;
		break;
	case VT_BSTR:
		hr = SafeArrayGetElement(pSA, &idx, &v.bstrVal);
		if(FAILED(hr))
			return hr;
		v.vt = VT_BSTR;
		hr = v.ChangeType(VT_R8);
		if(FAILED(hr))
			return hr;
		*pRetVal = v.dblVal;
		break;
	case VT_I4:
		hr = SafeArrayGetElement(pSA, &idx, &v.lVal);
		if(FAILED(hr))
			return hr;
		v.vt = VT_I4;
		hr = v.ChangeType(VT_R8);
		if(FAILED(hr))
			return hr;
		*pRetVal = v.dblVal;
		break;
	case VT_UI4:
		hr = SafeArrayGetElement(pSA, &idx, &v.ulVal);
		if(FAILED(hr))
			return hr;
		v.vt = VT_I4;
		hr = v.ChangeType(VT_R8);
		if(FAILED(hr))
			return hr;
		*pRetVal = v.dblVal;
		break;
	}
	return S_OK;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Canada Canada

Comments and Discussions