Click here to Skip to main content
15,883,705 members
Articles / Desktop Programming / MFC

High-speed Charting Control

Rate me:
Please Sign up or sign in to vote.
4.95/5 (327 votes)
13 Jul 2010CPOL35 min read 4.2M   100.5K   787  
A flexible charting control to display 2D data
/*
 *
 *	ChartCtrl.h
 *
 *	Written by C�dric Moonen (cedric_moonen@hotmail.com)
 *
 *
 *
 *	This code may be used for any non-commercial and commercial purposes in a compiled form.
 *	The code may be redistributed as long as it remains unmodified and providing that the 
 *	author name and this disclaimer remain intact. The sources can be modified WITH the author 
 *	consent only.
 *	
 *	This code is provided without any garanties. I cannot be held responsible for the damage or
 *	the loss of time it causes. Use it at your own risks
 *
 *	An e-mail to notify me that you are using this code is appreciated also.
 *
 *
 */


#pragma once

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#include "ChartSerie.h"
#include "ChartAxis.h"
#include "ChartGrid.h"
#include "ChartLegend.h"
#include "ChartTitle.h"
#include "ChartGradient.h"
#include "ChartCursor.h"
#include "ChartMouseListener.h"
#include "ChartStandardAxis.h"
#include "ChartLogarithmicAxis.h"
#include "ChartDateTimeAxis.h"
#include "ChartCrossHairCursor.h"
#include "ChartDragLineCursor.h"
	
#include <map>


class CChartStandardAxis;
class CChartLogarithmicAxis;
class CChartDateTimeAxis;
class CChartCrossHairCursor;
class CChartDragLineCursor;

class CChartPointsSerie;
class CChartLineSerie;
class CChartSurfaceSerie;
class CChartBarSerie;
class CChartCandlestickSerie;
class CChartGanttSerie;

/////////////////////////////////////////////////////////////////////////////
// CChartCtrl window

//! The main chart control class.
/**

**/
class CChartCtrl : public CWnd
{

public:
	//! Retrieves de device context.
	/**
		This function is used for internal purposes only.
	**/
	CDC* GetDC();
	//! Retrieves the plotting rectangle.
	CRect GetPlottingRect()  const { return m_PlottingRect; }

	//! Returns a pointer to the legend object.
	CChartLegend* GetLegend() const  { return m_pLegend; }
	//! Returns a pointer to the title object.
	CChartTitle*  GetTitle()  const  { return m_pTitles; }

	//! An enumeration of the different axis positions.
	enum EAxisPos
	{
		LeftAxis = 0,
		BottomAxis,
		RightAxis,
		TopAxis
	};

	//! Create and attach a standard axis to the control.
	/**
		@param axisPos
			The position of the axis. 
		@return The created standard axis.
	**/
	CChartStandardAxis* CreateStandardAxis(EAxisPos axisPos);
	//! Create and attach a logarithmic axis to the control.
	/**
		@param axisPos
			The position of the axis. 
		@return The created logarithmic axis.
	**/
	CChartLogarithmicAxis* CreateLogarithmicAxis(EAxisPos axisPos);
	//! Create and attach a date/time axis to the control.
	/**
		@param axisPos
			The position of the axis. 
		@return The created date/time axis.
	**/
	CChartDateTimeAxis* CreateDateTimeAxis(EAxisPos axisPos);
	//! Attach a custom axis to the control.
	/**
		This function takes ownership of the axis pointer, so you 
		must not destroy it. If the axis is alread attached to a
		chart control (even this one), the function will fail with 
		an assertion. This function should be used only when you want
		to provide a custom axis to the control, otherwise you should
		use the AttachStandardAxis, AttachLogarithmicAxis and 
		AttachDateTimeAxis instead.
		@param pAxis
			The axis to attach to the control.
		@param axisPos
			The position of the axis. This value can be:
			- LeftAxis
			- BottomAxis
			- RightAxis
			- TopAxis
	**/
	void AttachCustomAxis(CChartAxis* pAxis, EAxisPos axisPos);

	//! Create and attach a point series to the control
	/**
		@param bSecondaryHorizAxis
			Specifies if the horizontal axis is the secondary axis or not. 
		@param bSecondaryVertAxis
			Specifies if the vertical axis is the secondary axis or not. 
		@return The created chart point series.
		@remark The function will assert if the associated axes are not attached 
		to the control.
		@see AttachCustomSerie for more info about the parameters of the function.
	**/
	CChartPointsSerie* CreatePointsSerie(bool bSecondaryHorizAxis=false, bool bSecondaryVertAxis=false);
	//! Create and attach a line series to the control
	/**
		The function will assert if the associated axes are not attached 
		to the control.
		@param bSecondaryHorizAxis
			Specifies if the horizontal axis is the secondary axis or not. 
		@param bSecondaryVertAxis
			Specifies if the vertical axis is the secondary axis or not. 
		@return The created chart line series.
		@remark The function will assert if the associated axes are not attached 
		to the control.
		@see AttachCustomSerie for more info about the parameters of the function.
	**/
	CChartLineSerie* CreateLineSerie(bool bSecondaryHorizAxis=false, bool bSecondaryVertAxis=false);
	//! Create and attach a surface series to the control
	/**
		The function will assert if the associated axes are not attached 
		to the control.
		@param bSecondaryHorizAxis
			Specifies if the horizontal axis is the secondary axis or not. 
		@param bSecondaryVertAxis
			Specifies if the vertical axis is the secondary axis or not. 
		@return The created chart surface series.
		@remark The function will assert if the associated axes are not attached 
		to the control.
		@see AttachCustomSerie for more info about the parameters of the function.
	**/
	CChartSurfaceSerie* CreateSurfaceSerie(bool bSecondaryHorizAxis=false, bool bSecondaryVertAxis=false);
	//! Create and attach a bar series to the control
	/**
		@param bSecondaryHorizAxis
			Specifies if the horizontal axis is the secondary axis or not. 
		@param bSecondaryVertAxis
			Specifies if the vertical axis is the secondary axis or not. 
		@return The created chart bar series.
		@remark The function will assert if the associated axes are not attached 
		to the control.
		@see AttachCustomSerie for more info about the parameters of the function.
	**/
	CChartBarSerie* CreateBarSerie(bool bSecondaryHorizAxis=false, bool bSecondaryVertAxis=false);
	//! Create and attach a candlestick series to the control
	/**
		@param bSecondaryHorizAxis
			Specifies if the horizontal axis is the secondary axis or not. 
		@param bSecondaryVertAxis
			Specifies if the vertical axis is the secondary axis or not. 
		@return The created chart candlestick series.
		@remark The function will assert if the associated axes are not attached 
		to the control.
		@see AttachCustomSerie for more info about the parameters of the function.
	**/
	CChartCandlestickSerie* CreateCandlestickSerie(bool bSecondaryHorizAxis=false, bool bSecondaryVertAxis=false);
	//! Create and attach a gantt series to the control
	/**
		@param bSecondaryHorizAxis
			Specifies if the horizontal axis is the secondary axis or not. 
		@param bSecondaryVertAxis
			Specifies if the vertical axis is the secondary axis or not. 
		@return The created chart gantt series.
		@remark The function will assert if the associated axes are not attached 
		to the control.
		@see AttachCustomSerie for more info about the parameters of the function.
	**/
	CChartGanttSerie* CreateGanttSerie(bool bSecondaryHorizAxis=false, bool bSecondaryVertAxis=false);
	//! Attaches a custom series to the chart
	/**
		You should only use this function if you want to attach a custom series
		to the control. Otherwise, you should use the CreateXXXSerie helper functions.
		The function will assert if the associated axes are not attached 
		to the control.
		@param pNewSeries
			The new series to be added. The control will take ownership of 
			the pointer, so dont't delete it yourself.
		@param bSecondaryHorizAxis
			Specifies if the associated horizontal axis is secondary or not. 
			If this value is false, the associated horizontal axis is the 
			bottom axis, otherwise it is the top axis.
		@param bSecondaryVertAxis
			Specifies if the associated vertical axis is secondary or not. 
			If this value is false, the associated vertical axis is the 
			left axis, otherwise it is the right axis.
	**/
	void AttachCustomSerie(CChartSerie* pNewSeries, bool bSecondaryHorizAxis=false, bool bSecondaryVertAxis=false);
	//! Retrieves a specific series from the chart
	/**
		@param uSerieId
			The Id of the series to retrieve
		@return The series or NULL if uSerieId is not attributed.
	**/
	CChartSerie* GetSerie(unsigned uSerieId) const;
	//! Removes a specific series from the chart
	/**
		@param uSerieId
			The Id of the series to be removed.
	**/
	void RemoveSerie(unsigned uSerieId);
	//! Removes all the series from the chart
	void RemoveAllSeries();
	//! Returns the number of series in the chart
	size_t GetSeriesCount() const;

	//! Create and attach a cross-hair cursor to the control
	/**
		A cross-hair cursor display a cross on the control and move accordingly
		to the mouse. It is attached to an horizontal and a vertical axis.
		@param bSecondaryHorizAxis
			Specifies if the horizontal axis is the secondary axis or not. 
		@param bSecondaryVertAxis
			Specifies if the vertical axis is the secondary axis or not. 
		@return The created cross-hair cursor.
		@remark The function will assert if the associated axes are not attached 
		to the control.
	**/
	CChartCrossHairCursor* CreateCrossHairCursor(bool bSecondaryHorizAxis=false, bool bSecondaryVertAxis=false);
	//! Create and attach a drag-line cursor to the control
	/**
		A drag-line cursor is a simple line (horizontal or vertical) that can be
		dragged with the mouse by clicking on it. It is attached to a specific axis.
		@param relatedAxis
			The axis position to which the cursor is attached to.
		@return The created drag-line cursor.
		@remark The function will assert if the associated axis is not attached 
		to the control.
	**/
	CChartDragLineCursor* CreateDragLineCursor(EAxisPos relatedAxis);
	//! Attach a custom cursor to the control
	/**
		You should only use this function if you want to attach a custom cursor
		to the control. Otherwise, you should use the CreateXXXCursor helper functions.
		@param pCursor
			The custom cursor to be attached to the control.
	**/
	void AttachCustomCursor(CChartCursor* pCursor);
	//! Removes a cursor with a specific Id from the control.
	/**
		The cursor Id can be retrieved on through the CChartCursor::GetCursorId function.
		@param cursorId
			The Id of the cursor to remove from the control.
	**/
	void RemoveCursor(unsigned cursorId);

	//! Shows/hides the mouse cursor when it is over the plotting area.
	void ShowMouseCursor(bool bShow);

	CChartAxis* GetBottomAxis() const;
	CChartAxis* GetLeftAxis() const;
	CChartAxis* GetTopAxis() const;
	CChartAxis* GetRightAxis() const;
	//! Returns a specific axis attached to the control
	/**
		If the specified axis does not exist, NULL is returned.
		@param axisPos
			The axis position (left, bottom, right or top).
	**/
	CChartAxis* GetAxis(EAxisPos axisPos) const
	{
		return m_pAxes[axisPos];
	}

	//! Returns the type of the edge used as border.
	UINT GetEdgeType() const        { return EdgeType;    }
	//! Sets the edge type.
	/**
		@param NewEdge 
			The type of the edge. See the DrawEdge function in MSDN for 
			a list of the different types.
	**/
	void SetEdgeType(UINT NewEdge)  
	{ 
		EdgeType = NewEdge; 
		RefreshCtrl();
	}

	//! Returns the background color
	COLORREF GetBackColor() const			{ return m_BackColor;   }
	//! Sets the background color.
	void SetBackColor(COLORREF NewCol)		
	{ 
		m_BackColor = NewCol;  
		m_bBackGradient = false; 
		RefreshCtrl();
	}
	//! Returns the color of the plotting area's border 
	COLORREF GetBorderColor() const			{ return m_BorderColor;   }
	//! Sets the color of the plotting area's border 
	void SetBorderColor(COLORREF NewCol)	{ m_BorderColor = NewCol;	RefreshCtrl(); }
	//! Returns the color of the zoom rectangle
	COLORREF GetZoomRectColor() const		{ return m_ZoomRectColor;   }
	//! Sets the color of the zoom rectangle
	void SetZoomRectColor(COLORREF NewCol)	{ m_ZoomRectColor = NewCol; RefreshCtrl(); }
	//! Sets a gradient background
	/**
		@param Col1
			The first gradient color
		@param Col2
			The second gradient color
		@param GradientType
			The type of gradient used from Col1 to Col2. It can take the following values:
				- gtHorizontal: a simple left-to-right gradient, from Col1 to Col2.
				- gtVertical: a simple top-to-bottom gradient, from Col1 to Col2. 
				- gtHorizontalDouble: a left-to-middle-to-right gradient, with Col2 in the middle. 
				- gtVerticalDouble: a top-to-middle-to-bottom gradient, with Col2 in the middle. 
	**/
	void SetBackGradient(COLORREF Col1, COLORREF Col2, EGradientType GradientType);

	//! Enables/disables the pan feature
	void SetPanEnabled(bool bEnabled)  { m_bPanEnabled = bEnabled;  }
	//! Returns true if the pan feature is enabled
	bool GetPanEnabled() const		   { return m_bPanEnabled;	    }
	//! Enables/disables the zoom feature
	void SetZoomEnabled(bool bEnabled) { m_bZoomEnabled = bEnabled; }
	//! Returns true if the zoom feature is enabled
	bool GetZoomEnabled() const		   { return m_bZoomEnabled;	    }
	//! Undo all pan and zoom operations that were done on the chart
	void UndoPanZoom();

	//! Forces a refresh of the control.
	/**
		This function is used for internal purposes.
	**/
	void RefreshCtrl();
	//! Enables/disables the refresh of the control
	/**
		This function is used when several settings have to be changed at the same 
		time on the control. This way we can avoid refreshing the control when it 
		is not needed.
		@param bEnable
			false to disable the refresh and true to re-enable the refresh.
	**/
	void EnableRefresh(bool bEnable);
	//! Creates the control dynamically.
	/**
		@param pParentWnd 
			Parent window of the control
		@param rect 
			Position of the control
		@param nID
			ID of the control
		@param dwStyle
			Style of the control
	**/
	int Create(CWnd* pParentWnd, const RECT& rect, UINT nID, DWORD dwStyle=WS_VISIBLE);
	
	//! Helper function to convert a date to a double value
	static double DateToValue(const COleDateTime& Date);
	//! Helper function to convert a double value to a date
	static COleDateTime ValueToDate(double Value);

	//! Print the chart
	/**
		@param strTitle
			The title of the print document.
		@param pPrntDialog
			A pointer to a CPrintDialog. 
			If NULL is passed, the default print dialog will be displayed.
	**/
    virtual void Print(const TChartString& strTitle, CPrintDialog* pPrntDialog = NULL);

#if _MFC_VER > 0x0600
	//! Saves the chart to an image file
	/**
		This function is not available for VC6 and earlier.
		@param strFilename
			The name of the file in which to save the image.
		@param rect
			The size of the image. If an empty rectangle is provided, the
			size of the chart on screen will be used (this results in an identical
			image as what is seen on the screen).
		@param nBPP
			The numbers of bits per pixel in the bitmap. Usually 4, 8, 16, 24, or 32. 
		@param guidFileType
			The file type to save the image as. See the CImage::Save in MSDN
			for more information.
	**/
	void SaveAsImage(const TChartString& strFilename, const CRect& rect, 
			int nBPP, REFGUID guidFileType= GUID_NULL);
#endif

	//! Default constructor
    CChartCtrl();	
	//! Default destructor
    virtual ~CChartCtrl();

	//! Register a mouse listener with the control.
	/**
		This listener will be notified each time a mouse event occurs on the control.
		@param pMouseListener
			The mouse listener to register with this control.
	**/
	void RegisterMouseListener(CChartMouseListener* pMouseListener) { m_pMouseListener = pMouseListener;}

	//! Tell the control to set the current series to the first series.
	/**
		This function is used with the GetNextSerie to iterate over all 
		series in the control.
	**/
	void GoToFirstSerie();
	//! Returns the next series in the control.
	/**
		This function is used with the GoToFirstSerie to iterate over all 
		series in the control. First call GoToFirstSerie and then call this 
		function until it returns NULL to iterate over all series.
		Warning: calling this function without calling GoToFirstSerie before
		might lead to unpredicted results. The same if you add or remove
		series between the call to GetFirstSerie and the call to GetNextSerie.
		@return the next series or NULL if we already are at the last series.
	**/
	CChartSerie* GetNextSerie();

	//! Refreshes all the axes which are automatic for the screen.
	void RefreshScreenAutoAxes();

	// Generated message map functions
protected:
	//{{AFX_MSG(CChartCtrl)
	afx_msg void OnPaint();
	afx_msg BOOL OnEraseBkgnd(CDC* pDC);
	afx_msg void OnSize(UINT nType, int cx, int cy);
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
	afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
	afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnRButtonUp(UINT nFlags, CPoint point);
	afx_msg void OnRButtonDblClk(UINT nFlags, CPoint point);
	afx_msg void OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
	afx_msg void OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar);
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
	
protected:
    virtual void OnBeginPrinting(CDC *pDC, CPrintInfo *pInfo);
    virtual void OnPrint(CDC *pDC, CPrintInfo *pInfo);
    virtual void OnEndPrinting(CDC *pDC, CPrintInfo *pInfo);

	// This function can be called to draw the chart
	// on the screen or for printing.
	virtual void DrawChart(CDC* pDC, CRect ChartRect);
	virtual void DrawBackground(CDC* pDC, CRect ChartRect);

private:
	//! Register the window class used for this control
	bool RegisterWindowClass();

	//! Notifies the mouse listeners about a mouse event on the control.
	/**
		The function detects on which part of the control the event happened.
		@param mouseEvent
			The type of the mouse event
		@param screenPoint
			The screen point on which the event occured.
	**/
	void SendMouseEvent(CChartMouseListener::MouseEvent mouseEvent, const CPoint& screenPoint) const;

	//! Specifies if the refresh is currently enabled or not.
	int m_iEnableRefresh ;
	//! Specifies if there is a pending refresh. 
	/**
		If true, once EnableRefresh(true) is called, a refresh of the control
		will be	done.
	**/
	bool m_bPendingRefresh;
	//! Memory bitmap on which the chart background is drawn (axis, grid, title, ...)
	CDC m_BackgroundDC;	
	//! Specifies if the memory bitmap has already been created.
	bool m_bMemDCCreated;

	//! Specifies if the background is gradient or solid
	bool m_bBackGradient;	
	//! First gradient color for the background
	COLORREF m_BackGradientCol1;
	//! Second gradient color for the background
	COLORREF m_BackGradientCol2;
	//! The gradient type used for the background
	EGradientType m_BackGradientType;
	//! The background color (if no gradient used)
	COLORREF m_BackColor;	
	//! The border color
	COLORREF m_BorderColor;
	//! The type of edge
	UINT EdgeType;		

	//! Zone in wich the series will be plotted
	CRect m_PlottingRect;	

	typedef std::map<unsigned, CChartSerie*> TSeriesMap;
	//! Map containing all the series added to the chart.
	TSeriesMap m_mapSeries;	
	//! The four axis of the control.
	CChartAxis* m_pAxes[4];

	//! The chart legend
	CChartLegend* m_pLegend;	
	//! The chart titles
	CChartTitle*  m_pTitles;	

	//! Specifies if the mouse panning is enabled
	bool m_bPanEnabled;
	//! Specifies if the right mouse button is currently pressed
	bool m_bRMouseDown;		
	//! The point on which the panning started
	CPoint m_PanAnchor;

	//! Specifies if the zoom is enabled
	bool  m_bZoomEnabled;
	//! Specifies if the left mouse button is currently pressed
	bool  m_bLMouseDown;
	//! The rectangle of the zoom area
	CRect m_rectZoomArea;
	//! The color of the zoom rectangle
	COLORREF m_ZoomRectColor;

	//! Specifies if the toolbars have already been created
	bool m_bToolBarCreated;

	//! The font used for printing
    CFont  m_PrinterFont;  
	//! Page size in chartctrl units.
    CSize m_LogicalPageSize;     
	//! Page size in device units.
    CSize m_PaperSize;    

	typedef std::map<unsigned, CChartCursor*> TCursorMap;
	//! The map containing all the cursors
	TCursorMap m_mapCursors;

	//! The mouse listener registered with this control
	CChartMouseListener* m_pMouseListener;

	//! Specifies if the mouse is visible when over the plotting area.
	bool m_bMouseVisible;

	typedef TSeriesMap::const_iterator TSeriesMapIter;
	//! The iterator of the current series
	TSeriesMapIter m_currentSeries;
};

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

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

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
Engineer
Belgium Belgium
I am a 29 years old guy and I live with my girlfriend in Hoegaarden, little city from Belgium well known for its white beer Smile | :) .
I studied as an industrial engineer in electronics but I oriented myself more towards software development when I started to work.
Currently I am working in a research centre in mechatronica. I mainly develop in C++ but I also do a bit of Java.
When I have so spare time, I like to read (mainly fantasy) and play electric guitar.

Comments and Discussions