Click here to Skip to main content
15,892,298 members
Articles / Desktop Programming / MFC

Plot Graphic Library

Rate me:
Please Sign up or sign in to vote.
4.95/5 (70 votes)
7 May 2003LGPL36 min read 1.4M   51.3K   383  
A library to plot data (lines, maps...) in MFC projects
In this article, you will see a library called PGL that encapsulates plot capabilities in a MFC project for VC6 and VC7. It can easily plot data generated in a project without the need of any external software.
// PGLRegion.cpp: implementation of the CPGLRegion class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include <stdlib.h>
#include "PGL/pgl.h"
#include "PGL/PGLRegion.h"
#include "PGL/PGLRegionPropPage.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

IMPLEMENT_SERIAL(CPGLRegion,CPGLObject,1);

void CPGLRegion::Serialize(CArchive &archive)
{

    // call base class function first
    // base class is CPGLObject in this case
    CObject::Serialize( archive );

	if (archive.IsLoading())
	{
	}
	else
	{
	}
}

#ifdef _DEBUG
void CPGLRegion::Dump( CDumpContext& dc ) const
{
    // call base class function first
    CObject::Dump( dc );

    // now do the stuff for our specific class
	// now dumping..
	dc << _T("--- CPGLRegion ---\n")<< endl;
    dc << m_view<<"\n";
}
void CPGLRegion::AssertValid() const
{
    // call inherited AssertValid first
    CObject::AssertValid();

    // check members...
	m_view.AssertValid();
    // check members...
	m_axe.AssertValid();
	m_mObjects.AssertValid();

	if (sm_mClipboard!=NULL)
		ASSERT_VALID(sm_mClipboard);
} 
#endif

CPGLRegion::CPGLRegion()
{
	m_background=CPGLColor(1.0f,1.0f,1.0f,1.0f);
	m_axe.SetView(&m_view);

	m_pNormBBox[0]=m_pNormBBox[1]=0;
	m_pNormBBox[2]=m_pNormBBox[3]=1;

	LoadBitmap(IDB_PGL_REGION_BITMAP);
}

CPGLRegion::CPGLRegion(const CPGLRegion& g)
: CPGLObject(g)
{
	m_view=g.m_view;
	m_background=g.m_background;
	m_axe=g.m_axe;
	m_mObjects=g.m_mObjects;

	for (int i=0;i<4;i++)
	{
		m_pNormBBox[i] = g.m_pNormBBox[i];
	}

	PostUpdateGraph();
}

CPGLRegion& CPGLRegion::operator =(const CPGLRegion& g)
{
	if (&g!=this)
	{
		CPGLObject::operator =(g);

		m_view=g.m_view;
		m_background=g.m_background;
		m_axe=g.m_axe;


		for (int i=0;i<4;i++)
		{
			m_pNormBBox[i] = g.m_pNormBBox[i];
		}

		m_mObjects=g.m_mObjects;
		PostUpdateGraph();
	}
	return *this;
}

CPGLRegion::~CPGLRegion()
{
	// cleaning objects
	m_mObjects.DeleteAll();
	PGL_TRACE("PGL-Region: Objects deleted\n");
	ASSERT(m_mObjects.IsEmpty());
}

/// adds pObject to the graph (memory must be allowed by user)
void CPGLRegion::AddObject(CPGLObject* pObject)
{	
	ASSERT_VALID(pObject);

	ASSERT(! pObject->IsKindOf( RUNTIME_CLASS(CPGLRegion) ) );

	// adding pointer
	m_mObjects.AddHead(pObject); 
	// updating graph
	PostUpdateGraph();
}


CPGLRegion* CPGLRegion::AddRegion()
{
	CPGLRegion* pNewRegion = new CPGLRegion();

	// adding pointer
	m_mChilds.AddHead(pNewRegion); 

	// updating graph
	PostUpdateGraph();

	return pNewRegion;
}

/// removes the selected object from the graph
void CPGLRegion::DeleteSelection()
{	
	// deleting objects
	m_mObjects.DeleteSelection();

	POSITION pos=m_mChilds.GetHeadPosition();
	while (pos!=NULL)
	{
		((CPGLRegion*)m_mChilds.GetNext(pos))->DeleteSelection();
	}

	// updating graph
	PostUpdateGraph();
};

void CPGLRegion::SelectAll()
{
	POSITION pos;

	pos=m_mObjects.GetHeadPosition();
	while(pos!=NULL)
	{
		m_mObjects.GetNext(pos)->Select();
	}

	pos=m_mChilds.GetHeadPosition();
	while(pos!=NULL)
	{
		m_mChilds.GetNext(pos)->Select();
	}
}

void CPGLRegion::UnhideAll()
{
	POSITION pos;

	pos=m_mObjects.GetHeadPosition();
	while(pos!=NULL)
	{
		m_mObjects.GetNext(pos)->Show();
	}

	pos=m_mChilds.GetHeadPosition();
	while(pos!=NULL)
	{
		m_mChilds.GetNext(pos)->Show();
	}
}

void CPGLRegion::UnselectAll()
{
	POSITION pos;

	// unselect axe
	m_axe.Deselect();

	// unselect objects
	pos=m_mObjects.GetHeadPosition();
	while(pos!=NULL)
	{
		m_mObjects.GetNext(pos)->Deselect();
	}

	// unselect objects
	pos=m_mChilds.GetHeadPosition();
	while(pos!=NULL)
	{
		m_mChilds.GetNext(pos)->Deselect();
	}
}

// finding an object by it's ID
CPGLObject* CPGLRegion::FindObject(UINT ID)
{
	POSITION pos;
	CPGLObject* pObject;
	CPGLObject* pRegion;

	// testing if it is itself
	if (CheckID(ID))
		return this;

	// test m_axe
	pObject=m_axe.FindObject(ID);
	if (pObject)
		return pObject;

	// test objects
	pos=m_mObjects.GetHeadPosition();
	while (pos!=NULL)
	{
		pObject=m_mObjects.GetNext(pos);
		if (pObject->CheckID(ID))
			return pObject;
	}

	// test objects
	pos=m_mChilds.GetHeadPosition();
	while (pos!=NULL)
	{
		pRegion=m_mChilds.GetNext(pos);
		pObject = ((CPGLRegion*)pRegion)->FindObject(ID);
		if (pObject)
			return pObject;
	}

	// not found
	return NULL;
}

double* CPGLRegion::GetExtent()
{
	POSITION pos;
	CPGLObject* pObject;
	double* locExtent;
	bool first=true;

	// no object case
	if (m_mObjects.IsEmpty())
	{
		m_extent[0]=0;
		m_extent[1]=1;
		m_extent[2]=0;
		m_extent[3]=1;

		return m_extent;
	}

	// first, non text objects.
	pos=m_mObjects.GetHeadPosition();
	while (pos!=NULL)
	{
		pObject=m_mObjects.GetNext(pos);
		ASSERT_VALID(pObject);

		if (!pObject->IsVisible())
			continue;

		// skip text for the moment
		if (pObject->GetRuntimeClass()==RUNTIME_CLASS(CPGLText)
			|| pObject->GetRuntimeClass()==RUNTIME_CLASS(CPGLLineVer)
			|| pObject->GetRuntimeClass()==RUNTIME_CLASS(CPGLLineHor))
			continue;

		// test for type...
		locExtent=pObject->GetExtent(&m_view);

		// testing...
		if (first)
		{
			m_extent[0]=locExtent[0];
			m_extent[1]=locExtent[1];
			m_extent[2]=locExtent[2];
			m_extent[3]=locExtent[3];
			first=false;
		}
		else
		{
			m_extent[0]=__min(m_extent[0],locExtent[0]);	//left
			m_extent[1]=__max(m_extent[1],locExtent[1]);	//right
			m_extent[2]=__min(m_extent[2],locExtent[2]);	//bottom
			m_extent[3]=__max(m_extent[3],locExtent[3]);	//up
		}
	}
	
	return m_extent;
}

void CPGLRegion::Delete()
{
	PGL_TRACE("PGL-Region: Starting cleaning\n");

	// deleting axe
	m_axe.Delete();
	PGL_TRACE("PGL-Graph: Axe deleted\n");

	// deleting objects
	DeleteAllObjects();

	PGL_TRACE("PGL-Region: Fonts unassigned\n");

	// cleaning clipboard
	if (sm_mClipboard!=NULL)
	{
		sm_mClipboard->DeleteAll();
		delete sm_mClipboard;
		sm_mClipboard=NULL;
	}
	// Post updating
	PostUpdateGraph();
	PGL_TRACE("PGL-Region: Cleaning success, processing childs\n");
}

void CPGLRegion::UpdateGraph()
{	
	double pExt[4];
	// retreive info
	m_axe.GetLimits(0,pExt);
	m_axe.GetLimits(1,pExt+2);

	// updating extent to put axis
	m_axe.GrowExtent(pExt);

	// updating view
	m_view.ZoomAll(pExt[0],pExt[1],pExt[2],pExt[3]);
	
	// update labels
	m_axe.UpdateLabels();
	m_bNeedUpdate=FALSE;

	// computing child viewports
	CPGLRegion* pRegion;
	POSITION pos=m_mChilds.GetHeadPosition();
	while (pos!=NULL)
	{
		pRegion=(CPGLRegion*)m_mChilds.GetNext(pos);
		ASSERT_VALID(pRegion);
		pRegion->UpdateGraph();
	}
};

void CPGLRegion::SetViewport(int tx, int ty, int width, int height)
{
	CPGLRegion* pRegion;

	// computing own viewport
	m_view.SetViewport(
		tx + (int) floor(width * m_pNormBBox[0]),
		ty + (int) floor(height * m_pNormBBox[1]),
		(int) floor(width * (m_pNormBBox[2]-m_pNormBBox[0])),
		(int) floor(height * (m_pNormBBox[3]-m_pNormBBox[1])));

	// computing child viewports
	POSITION pos=m_mChilds.GetHeadPosition();
	while (pos!=NULL)
	{
		pRegion=(CPGLRegion*)m_mChilds.GetNext(pos);
		ASSERT_VALID(pRegion);
		pRegion->SetViewport(m_view.GetTx(), m_view.GetTy(), m_view.GetWidth(), m_view.GetHeight());
	}
};

void CPGLRegion::ZoomAll(BOOL recurse)
{
//	ASSERT_VALID(m_pDC);
	// getting extent
	double* pExt=GetExtent();

	// updating extent to put axis
	double pExtNew[4];
	for (int i=0;i<4;i++)
		pExtNew[i]=pExt[i];

	// updating axis
	m_axe.SetLimits(0,pExtNew[0],pExtNew[1]);
	m_axe.SetLimits(1,pExtNew[2],pExtNew[3]);

	// retreive info
	m_axe.GetLimits(0,pExtNew);
	m_axe.GetLimits(1,pExtNew+2);

	// grow window
	m_axe.GrowExtent(pExtNew);

	// updating view
	m_view.ZoomAll(pExtNew[0],pExtNew[1],pExtNew[2],pExtNew[3]);

	if (recurse)
	{
		POSITION pos=m_mChilds.GetHeadPosition();
		while (pos!=NULL)
		{
			((CPGLRegion*)m_mChilds.GetNext(pos))->ZoomAll(TRUE);
		}
	}
}

void CPGLRegion::ZoomRegion(double* pExt)
{
	// updating axis
	m_axe.SetLimits(0,pExt[0],pExt[1]);
	m_axe.SetLimits(1,pExt[2],pExt[3]);

	// retreive info
	m_axe.GetLimits(0,pExt);
	m_axe.GetLimits(1,pExt+2);

	// updating extent to put axis
	m_axe.GrowExtent(pExt);

	// updating view
	m_view.ZoomAll(pExt[0],pExt[1],pExt[2],pExt[3]);
}

void CPGLRegion::ZoomBox(int xStart, int yStart, int xEnd, int yEnd)
{
	// putting start and end in order
	int temp;
	if (xStart>xEnd)
	{
		temp=xStart;
		xStart=xEnd;
		xEnd=temp;
	}
	// y is going down
	if (yStart<yEnd)
	{
		temp=yStart;
		yStart=yEnd;
		yEnd=temp;
	}
	// checking that box has at least 1 of size
	if (xStart==xEnd)
		xEnd++;
	if (yStart==yEnd)
		yEnd++;

	// step 1 setting axis
	// sending dimension of box to axe and computing box
	m_axe.SetLimits(0, 
		m_view.PixelToWorldCoord(0,xStart), //left
		m_view.PixelToWorldCoord(0,xEnd)); //right
	m_axe.SetLimits(1, 
		m_view.PixelToWorldCoord(1,yStart), //bottom
		m_view.PixelToWorldCoord(1,yEnd)); //top

	// retreivinf info
	double pExt[4];
	m_axe.GetLimits(0,pExt);
	m_axe.GetLimits(1,pExt+2);

	// Step 2 grow extent
	m_axe.GrowExtent(pExt);

	// Step 3, updating view
	m_view.ZoomAll(pExt[0],pExt[1],pExt[2],pExt[3]);
}

void CPGLRegion::ZoomIn()
{
	// step 1 zoom in of view
	m_view.ZoomIn();

	// step 2 set axis
	m_axe.SetLimits(0, 
		m_view.GetLeft(), //left
		m_view.GetRight()); //right
	m_axe.SetLimits(1, 
		m_view.GetBottom(), //bottom
		m_view.GetTop()); //top

	// retreivinf info
	double pExt[4];
	m_axe.GetLimits(0,pExt);
	m_axe.GetLimits(1,pExt+2);

	// Step 2 grow extent
	m_axe.GrowExtent(pExt);

	// Step 3, updating view
	m_view.ZoomAll(pExt[0],pExt[1],pExt[2],pExt[3]);
}

void CPGLRegion::ZoomOut()
{
	// step 1 zoom in of view
	m_view.ZoomOut();

	// step 2 set axis
	m_axe.SetLimits(0, 
		m_view.GetLeft(), //left
		m_view.GetRight()); //right
	m_axe.SetLimits(1, 
		m_view.GetBottom(), //bottom
		m_view.GetTop()); //top

	// retreivinf info
	double pExt[4];
	m_axe.GetLimits(0,pExt);
	m_axe.GetLimits(1,pExt+2);

	// Step 2 grow extent
	m_axe.GrowExtent(pExt);

	// Step 3, updating view
	m_view.ZoomAll(pExt[0],pExt[1],pExt[2],pExt[3]);
}


void CPGLRegion::Pan(int x, int y)
{
	// step 1 pan view
	m_view.Pan(x,y);

	m_axe.Pan(m_view.PixelToWorld(0,x),m_view.PixelToWorld(1,y));
}


void CPGLRegion::DeleteAllObjects()
{
	// Remove objects
	m_mObjects.DeleteAll();

	// remove region
	CPGLRegion* pRegion;
	POSITION pos=m_mChilds.GetHeadPosition();
	while (pos!=NULL)
	{
		pRegion = (CPGLRegion*)m_mChilds.GetNext(pos);
		pRegion->Delete();
	}
	m_mChilds.DeleteAll();
}

const float* CPGLRegion::GetNormBBox() const
{
	return m_pNormBBox;
}

void CPGLRegion::SetNormBBox(float BBox[])
{
	// make lower left corner and upper right are ordered
	m_pNormBBox[0] = __max(0, __min(BBox[0], BBox[2]));
	m_pNormBBox[2] = __min(1, __max(BBox[0], BBox[2]));
	m_pNormBBox[1] = __max(0, __min(BBox[1], BBox[3]));
	m_pNormBBox[3] = __min(1, __max(BBox[1], BBox[3]));
	ASSERT(m_pNormBBox[0] != 1);
	ASSERT(m_pNormBBox[1] != 1);
	ASSERT(m_pNormBBox[2] != 0);
	ASSERT(m_pNormBBox[3] != 0);
	ASSERT(m_pNormBBox[0] != m_pNormBBox[2]);
	ASSERT(m_pNormBBox[1] != m_pNormBBox[3]);
}

void CPGLRegion::SetNormBBox(float llx, float lly, float urx, float ury)
{
	// make lower left corner and upper right are ordered
	m_pNormBBox[0] = __max(0, __min(llx, urx));
	m_pNormBBox[2] = __min(1, __max(llx, urx));
	m_pNormBBox[1] = __max(0, __min(lly, ury));
	m_pNormBBox[3] = __min(1, __max(lly, ury));
	ASSERT(m_pNormBBox[0] != 1);
	ASSERT(m_pNormBBox[1] != 1);
	ASSERT(m_pNormBBox[2] != 0);
	ASSERT(m_pNormBBox[3] != 0);
	ASSERT(m_pNormBBox[0] != m_pNormBBox[2]);
	ASSERT(m_pNormBBox[1] != m_pNormBBox[3]);
}

void CPGLRegion::AddPropertyPage(CPropertySheet* pPropSheet)
{
	ASSERT_VALID(pPropSheet);
	// call own functions
	CPGLRegionPropPage* propPage=new CPGLRegionPropPage(this);
	pPropSheet->AddPage(propPage);

	// first call base class function
	CPGLObject::AddPropertyPage(pPropSheet);
}

HTREEITEM CPGLRegion::AddPropTree(CTreeCtrl* pTree, HTREEITEM hParent)
{
	ASSERT_VALID(pTree);
	CString str;

	CImageList* pImgList=pTree->GetImageList(TVSIL_NORMAL);
	ASSERT_VALID(pImgList);
	ASSERT(hParent);
	COLORREF crMask=0;

	// adding bitmap
	pImgList->Add(CBitmap::FromHandle(GetBitmap()),crMask);

	// adding to tree
	POSITION pos;
	CPGLObject* pObject;
	CPGLRegion* pRegion;
	HTREEITEM pRegionItem;
	HTREEITEM pObjItem;
	HTREEITEM pRgnItem;

	// inserting object root
	pRegionItem=pTree->InsertItem(TVIF_TEXT | TVIF_IMAGE | TVIF_PARAM   /* nMask*/, 
				GetName() /* lpszItem*/, 
				pImgList->GetImageCount()-1 /* nImage */, 
				0 /* nSelectedImage */, 
				0  /* nState */, 
				0 /* nStateMask */, 
				GetID() /*  lParam */, 
				hParent /* hParent */, 
				TVI_LAST /* hInsertAfter */);
	ASSERT(pRegionItem!=NULL);

	// insert axis
	// adding to tree
	HTREEITEM pAxeItem=m_axe.AddPropTree(pTree,pRegionItem);
	ASSERT(pAxeItem!=NULL);
	// check if selected...
//	if ((long)m_axe.GetID()==m_lSelID)
//		pTree->SelectItem(pAxeItem);

	// filling up insert structure with objects
	pos=m_mObjects.GetHeadPosition();
	while (pos!=NULL)
	{
		pObject=m_mObjects.GetNext(pos);
		ASSERT_VALID(pObject);

		pObjItem=pObject->AddPropTree(pTree,pRegionItem);
		ASSERT(pObjItem!=NULL);
		// check if selected...
//		if ((long)pObject->GetID()==m_lSelID)
//			pTree->SelectItem(pObjItem);
	}

	// filling up insert structure with subplots
	pos=m_mChilds.GetHeadPosition();
	while (pos!=NULL)
	{
		pRegion=(CPGLRegion*)m_mChilds.GetNext(pos);
		ASSERT_VALID(pRegion);

		pRgnItem=pRegion->AddPropTree(pTree,pRegionItem);
		ASSERT(pRgnItem!=NULL);
		// check if selected...
//		if ((long)pRegion->GetID()==m_lSelID)
//			pTree->SelectItem(pRgnItem);
	}

	return pRegionItem;
}

void CPGLRegion::Divide(int nrows, int ncols)
{
	float llx, lly, urx, ury;
	CString str;

	if ((nrows == 0) || (ncols == 0))
		return;

	// deleting all objects
	DeleteAllObjects();
	// hiding axis
	m_axe.Hide();

	// adding new regions...
	CPGLRegion* pRegion;
	int i,j;

	for (i=0;i<nrows;i++)
	{
		lly = (float)((double) i / (double) (nrows));
		ury = (float)((double) (i+1) / (double) (nrows));
		for (j=ncols-1;j>=0;j--)
		{
			llx = (float)((double) j / (double) (ncols));
			urx = (float)((double) (j+1) / (double) (ncols));

			pRegion = AddRegion();
			pRegion->SetNormBBox(llx, lly, urx, ury);
			str.Format("Subplot (%i,%i)",i+1,j+1);
			pRegion->SetName(str);
		}
	}
}

CPGLRegion* CPGLRegion::GetChild(int i)
{
	POSITION pos = m_mChilds.FindIndex(i);

	if (pos)
		return (CPGLRegion*)m_mChilds.GetAt(pos);
	else
		return NULL;
}

void CPGLRegion::PlotGfx(gfxinterface::CGfxInterface& gfx)
{	
	CPGLObject::PlotGfx(gfx);
	CString str;

	POSITION pos;
	// updating graph
	UpdateGraph();

	str.Format("--- CPGLRegion %s ---", GetName());
	gfx.AddComment(str);
	gfx.PushState();
	// setting bounding box
	m_view.PlotGfx(gfx);

	// drawing background...
	gfx.PushState();
	gfx.SetFillColor(m_background.GetRed(), m_background.GetGreen(), m_background.GetBlue(), m_background.GetAlpha());
	gfx.SetColor(m_background.GetRed(), m_background.GetGreen(), m_background.GetBlue(), m_background.GetAlpha());
	gfx.DrawRect( m_view.GetLeft(), m_view.GetBottom(), m_view.GetRight(), m_view.GetTop() , TRUE);
	gfx.PopState();

	// setting clipping path
	m_axe.ClipGfx(gfx);

	// now plotting the objects
	pos=m_mObjects.GetHeadPosition();
	while (pos!=NULL)
	{
		m_mObjects.GetNext(pos)->PlotGfx(gfx);
	}
	// remove clipping path
	m_axe.UnClipGfx(gfx);
	// plotting the axe
	m_axe.PlotGfx(gfx);
	gfx.PopState();
	
	// Drawing childs
	pos=m_mChilds.GetHeadPosition();
	while (pos!=NULL)
	{
		m_mChilds.GetNext(pos)->PlotGfx(gfx);
	}
};

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 GNU Lesser General Public License (LGPLv3)


Written By
Engineer
United States United States
Jonathan de Halleux is Civil Engineer in Applied Mathematics. He finished his PhD in 2004 in the rainy country of Belgium. After 2 years in the Common Language Runtime (i.e. .net), he is now working at Microsoft Research on Pex (http://research.microsoft.com/pex).

Comments and Discussions