/* * * 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(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(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(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