1144 lines
30 KiB
C++
1144 lines
30 KiB
C++
/*
|
|
*
|
|
* ChartCtrl.cpp
|
|
*
|
|
* 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.
|
|
*
|
|
*
|
|
* History:
|
|
* - 18/05/2006: Added support for panning
|
|
* - 28/05/2006: Bug corrected in RemoveAllSeries
|
|
* - 28/05/2006: Added support for resizing
|
|
* - 12/06/2006: Added support for manual zoom
|
|
* - 10/08/2006: Added SetZoomMinMax and UndoZoom
|
|
* - 24/03/2007: GDI leak corrected
|
|
* - 24/03/2007: Invisible series are not taken in account for auto axis
|
|
* and legend (thanks to jerminator-jp).
|
|
* - 24/03/2007: possibility to specify a margin for the axis. Needs to be improved
|
|
* - 05/04/2007: ability to change the text color of the axis.
|
|
* - 05/04/2007: ability to change the color of the border of the drawing area.
|
|
* - 05/04/2007: Surface series added.
|
|
* - 26/08/2007: The clipping area of the series is a bit larger (they will be
|
|
* drawn over the bottom and right axes).
|
|
* - 12/01/2007: Ability to change the color of the zoom rectangle.
|
|
* - 08/02/2008: Added convenience functions to convert from date to value and
|
|
* opposite.
|
|
* - 21/02/2008: The zoom doesn't do anything if the user only clicks on the control
|
|
* (thanks to Eugene Pustovoyt).
|
|
* - 29/02/2008: The auto axis are now refreshed when a series is removed (thanks to
|
|
* Bruno Lavier).
|
|
* - 08/03/2008: EnableRefresh function added (thanks to Bruno Lavier).
|
|
* - 21/03/2008: Added support for scrolling.
|
|
* - 25/03/2008: UndoZoom function added.
|
|
* - 25/03/2008: A series can now be removed using its pointer.
|
|
* - 12/08/2008: Performance patch (thanks to Nick Holgate).
|
|
* - 18/08/2008: Added support for printing.
|
|
* - 31/10/2008: Fixed a bug for unicode.
|
|
*
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "ChartCtrl.h"
|
|
|
|
#include "ChartSerie.h"
|
|
#include "ChartGradient.h"
|
|
#include "ChartStandardAxis.h"
|
|
#include "ChartDateTimeAxis.h"
|
|
#include "ChartLogarithmicAxis.h"
|
|
#include "ChartCrossHairCursor.h"
|
|
#include "ChartDragLineCursor.h"
|
|
|
|
#include "ChartPointsSerie.h"
|
|
#include "ChartLineSerie.h"
|
|
#include "ChartSurfaceSerie.h"
|
|
#include "ChartBarSerie.h"
|
|
#include "ChartCandlestickSerie.h"
|
|
#include "ChartGanttSerie.h"
|
|
|
|
#if _MFC_VER > 0x0600
|
|
#include "atlImage.h"
|
|
#endif
|
|
|
|
using namespace std;
|
|
|
|
#ifdef _DEBUG
|
|
#define new DEBUG_NEW
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
#define CHARTCTRL_CLASSNAME _T("ChartCtrl") // Window class name
|
|
|
|
|
|
COLORREF pSeriesColorTable[] = { RGB(255,0,0), RGB(0,150,0), RGB(0,0,255), RGB(255,255,0), RGB(0,255,255),
|
|
RGB(255,128,0), RGB(128,0,128), RGB(128,128,0), RGB(255,0,255), RGB(64,128,128)};
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CChartCtrl
|
|
|
|
CChartCtrl::CChartCtrl()
|
|
{
|
|
RegisterWindowClass();
|
|
|
|
m_iEnableRefresh = 1;
|
|
m_bPendingRefresh = false;
|
|
m_BorderColor = RGB(0,0,0);
|
|
m_BackColor = GetSysColor(COLOR_BTNFACE);
|
|
EdgeType = EDGE_RAISED;
|
|
m_BackGradientType = gtVertical;
|
|
m_bBackGradient = false;
|
|
m_BackGradientCol1 = m_BackGradientCol2 = m_BackColor;
|
|
|
|
for (int i=0;i<4;i++)
|
|
m_pAxes[i] = NULL;
|
|
|
|
m_pLegend = new CChartLegend(this);
|
|
m_pTitles = new CChartTitle(this);
|
|
|
|
m_bMemDCCreated = false;
|
|
m_bPanEnabled = true;
|
|
m_bRMouseDown = false;
|
|
|
|
m_bZoomEnabled = true;
|
|
m_bLMouseDown = false;
|
|
m_ZoomRectColor = RGB(255,255,255);
|
|
|
|
m_bToolBarCreated = false;
|
|
m_pMouseListener = NULL;
|
|
m_bMouseVisible = true;
|
|
}
|
|
|
|
CChartCtrl::~CChartCtrl()
|
|
{
|
|
TSeriesMap::iterator seriesIter = m_mapSeries.begin();
|
|
for (seriesIter; seriesIter!=m_mapSeries.end(); seriesIter++)
|
|
{
|
|
delete (seriesIter->second);
|
|
}
|
|
TCursorMap::iterator cursorIter = m_mapCursors.begin();
|
|
for (cursorIter; cursorIter!=m_mapCursors.end(); cursorIter++)
|
|
{
|
|
delete (cursorIter->second);
|
|
}
|
|
|
|
for (int i=0; i<4 ;i++)
|
|
{
|
|
if (m_pAxes[i])
|
|
delete m_pAxes[i];
|
|
}
|
|
|
|
if (m_pLegend)
|
|
{
|
|
delete m_pLegend;
|
|
m_pLegend = NULL;
|
|
}
|
|
if (m_pTitles)
|
|
{
|
|
delete m_pTitles;
|
|
m_pTitles = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
BEGIN_MESSAGE_MAP(CChartCtrl, CWnd)
|
|
//{{AFX_MSG_MAP(CChartCtrl)
|
|
ON_WM_PAINT()
|
|
ON_WM_ERASEBKGND()
|
|
ON_WM_MOUSEMOVE()
|
|
ON_WM_LBUTTONDOWN()
|
|
ON_WM_LBUTTONUP()
|
|
ON_WM_LBUTTONDBLCLK()
|
|
ON_WM_RBUTTONDOWN()
|
|
ON_WM_RBUTTONUP()
|
|
ON_WM_RBUTTONDBLCLK()
|
|
ON_WM_SIZE()
|
|
ON_WM_HSCROLL()
|
|
ON_WM_VSCROLL()
|
|
//}}AFX_MSG_MAP
|
|
END_MESSAGE_MAP()
|
|
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// CChartCtrl message handlers
|
|
|
|
void CChartCtrl::OnPaint()
|
|
{
|
|
CPaintDC dc(this); // device context for painting
|
|
|
|
if (!m_bMemDCCreated)
|
|
{
|
|
RefreshCtrl();
|
|
m_bMemDCCreated = true;
|
|
}
|
|
|
|
// Get Size of Display area
|
|
CRect rect;
|
|
GetClientRect(&rect);
|
|
dc.BitBlt(0, 0, rect.Width(), rect.Height(),
|
|
&m_BackgroundDC, 0, 0, SRCCOPY) ;
|
|
|
|
// Draw the zoom rectangle
|
|
if (m_bZoomEnabled && m_bLMouseDown)
|
|
{
|
|
CPen NewPen(PS_SOLID,1,m_ZoomRectColor);
|
|
CPen* pOldPen = dc.SelectObject(&NewPen);
|
|
|
|
dc.MoveTo(m_rectZoomArea.TopLeft());
|
|
dc.LineTo(m_rectZoomArea.right,m_rectZoomArea.top);
|
|
dc.LineTo(m_rectZoomArea.BottomRight());
|
|
dc.LineTo(m_rectZoomArea.left,m_rectZoomArea.bottom);
|
|
dc.LineTo(m_rectZoomArea.TopLeft());
|
|
|
|
dc.SelectObject(pOldPen);
|
|
DeleteObject(NewPen);
|
|
}
|
|
|
|
// Draw the cursors.
|
|
TCursorMap::iterator iter = m_mapCursors.begin();
|
|
for (iter; iter!=m_mapCursors.end(); iter++)
|
|
iter->second->Draw(&dc);
|
|
}
|
|
|
|
BOOL CChartCtrl::OnEraseBkgnd(CDC* )
|
|
{
|
|
// To avoid flickering
|
|
// return CWnd::OnEraseBkgnd(pDC);
|
|
return FALSE;
|
|
}
|
|
|
|
CChartCrossHairCursor* CChartCtrl::CreateCrossHairCursor(bool bSecondaryHorizAxis,
|
|
bool bSecondaryVertAxis)
|
|
{
|
|
CChartAxis* pHorizAxis = NULL;
|
|
CChartAxis* pVertAxis = NULL;
|
|
if (bSecondaryHorizAxis)
|
|
pHorizAxis = m_pAxes[TopAxis];
|
|
else
|
|
pHorizAxis = m_pAxes[BottomAxis];
|
|
if (bSecondaryVertAxis)
|
|
pVertAxis = m_pAxes[RightAxis];
|
|
else
|
|
pVertAxis = m_pAxes[LeftAxis];
|
|
|
|
ASSERT(pHorizAxis != NULL);
|
|
ASSERT(pVertAxis != NULL);
|
|
|
|
CChartCrossHairCursor* pNewCursor = new CChartCrossHairCursor(this, pHorizAxis, pVertAxis);
|
|
m_mapCursors[pNewCursor->GetCursorId()] = pNewCursor;
|
|
return pNewCursor;
|
|
}
|
|
|
|
CChartDragLineCursor* CChartCtrl::CreateDragLineCursor(EAxisPos relatedAxis)
|
|
{
|
|
ASSERT(m_pAxes[relatedAxis] != NULL);
|
|
|
|
CChartDragLineCursor* pNewCursor = new CChartDragLineCursor(this, m_pAxes[relatedAxis]);
|
|
m_mapCursors[pNewCursor->GetCursorId()] = pNewCursor;
|
|
return pNewCursor;
|
|
}
|
|
|
|
void CChartCtrl::AttachCustomCursor(CChartCursor* pCursor)
|
|
{
|
|
m_mapCursors[pCursor->GetCursorId()] = pCursor;
|
|
}
|
|
|
|
void CChartCtrl::RemoveCursor(unsigned cursorId)
|
|
{
|
|
TCursorMap::iterator iter = m_mapCursors.find(cursorId);
|
|
if (iter != m_mapCursors.end())
|
|
{
|
|
delete iter->second;
|
|
m_mapCursors.erase(iter);
|
|
}
|
|
}
|
|
|
|
void CChartCtrl::ShowMouseCursor(bool bShow)
|
|
{
|
|
m_bMouseVisible = bShow;
|
|
if (!bShow)
|
|
SetCursor(NULL);
|
|
}
|
|
|
|
CChartStandardAxis* CChartCtrl::CreateStandardAxis(EAxisPos axisPos)
|
|
{
|
|
CChartStandardAxis* pAxis = new CChartStandardAxis();
|
|
AttachCustomAxis(pAxis, axisPos);
|
|
return pAxis;
|
|
}
|
|
|
|
CChartLogarithmicAxis* CChartCtrl::CreateLogarithmicAxis(EAxisPos axisPos)
|
|
{
|
|
CChartLogarithmicAxis* pAxis = new CChartLogarithmicAxis();
|
|
AttachCustomAxis(pAxis, axisPos);
|
|
return pAxis;
|
|
}
|
|
|
|
CChartDateTimeAxis* CChartCtrl::CreateDateTimeAxis(EAxisPos axisPos)
|
|
{
|
|
CChartDateTimeAxis* pAxis = new CChartDateTimeAxis();
|
|
AttachCustomAxis(pAxis, axisPos);
|
|
return pAxis;
|
|
}
|
|
|
|
void CChartCtrl::AttachCustomAxis(CChartAxis* pAxis, EAxisPos axisPos)
|
|
{
|
|
// The axis should not be already attached to another control
|
|
ASSERT(pAxis->m_pParentCtrl == NULL);
|
|
pAxis->SetParent(this);
|
|
|
|
if ( (axisPos==RightAxis) || (axisPos==TopAxis) )
|
|
pAxis->SetSecondary(true);
|
|
if ( (axisPos==BottomAxis) || (axisPos==TopAxis) )
|
|
pAxis->SetHorizontal(true);
|
|
else
|
|
pAxis->SetHorizontal(false);
|
|
pAxis->CreateScrollBar();
|
|
|
|
// Beofre storing the new axis, we should delete the previous one if any
|
|
if (m_pAxes[axisPos])
|
|
delete m_pAxes[axisPos];
|
|
m_pAxes[axisPos] = pAxis;
|
|
}
|
|
|
|
bool CChartCtrl::RegisterWindowClass()
|
|
{
|
|
WNDCLASS wndcls;
|
|
HINSTANCE hInst = AfxGetInstanceHandle();
|
|
|
|
if (!(::GetClassInfo(hInst, CHARTCTRL_CLASSNAME, &wndcls)))
|
|
{
|
|
memset(&wndcls, 0, sizeof(WNDCLASS));
|
|
|
|
wndcls.hInstance = hInst;
|
|
wndcls.lpfnWndProc = ::DefWindowProc;
|
|
wndcls.hCursor = NULL; //LoadCursor(NULL, IDC_ARROW);
|
|
wndcls.hIcon = 0;
|
|
wndcls.lpszMenuName = NULL;
|
|
wndcls.hbrBackground = (HBRUSH) ::GetStockObject(WHITE_BRUSH);
|
|
wndcls.style = CS_DBLCLKS;
|
|
wndcls.cbClsExtra = 0;
|
|
wndcls.cbWndExtra = 0;
|
|
wndcls.lpszClassName = CHARTCTRL_CLASSNAME;
|
|
|
|
if (!RegisterClass(&wndcls))
|
|
{
|
|
// AfxThrowResourceException();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
int CChartCtrl::Create(CWnd *pParentWnd, const RECT &rect, UINT nID, DWORD dwStyle)
|
|
{
|
|
dwStyle |= WS_CLIPCHILDREN;
|
|
int Result = CWnd::Create(CHARTCTRL_CLASSNAME, _T(""), dwStyle, rect, pParentWnd, nID);
|
|
|
|
if (Result)
|
|
RefreshCtrl();
|
|
|
|
return Result;
|
|
}
|
|
|
|
void CChartCtrl::SetBackGradient(COLORREF Col1, COLORREF Col2, EGradientType GradientType)
|
|
{
|
|
m_bBackGradient = true;
|
|
m_BackGradientCol1 = Col1;
|
|
m_BackGradientCol2 = Col2;
|
|
m_BackGradientType = GradientType;
|
|
RefreshCtrl();
|
|
}
|
|
|
|
void CChartCtrl::EnableRefresh(bool bEnable)
|
|
{
|
|
if (bEnable)
|
|
m_iEnableRefresh++;
|
|
else
|
|
m_iEnableRefresh--;
|
|
if (m_iEnableRefresh > 0 && m_bPendingRefresh)
|
|
{
|
|
m_bPendingRefresh = false;
|
|
RefreshCtrl();
|
|
}
|
|
}
|
|
|
|
void CChartCtrl::UndoPanZoom()
|
|
{
|
|
EnableRefresh(false);
|
|
if (m_pAxes[BottomAxis])
|
|
m_pAxes[BottomAxis]->UndoZoom();
|
|
if (m_pAxes[LeftAxis])
|
|
m_pAxes[LeftAxis]->UndoZoom();
|
|
if (m_pAxes[TopAxis])
|
|
m_pAxes[TopAxis]->UndoZoom();
|
|
if (m_pAxes[RightAxis])
|
|
m_pAxes[RightAxis]->UndoZoom();
|
|
EnableRefresh(true);
|
|
}
|
|
|
|
void CChartCtrl::RefreshCtrl()
|
|
{
|
|
// Window is not created yet, so skip the refresh.
|
|
if (!GetSafeHwnd())
|
|
return;
|
|
if (m_iEnableRefresh < 1)
|
|
{
|
|
m_bPendingRefresh = true;
|
|
return;
|
|
}
|
|
|
|
// Retrieve the client rect and initialize the
|
|
// plotting rect
|
|
CClientDC dc(this) ;
|
|
CRect ClientRect;
|
|
GetClientRect(&ClientRect);
|
|
m_PlottingRect = ClientRect;
|
|
|
|
// If the backgroundDC was not created yet, create it (it
|
|
// is used to avoid flickering).
|
|
if (!m_BackgroundDC.GetSafeHdc() )
|
|
{
|
|
CBitmap memBitmap;
|
|
m_BackgroundDC.CreateCompatibleDC(&dc) ;
|
|
memBitmap.CreateCompatibleBitmap(&dc, ClientRect.Width(),ClientRect.Height()) ;
|
|
m_BackgroundDC.SelectObject(&memBitmap) ;
|
|
}
|
|
|
|
// Draw the chart background, which is not part of
|
|
// the DrawChart function (to avoid a background when
|
|
// printing).
|
|
DrawBackground(&m_BackgroundDC, ClientRect);
|
|
ClientRect.DeflateRect(3,3);
|
|
DrawChart(&m_BackgroundDC,ClientRect);
|
|
for (int i=0; i<4 ;i++)
|
|
{
|
|
if (m_pAxes[i])
|
|
m_pAxes[i]->UpdateScrollBarPos();
|
|
}
|
|
Invalidate();
|
|
}
|
|
|
|
void CChartCtrl::DrawChart(CDC* pDC, CRect ChartRect)
|
|
{
|
|
m_PlottingRect = ChartRect;
|
|
CSize TitleSize = m_pTitles->GetSize(pDC);
|
|
CRect rcTitle;
|
|
rcTitle = ChartRect;
|
|
rcTitle.bottom = TitleSize.cy;
|
|
|
|
ChartRect.top += TitleSize.cy;
|
|
m_pTitles->SetTitleRect(rcTitle);
|
|
m_pTitles->Draw(pDC);
|
|
|
|
m_pLegend->ClipArea(ChartRect,pDC);
|
|
|
|
//Clip all the margins (axis) of the client rect
|
|
int n=0;
|
|
for (n=0;n<4;n++)
|
|
{
|
|
if (m_pAxes[n])
|
|
{
|
|
m_pAxes[n]->SetAxisSize(ChartRect,m_PlottingRect);
|
|
m_pAxes[n]->Recalculate();
|
|
m_pAxes[n]->ClipMargin(ChartRect,m_PlottingRect,pDC);
|
|
}
|
|
}
|
|
for (n=0;n<4;n++)
|
|
{
|
|
if (m_pAxes[n])
|
|
{
|
|
m_pAxes[n]->SetAxisSize(ChartRect,m_PlottingRect);
|
|
m_pAxes[n]->Recalculate();
|
|
m_pAxes[n]->Draw(pDC);
|
|
}
|
|
}
|
|
|
|
CPen SolidPen(PS_SOLID,0,m_BorderColor);
|
|
CPen* pOldPen = pDC->SelectObject(&SolidPen);
|
|
|
|
pDC->MoveTo(m_PlottingRect.left,m_PlottingRect.top);
|
|
pDC->LineTo(m_PlottingRect.right,m_PlottingRect.top);
|
|
pDC->LineTo(m_PlottingRect.right,m_PlottingRect.bottom);
|
|
pDC->LineTo(m_PlottingRect.left,m_PlottingRect.bottom);
|
|
pDC->LineTo(m_PlottingRect.left,m_PlottingRect.top);
|
|
|
|
pDC->SelectObject(pOldPen);
|
|
DeleteObject(SolidPen);
|
|
|
|
TSeriesMap::iterator iter = m_mapSeries.begin();
|
|
for (iter; iter!=m_mapSeries.end(); iter++)
|
|
{
|
|
CRect drawingRect = m_PlottingRect;
|
|
drawingRect.bottom += 1;
|
|
drawingRect.right += 1;
|
|
iter->second->SetPlottingRect(drawingRect);
|
|
iter->second->DrawAll(pDC);
|
|
}
|
|
|
|
pDC->IntersectClipRect(m_PlottingRect);
|
|
// Draw the labels when all series have been drawn
|
|
for (iter=m_mapSeries.begin(); iter!=m_mapSeries.end(); iter++)
|
|
{
|
|
iter->second->DrawLabels(pDC);
|
|
}
|
|
pDC->SelectClipRgn(NULL);
|
|
|
|
// Draw the legend at the end (when floating it should come over the plotting area).
|
|
m_pLegend->Draw(pDC);
|
|
}
|
|
|
|
void CChartCtrl::DrawBackground(CDC* pDC, CRect ChartRect)
|
|
{
|
|
CBrush BrushBack;
|
|
BrushBack.CreateSolidBrush(m_BackColor) ;
|
|
if (!m_bBackGradient)
|
|
{
|
|
pDC->SetBkColor(m_BackColor);
|
|
pDC->FillRect(ChartRect,&BrushBack);
|
|
}
|
|
else
|
|
{
|
|
CChartGradient::DrawGradient(pDC,ChartRect,m_BackGradientCol1,
|
|
m_BackGradientCol2,m_BackGradientType);
|
|
}
|
|
|
|
// Draw the edge.
|
|
pDC->DrawEdge(ChartRect,EdgeType,BF_RECT);
|
|
}
|
|
|
|
void CChartCtrl::RefreshScreenAutoAxes()
|
|
{
|
|
for (int n=0;n<4;n++)
|
|
{
|
|
if (m_pAxes[n])
|
|
m_pAxes[n]->RefreshScreenAutoAxis();
|
|
}
|
|
}
|
|
|
|
CChartPointsSerie* CChartCtrl::CreatePointsSerie(bool bSecondaryHorizAxis,
|
|
bool bSecondaryVertAxis)
|
|
{
|
|
CChartPointsSerie* pNewSerie = new CChartPointsSerie(this);
|
|
AttachCustomSerie(pNewSerie, bSecondaryHorizAxis, bSecondaryVertAxis);
|
|
return pNewSerie;
|
|
}
|
|
|
|
CChartLineSerie* CChartCtrl::CreateLineSerie(bool bSecondaryHorizAxis,
|
|
bool bSecondaryVertAxis)
|
|
{
|
|
CChartLineSerie* pNewSerie = new CChartLineSerie(this);
|
|
AttachCustomSerie(pNewSerie, bSecondaryHorizAxis, bSecondaryVertAxis);
|
|
return pNewSerie;
|
|
}
|
|
|
|
CChartSurfaceSerie* CChartCtrl::CreateSurfaceSerie(bool bSecondaryHorizAxis,
|
|
bool bSecondaryVertAxis)
|
|
{
|
|
CChartSurfaceSerie* pNewSerie = new CChartSurfaceSerie(this);
|
|
AttachCustomSerie(pNewSerie, bSecondaryHorizAxis, bSecondaryVertAxis);
|
|
return pNewSerie;
|
|
}
|
|
|
|
CChartBarSerie* CChartCtrl::CreateBarSerie(bool bSecondaryHorizAxis,
|
|
bool bSecondaryVertAxis)
|
|
{
|
|
CChartBarSerie* pNewSerie = new CChartBarSerie(this);
|
|
AttachCustomSerie(pNewSerie, bSecondaryHorizAxis, bSecondaryVertAxis);
|
|
return pNewSerie;
|
|
}
|
|
|
|
CChartCandlestickSerie* CChartCtrl::CreateCandlestickSerie(bool bSecondaryHorizAxis,
|
|
bool bSecondaryVertAxis)
|
|
{
|
|
CChartCandlestickSerie* pNewSerie = new CChartCandlestickSerie(this);
|
|
AttachCustomSerie(pNewSerie, bSecondaryHorizAxis, bSecondaryVertAxis);
|
|
return pNewSerie;
|
|
}
|
|
|
|
CChartGanttSerie* CChartCtrl::CreateGanttSerie(bool bSecondaryHorizAxis,
|
|
bool bSecondaryVertAxis)
|
|
{
|
|
CChartGanttSerie* pNewSerie = new CChartGanttSerie(this);
|
|
AttachCustomSerie(pNewSerie, bSecondaryHorizAxis, bSecondaryVertAxis);
|
|
return pNewSerie;
|
|
}
|
|
|
|
void CChartCtrl::AttachCustomSerie(CChartSerie* pNewSeries,
|
|
bool bSecondaryHorizAxis,
|
|
bool bSecondaryVertAxis)
|
|
{
|
|
size_t ColIndex = m_mapSeries.size()%10;
|
|
|
|
CChartAxis* pHorizAxis = NULL;
|
|
CChartAxis* pVertAxis = NULL;
|
|
if (bSecondaryHorizAxis)
|
|
pHorizAxis = m_pAxes[TopAxis];
|
|
else
|
|
pHorizAxis = m_pAxes[BottomAxis];
|
|
if (bSecondaryVertAxis)
|
|
pVertAxis = m_pAxes[RightAxis];
|
|
else
|
|
pVertAxis = m_pAxes[LeftAxis];
|
|
|
|
ASSERT(pHorizAxis != NULL);
|
|
ASSERT(pVertAxis != NULL);
|
|
|
|
if (pNewSeries)
|
|
{
|
|
pNewSeries->SetPlottingRect(m_PlottingRect);
|
|
pNewSeries->SetColor(pSeriesColorTable[ColIndex]);
|
|
pNewSeries->m_pHorizontalAxis = pHorizAxis;
|
|
pNewSeries->m_pVerticalAxis = pVertAxis;
|
|
m_mapSeries[pNewSeries->GetSerieId()] = pNewSeries;
|
|
|
|
EnableRefresh(false);
|
|
pVertAxis->RegisterSeries(pNewSeries);
|
|
pVertAxis->RefreshAutoAxis();
|
|
pHorizAxis->RegisterSeries(pNewSeries);
|
|
pHorizAxis->RefreshAutoAxis();
|
|
|
|
// The series will need to be redrawn so we need to refresh the control
|
|
RefreshCtrl();
|
|
EnableRefresh(true);
|
|
}
|
|
}
|
|
|
|
CChartSerie* CChartCtrl::GetSerie(size_t uSerieId) const
|
|
{
|
|
CChartSerie* pToReturn = NULL;
|
|
TSeriesMap::const_iterator iter = m_mapSeries.find(uSerieId);
|
|
if (iter != m_mapSeries.end())
|
|
{
|
|
pToReturn = iter->second;
|
|
}
|
|
|
|
return pToReturn;
|
|
}
|
|
|
|
void CChartCtrl::RemoveSerie(unsigned uSerieId)
|
|
{
|
|
TSeriesMap::iterator iter = m_mapSeries.find(uSerieId);
|
|
if (iter != m_mapSeries.end())
|
|
{
|
|
CChartSerie* pToDelete = iter->second;
|
|
m_mapSeries.erase(iter);
|
|
|
|
EnableRefresh(false);
|
|
pToDelete->m_pVerticalAxis->UnregisterSeries(pToDelete);
|
|
pToDelete->m_pHorizontalAxis->UnregisterSeries(pToDelete);
|
|
pToDelete->m_pVerticalAxis->RefreshAutoAxis();
|
|
pToDelete->m_pHorizontalAxis->RefreshAutoAxis();
|
|
delete pToDelete;
|
|
RefreshCtrl();
|
|
EnableRefresh(true);
|
|
}
|
|
}
|
|
|
|
void CChartCtrl::RemoveAllSeries()
|
|
{
|
|
TSeriesMap::iterator iter = m_mapSeries.begin();
|
|
for (iter; iter != m_mapSeries.end(); iter++)
|
|
{
|
|
delete iter->second;
|
|
}
|
|
|
|
m_mapSeries.clear();
|
|
RefreshCtrl();
|
|
}
|
|
|
|
|
|
CChartAxis* CChartCtrl::GetBottomAxis() const
|
|
{
|
|
return (m_pAxes[BottomAxis]);
|
|
}
|
|
|
|
CChartAxis* CChartCtrl::GetLeftAxis() const
|
|
{
|
|
return (m_pAxes[LeftAxis]);
|
|
}
|
|
|
|
CChartAxis* CChartCtrl::GetTopAxis() const
|
|
{
|
|
return (m_pAxes[TopAxis]);
|
|
}
|
|
|
|
CChartAxis* CChartCtrl::GetRightAxis() const
|
|
{
|
|
return (m_pAxes[RightAxis]);
|
|
}
|
|
|
|
|
|
CDC* CChartCtrl::GetDC()
|
|
{
|
|
return &m_BackgroundDC;
|
|
}
|
|
|
|
|
|
size_t CChartCtrl::GetSeriesCount() const
|
|
{
|
|
return m_mapSeries.size();
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////////
|
|
// Mouse events
|
|
|
|
void CChartCtrl::OnMouseMove(UINT nFlags, CPoint point)
|
|
{
|
|
if (m_bRMouseDown && m_bPanEnabled)
|
|
{
|
|
if (point != m_PanAnchor)
|
|
{
|
|
EnableRefresh(false);
|
|
if (m_pAxes[LeftAxis])
|
|
m_pAxes[LeftAxis]->PanAxis(m_PanAnchor.y,point.y);
|
|
if (m_pAxes[RightAxis])
|
|
m_pAxes[RightAxis]->PanAxis(m_PanAnchor.y,point.y);
|
|
if (m_pAxes[BottomAxis])
|
|
m_pAxes[BottomAxis]->PanAxis(m_PanAnchor.x,point.x);
|
|
if (m_pAxes[TopAxis])
|
|
m_pAxes[TopAxis]->PanAxis(m_PanAnchor.x,point.x);
|
|
RefreshCtrl();
|
|
EnableRefresh(true);
|
|
// Force an immediate repaint of the window, so that the mouse messages
|
|
// are by passed (this allows for a smooth pan)
|
|
UpdateWindow();
|
|
|
|
m_PanAnchor = point;
|
|
}
|
|
}
|
|
|
|
if (m_bLMouseDown && m_bZoomEnabled)
|
|
{
|
|
m_rectZoomArea.BottomRight() = point;
|
|
Invalidate();
|
|
}
|
|
|
|
for (int i=0; i<4; i++)
|
|
{
|
|
if (m_pAxes[i])
|
|
m_pAxes[i]->m_pScrollBar->OnMouseLeave();
|
|
}
|
|
CWnd* pWnd = ChildWindowFromPoint(point);
|
|
if (pWnd != this)
|
|
{
|
|
CChartScrollBar* pScrollBar = dynamic_cast<CChartScrollBar*>(pWnd);
|
|
if (pScrollBar)
|
|
pScrollBar->OnMouseEnter();
|
|
}
|
|
|
|
if (m_PlottingRect.PtInRect(point))
|
|
{
|
|
TCursorMap::iterator iter = m_mapCursors.begin();
|
|
for (iter; iter!=m_mapCursors.end(); iter++)
|
|
iter->second->OnMouseMove(point);
|
|
|
|
Invalidate();
|
|
}
|
|
|
|
if (!m_bMouseVisible && m_PlottingRect.PtInRect(point))
|
|
SetCursor(NULL);
|
|
else
|
|
SetCursor(::LoadCursor(NULL,IDC_ARROW));
|
|
|
|
SendMouseEvent(CChartMouseListener::MouseMove, point);
|
|
CWnd::OnMouseMove(nFlags, point);
|
|
}
|
|
|
|
void CChartCtrl::OnLButtonDown(UINT nFlags, CPoint point)
|
|
{
|
|
SetCapture();
|
|
if (m_bZoomEnabled)
|
|
{
|
|
m_bLMouseDown = true;
|
|
m_rectZoomArea.TopLeft() = point;
|
|
m_rectZoomArea.BottomRight() = point;
|
|
}
|
|
|
|
if (m_PlottingRect.PtInRect(point))
|
|
{
|
|
TCursorMap::iterator iter = m_mapCursors.begin();
|
|
for (iter; iter!=m_mapCursors.end(); iter++)
|
|
iter->second->OnMouseButtonDown(point);
|
|
|
|
Invalidate();
|
|
}
|
|
|
|
SendMouseEvent(CChartMouseListener::LButtonDown, point);
|
|
CWnd::OnLButtonDown(nFlags, point);
|
|
}
|
|
|
|
void CChartCtrl::OnLButtonUp(UINT nFlags, CPoint point)
|
|
{
|
|
ReleaseCapture();
|
|
m_bLMouseDown = false;
|
|
if (m_bZoomEnabled)
|
|
{
|
|
if ( (m_rectZoomArea.left > m_rectZoomArea.right) ||
|
|
(m_rectZoomArea.top > m_rectZoomArea.bottom))
|
|
{
|
|
UndoPanZoom();
|
|
}
|
|
else if ( (m_rectZoomArea.left!=m_rectZoomArea.right) &&
|
|
(m_rectZoomArea.top!=m_rectZoomArea.bottom))
|
|
{
|
|
double MinVal = 0;
|
|
double MaxVal = 0;
|
|
|
|
if (m_pAxes[BottomAxis])
|
|
{
|
|
if (m_pAxes[BottomAxis]->IsInverted())
|
|
{
|
|
MaxVal = m_pAxes[BottomAxis]->ScreenToValue(m_rectZoomArea.left);
|
|
MinVal = m_pAxes[BottomAxis]->ScreenToValue(m_rectZoomArea.right);
|
|
}
|
|
else
|
|
{
|
|
MinVal = m_pAxes[BottomAxis]->ScreenToValue(m_rectZoomArea.left);
|
|
MaxVal = m_pAxes[BottomAxis]->ScreenToValue(m_rectZoomArea.right);
|
|
}
|
|
m_pAxes[BottomAxis]->SetZoomMinMax(MinVal,MaxVal);
|
|
}
|
|
|
|
if (m_pAxes[LeftAxis])
|
|
{
|
|
if (m_pAxes[LeftAxis]->IsInverted())
|
|
{
|
|
MaxVal = m_pAxes[LeftAxis]->ScreenToValue(m_rectZoomArea.bottom);
|
|
MinVal = m_pAxes[LeftAxis]->ScreenToValue(m_rectZoomArea.top);
|
|
}
|
|
else
|
|
{
|
|
MinVal = m_pAxes[LeftAxis]->ScreenToValue(m_rectZoomArea.bottom);
|
|
MaxVal = m_pAxes[LeftAxis]->ScreenToValue(m_rectZoomArea.top);
|
|
}
|
|
m_pAxes[LeftAxis]->SetZoomMinMax(MinVal,MaxVal);
|
|
}
|
|
|
|
if (m_pAxes[TopAxis])
|
|
{
|
|
if (m_pAxes[TopAxis]->IsInverted())
|
|
{
|
|
MaxVal = m_pAxes[TopAxis]->ScreenToValue(m_rectZoomArea.left);
|
|
MinVal = m_pAxes[TopAxis]->ScreenToValue(m_rectZoomArea.right);
|
|
}
|
|
else
|
|
{
|
|
MinVal = m_pAxes[TopAxis]->ScreenToValue(m_rectZoomArea.left);
|
|
MaxVal = m_pAxes[TopAxis]->ScreenToValue(m_rectZoomArea.right);
|
|
}
|
|
m_pAxes[TopAxis]->SetZoomMinMax(MinVal,MaxVal);
|
|
}
|
|
|
|
if (m_pAxes[RightAxis])
|
|
{
|
|
if (m_pAxes[RightAxis]->IsInverted())
|
|
{
|
|
MaxVal = m_pAxes[RightAxis]->ScreenToValue(m_rectZoomArea.bottom);
|
|
MinVal = m_pAxes[RightAxis]->ScreenToValue(m_rectZoomArea.top);
|
|
}
|
|
else
|
|
{
|
|
MinVal = m_pAxes[RightAxis]->ScreenToValue(m_rectZoomArea.bottom);
|
|
MaxVal = m_pAxes[RightAxis]->ScreenToValue(m_rectZoomArea.top);
|
|
}
|
|
m_pAxes[RightAxis]->SetZoomMinMax(MinVal,MaxVal);
|
|
}
|
|
|
|
RefreshCtrl();
|
|
}
|
|
}
|
|
|
|
if (m_PlottingRect.PtInRect(point))
|
|
{
|
|
TCursorMap::iterator iter = m_mapCursors.begin();
|
|
for (iter; iter!=m_mapCursors.end(); iter++)
|
|
iter->second->OnMouseButtonUp(point);
|
|
|
|
Invalidate();
|
|
}
|
|
|
|
SendMouseEvent(CChartMouseListener::LButtonUp, point);
|
|
CWnd::OnLButtonUp(nFlags, point);
|
|
}
|
|
|
|
void CChartCtrl::OnLButtonDblClk(UINT nFlags, CPoint point)
|
|
{
|
|
SendMouseEvent(CChartMouseListener::LButtonDoubleClick, point);
|
|
CWnd::OnLButtonDblClk(nFlags, point);
|
|
}
|
|
|
|
void CChartCtrl::OnRButtonDown(UINT nFlags, CPoint point)
|
|
{
|
|
SetCapture();
|
|
m_bRMouseDown = true;
|
|
if (m_bPanEnabled)
|
|
m_PanAnchor = point;
|
|
|
|
SendMouseEvent(CChartMouseListener::RButtonDown, point);
|
|
CWnd::OnRButtonDown(nFlags, point);
|
|
}
|
|
|
|
void CChartCtrl::OnRButtonUp(UINT nFlags, CPoint point)
|
|
{
|
|
ReleaseCapture();
|
|
m_bRMouseDown = false;
|
|
|
|
SendMouseEvent(CChartMouseListener::RButtonUp, point);
|
|
CWnd::OnRButtonUp(nFlags, point);
|
|
}
|
|
|
|
void CChartCtrl::OnRButtonDblClk(UINT nFlags, CPoint point)
|
|
{
|
|
SendMouseEvent(CChartMouseListener::RButtonDoubleClick, point);
|
|
CWnd::OnRButtonDblClk(nFlags, point);
|
|
}
|
|
|
|
void CChartCtrl::SendMouseEvent(CChartMouseListener::MouseEvent mouseEvent,
|
|
const CPoint& screenPoint) const
|
|
{
|
|
if (m_pMouseListener)
|
|
{
|
|
// Check where the click occured.
|
|
if (m_pTitles->IsPointInside(screenPoint))
|
|
m_pMouseListener->OnMouseEventTitle(mouseEvent,screenPoint);
|
|
if (m_pLegend->IsPointInside(screenPoint))
|
|
m_pMouseListener->OnMouseEventLegend(mouseEvent,screenPoint);
|
|
for (int i=0; i<4; i++)
|
|
{
|
|
if ( m_pAxes[i] && m_pAxes[i]->IsPointInside(screenPoint) )
|
|
m_pMouseListener->OnMouseEventAxis(mouseEvent,screenPoint,m_pAxes[i]);
|
|
}
|
|
if (m_PlottingRect.PtInRect(screenPoint))
|
|
m_pMouseListener->OnMouseEventPlotArea(mouseEvent,screenPoint);
|
|
}
|
|
|
|
// Check all the series in reverse order (check the series on top first).
|
|
TSeriesMap::const_reverse_iterator rIter = m_mapSeries.rbegin();
|
|
for(rIter; rIter!=m_mapSeries.rend(); rIter++)
|
|
{
|
|
if (rIter->second->OnMouseEvent(mouseEvent, screenPoint))
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CChartCtrl::OnSize(UINT nType, int cx, int cy)
|
|
{
|
|
CWnd::OnSize(nType, cx, cy);
|
|
|
|
// Force recreation of background DC
|
|
if (m_BackgroundDC.GetSafeHdc() )
|
|
m_BackgroundDC.DeleteDC();
|
|
|
|
RefreshCtrl();
|
|
}
|
|
|
|
double CChartCtrl::DateToValue(const COleDateTime& Date)
|
|
{
|
|
return (DATE)Date;
|
|
}
|
|
|
|
COleDateTime CChartCtrl::ValueToDate(double Value)
|
|
{
|
|
COleDateTime RetDate((DATE)Value);
|
|
return RetDate;
|
|
}
|
|
|
|
void CChartCtrl::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
|
|
{
|
|
CChartScrollBar* pChartBar = dynamic_cast<CChartScrollBar*>(pScrollBar);
|
|
if (pChartBar)
|
|
pChartBar->OnHScroll(nSBCode, nPos);
|
|
|
|
CWnd::OnHScroll(nSBCode, nPos, pScrollBar);
|
|
RefreshCtrl();
|
|
}
|
|
|
|
void CChartCtrl::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
|
|
{
|
|
CChartScrollBar* pChartBar = dynamic_cast<CChartScrollBar*>(pScrollBar);
|
|
if (pChartBar)
|
|
pChartBar->OnVScroll(nSBCode, nPos);
|
|
|
|
CWnd::OnVScroll(nSBCode, nPos, pScrollBar);
|
|
RefreshCtrl();
|
|
}
|
|
|
|
void CChartCtrl::Print(const TChartString& strTitle, CPrintDialog* pPrntDialog)
|
|
{
|
|
CDC dc;
|
|
if (pPrntDialog == NULL)
|
|
{
|
|
CPrintDialog printDlg(FALSE);
|
|
if (printDlg.DoModal() != IDOK) // Get printer settings from user
|
|
return;
|
|
|
|
dc.Attach(printDlg.GetPrinterDC()); // attach a printer DC
|
|
}
|
|
else
|
|
dc.Attach(pPrntDialog->GetPrinterDC()); // attach a printer DC
|
|
dc.m_bPrinting = TRUE;
|
|
|
|
DOCINFO di; // Initialise print doc details
|
|
memset(&di, 0, sizeof (DOCINFO));
|
|
di.cbSize = sizeof (DOCINFO);
|
|
di.lpszDocName = strTitle.c_str();
|
|
|
|
BOOL bPrintingOK = dc.StartDoc(&di); // Begin a new print job
|
|
|
|
CPrintInfo Info;
|
|
Info.m_rectDraw.SetRect(0,0, dc.GetDeviceCaps(HORZRES), dc.GetDeviceCaps(VERTRES));
|
|
|
|
OnBeginPrinting(&dc, &Info); // Initialise printing
|
|
for (UINT page = Info.GetMinPage(); page <= Info.GetMaxPage() && bPrintingOK; page++)
|
|
{
|
|
dc.StartPage(); // begin new page
|
|
Info.m_nCurPage = page;
|
|
OnPrint(&dc, &Info); // Print page
|
|
bPrintingOK = (dc.EndPage() > 0); // end page
|
|
}
|
|
OnEndPrinting(&dc, &Info); // Clean up after printing
|
|
|
|
if (bPrintingOK)
|
|
dc.EndDoc(); // end a print job
|
|
else
|
|
dc.AbortDoc(); // abort job.
|
|
|
|
dc.Detach(); // detach the printer DC
|
|
}
|
|
|
|
void CChartCtrl::OnBeginPrinting(CDC *pDC, CPrintInfo *pInfo)
|
|
{
|
|
// OnBeginPrinting() is called after the user has committed to
|
|
// printing by OK'ing the Print dialog, and after the framework
|
|
// has created a CDC object for the printer or the preview view.
|
|
|
|
// This is the right opportunity to set up the page range.
|
|
// Given the CDC object, we can determine how many rows will
|
|
// fit on a page, so we can in turn determine how many printed
|
|
// pages represent the entire document.
|
|
ASSERT(pDC && pInfo);
|
|
|
|
// Get a DC for the current window (will be a screen DC for print previewing)
|
|
CDC *pCurrentDC = GetDC(); // will have dimensions of the client area
|
|
if (!pCurrentDC)
|
|
return;
|
|
|
|
CSize PaperPixelsPerInch(pDC->GetDeviceCaps(LOGPIXELSX), pDC->GetDeviceCaps(LOGPIXELSY));
|
|
CSize ScreenPixelsPerInch(pCurrentDC->GetDeviceCaps(LOGPIXELSX), pCurrentDC->GetDeviceCaps(LOGPIXELSY));
|
|
|
|
// Create the printer font
|
|
int nFontSize = -10;
|
|
CString strFontName = _T("Arial");
|
|
m_PrinterFont.CreateFont(nFontSize, 0,0,0, FW_NORMAL, 0,0,0, DEFAULT_CHARSET,
|
|
OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY,
|
|
DEFAULT_PITCH | FF_DONTCARE, strFontName);
|
|
CFont *pOldFont = pDC->SelectObject(&m_PrinterFont);
|
|
|
|
// Get the page sizes (physical and logical)
|
|
m_PaperSize = CSize(pDC->GetDeviceCaps(HORZRES), pDC->GetDeviceCaps(VERTRES));
|
|
|
|
m_LogicalPageSize.cx = ScreenPixelsPerInch.cx * m_PaperSize.cx / PaperPixelsPerInch.cx * 3 / 4;
|
|
m_LogicalPageSize.cy = ScreenPixelsPerInch.cy * m_PaperSize.cy / PaperPixelsPerInch.cy * 3 / 4;
|
|
|
|
|
|
// Set up the print info
|
|
pInfo->SetMaxPage(1);
|
|
pInfo->m_nCurPage = 1; // start printing at page# 1
|
|
|
|
ReleaseDC(pCurrentDC);
|
|
pDC->SelectObject(pOldFont);
|
|
}
|
|
|
|
void CChartCtrl::OnPrint(CDC *pDC, CPrintInfo *pInfo)
|
|
{
|
|
if (!pDC || !pInfo)
|
|
return;
|
|
|
|
CFont *pOldFont = pDC->SelectObject(&m_PrinterFont);
|
|
|
|
// Set the page map mode to use GraphCtrl units
|
|
pDC->SetMapMode(MM_ANISOTROPIC);
|
|
pDC->SetWindowExt(m_LogicalPageSize);
|
|
pDC->SetViewportExt(m_PaperSize);
|
|
pDC->SetWindowOrg(0, 0);
|
|
|
|
// Header
|
|
pInfo->m_rectDraw.top = 0;
|
|
pInfo->m_rectDraw.left = 0;
|
|
pInfo->m_rectDraw.right = m_LogicalPageSize.cx;
|
|
pInfo->m_rectDraw.bottom = m_LogicalPageSize.cy;
|
|
|
|
DrawChart(pDC, &pInfo->m_rectDraw);
|
|
|
|
// SetWindowOrg back for next page
|
|
pDC->SetWindowOrg(0,0);
|
|
|
|
pDC->SelectObject(pOldFont);
|
|
}
|
|
|
|
void CChartCtrl::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
|
|
{
|
|
m_PrinterFont.DeleteObject();
|
|
// RefreshCtrl is needed because the print job
|
|
// modifies the chart components (axis, ...)
|
|
RefreshCtrl();
|
|
}
|
|
|
|
void CChartCtrl::GoToFirstSerie()
|
|
{
|
|
m_currentSeries = m_mapSeries.begin();
|
|
}
|
|
|
|
CChartSerie* CChartCtrl::GetNextSerie()
|
|
{
|
|
CChartSerie* pSeries = NULL;
|
|
if (m_currentSeries != m_mapSeries.end())
|
|
{
|
|
pSeries = m_currentSeries->second;
|
|
m_currentSeries++;
|
|
}
|
|
|
|
return pSeries;
|
|
}
|
|
|
|
#if _MFC_VER > 0x0600
|
|
void CChartCtrl::SaveAsImage(const TChartString& strFilename,
|
|
const CRect& rect,
|
|
int nBPP,
|
|
REFGUID guidFileType)
|
|
{
|
|
CImage chartImage;
|
|
CRect chartRect = rect;
|
|
if (chartRect.IsRectEmpty())
|
|
{
|
|
GetClientRect(&chartRect);
|
|
}
|
|
|
|
chartImage.Create(chartRect.Width(), chartRect.Height(), nBPP);
|
|
CDC newDC;
|
|
newDC.Attach(chartImage.GetDC());
|
|
|
|
DrawBackground(&newDC, chartRect);
|
|
chartRect.DeflateRect(3,3);
|
|
DrawChart(&newDC, chartRect);
|
|
|
|
newDC.Detach();
|
|
chartImage.Save(strFilename.c_str(), guidFileType);
|
|
chartImage.ReleaseDC();
|
|
}
|
|
#endif
|