/* * * ChartAxis.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. * */ #include "stdafx.h" #include "ChartAxis.h" #include "ChartAxisLabel.h" #include "ChartGrid.h" #include "ChartCtrl.h" #include "Math.h" #include using namespace std; CChartAxis::CChartAxis() : m_pParentCtrl(NULL), m_bIsHorizontal(true), m_bIsInverted(false), m_AutoMode(NotAutomatic), m_bIsVisible(true), m_bIsSecondary(false), m_MaxValue(0), m_MinValue(0), m_UnzoomMax(0), m_UnzoomMin(0), m_bAutoTicks(true), m_bDiscrete(false),m_StartPos(0), m_EndPos(0), m_nFontSize(80), m_strFontName(_T("Microsoft Sans Serif")), m_TextColor(0), m_bAutoMargin(true), m_iMarginSize(0), m_bZoomEnabled(true), m_dZoomLimit(0.001), m_pScrollBar(NULL), m_AxisColor(RGB(0,0,0)) { m_pAxisGrid = new CChartGrid(); m_pAxisLabel = new CChartAxisLabel(); } CChartAxis::~CChartAxis() { if (m_pAxisGrid) { delete m_pAxisGrid; m_pAxisGrid = NULL; } if (m_pAxisLabel) { delete m_pAxisLabel; m_pAxisLabel = NULL; } if (m_pScrollBar) { delete m_pScrollBar; m_pScrollBar = NULL; } } int CChartAxis::GetPosition() { if (m_bIsHorizontal) { if (m_bIsSecondary) return 0; else return 100; } else { if (m_bIsSecondary) return 100; else return 0; } } void CChartAxis::SetParent(CChartCtrl* pParent) { m_pParentCtrl = pParent; m_pAxisGrid->m_pParentCtrl = pParent; m_pAxisLabel->m_pParentCtrl = pParent; } void CChartAxis::SetHorizontal(bool bHorizontal) { m_bIsHorizontal = bHorizontal; m_pAxisGrid->m_bIsHorizontal = bHorizontal; m_pAxisLabel->SetHorizontal(bHorizontal); } void CChartAxis::Draw(CDC *pDC) { if (!m_bIsVisible) return; if (pDC->GetSafeHdc() == NULL) return; // Initialize the different GDI objects CPen SolidPen(PS_SOLID,0,m_AxisColor); CFont NewFont; NewFont.CreatePointFont(m_nFontSize,m_strFontName.c_str(),pDC) ; CPen* pOldPen = pDC->SelectObject(&SolidPen); CFont* pOldFont = pDC->SelectObject(&NewFont); COLORREF OldTextColor = pDC->SetTextColor(m_TextColor); int iPrevMode = pDC->SetBkMode(TRANSPARENT); // Draw the axis line int Pos = 0; if (m_bIsHorizontal) { if (!m_bIsSecondary) Pos = m_AxisRect.top+1; else Pos = m_AxisRect.bottom-1; pDC->MoveTo(m_StartPos,Pos); pDC->LineTo(m_EndPos,Pos); } else { if (!m_bIsSecondary) Pos = m_AxisRect.right-1; else Pos = m_AxisRect.left+1; pDC->MoveTo(Pos,m_StartPos); pDC->LineTo(Pos,m_EndPos); } // Draw the label DrawLabel(pDC); // Clear the ticks on the grid m_pAxisGrid->ClearTicks(); // Now draw all the ticks and their label. if (m_MaxValue == m_MinValue) DrawTick(pDC,m_MinValue); else { double TickValue = GetFirstTickValue(); do { DrawTick(pDC,TickValue); } while (GetNextTickValue(TickValue, TickValue)); } // Draw the grid. m_pAxisGrid->Draw(pDC); // Reset the GDI objects pDC->SelectObject(pOldPen); SolidPen.DeleteObject(); pDC->SelectObject(pOldFont); NewFont.DeleteObject(); pDC->SetTextColor(OldTextColor); pDC->SetBkMode(iPrevMode); } void CChartAxis::DrawTick(CDC* pDC, double dTickVal) { long TickPos = GetTickPos(dTickVal); long lLabelPos = ValueToScreen(dTickVal); TChartString strBuffer = GetTickLabel(dTickVal); CSize TextSize = pDC->GetTextExtent(strBuffer.c_str()); CSize LabelSize = m_pAxisLabel->GetSize(pDC); bool bLabelOnAxis = IsLabelOnAxis(dTickVal); bool bTickOnAxis = true; if (m_bIsHorizontal) { if (TickPosm_EndPos) bTickOnAxis = false; if (!m_bIsSecondary) { if (bTickOnAxis) { pDC->MoveTo(TickPos,m_AxisRect.top+1); pDC->LineTo(TickPos,m_AxisRect.top+4); } if (bLabelOnAxis) { pDC->ExtTextOut(lLabelPos-TextSize.cx/2,m_AxisRect.top+5, ETO_CLIPPED|ETO_OPAQUE,NULL,strBuffer.c_str(),NULL); } } else { if (bTickOnAxis) { pDC->MoveTo(TickPos,m_AxisRect.bottom-1); pDC->LineTo(TickPos,m_AxisRect.bottom-4); } if (bLabelOnAxis) { pDC->ExtTextOut(lLabelPos-TextSize.cx/2,m_AxisRect.bottom-5-TextSize.cy, ETO_CLIPPED|ETO_OPAQUE,NULL,strBuffer.c_str(),NULL); } } } else { if (TickPos>m_StartPos || TickPosMoveTo(m_AxisRect.right-1,TickPos); pDC->LineTo(m_AxisRect.right-4,TickPos); } if (bLabelOnAxis) { pDC->ExtTextOut(m_AxisRect.left+LabelSize.cx+4,lLabelPos-TextSize.cy/2, ETO_CLIPPED|ETO_OPAQUE,NULL,strBuffer.c_str(),NULL); } } else { if (bTickOnAxis) { pDC->MoveTo(m_AxisRect.left+1,TickPos); pDC->LineTo(m_AxisRect.left+4,TickPos); } if (bLabelOnAxis) { pDC->ExtTextOut(m_AxisRect.left+6,lLabelPos-TextSize.cy/2, ETO_CLIPPED|ETO_OPAQUE,NULL,strBuffer.c_str(),NULL); } } } m_pAxisGrid->AddTick(TickPos); } bool CChartAxis::IsLabelOnAxis(double TickVal) { long lLabelPos = ValueToScreen(TickVal); bool bLabelOnAxis = true; if (m_bIsHorizontal) { if (lLabelPosm_EndPos) bLabelOnAxis = false; } else { if (lLabelPos>m_StartPos || lLabelPosGetSize(pDC); int HalfAxisPos = (int)fabs((m_EndPos + m_StartPos)/2.0); int XPos = 0; int YPos = 0; if (m_bIsHorizontal) { if (!m_bIsSecondary) { CSize TextSize = GetLargestTick(pDC); YPos = m_AxisRect.top + TextSize.cy + 2; XPos = HalfAxisPos - LabelSize.cx/2; } else { YPos = m_AxisRect.top + 0; XPos = HalfAxisPos - LabelSize.cx/2; } } else { if (!m_bIsSecondary) { YPos = HalfAxisPos + LabelSize.cy/2; XPos = m_AxisRect.left + 0; } else { YPos = HalfAxisPos + LabelSize.cy/2; XPos = m_AxisRect.right - LabelSize.cx - 2; } } m_pAxisLabel->SetPosition(XPos,YPos,pDC); m_pAxisLabel->Draw(pDC); } CSize CChartAxis::GetLargestTick(CDC* pDC) { CFont NewFont; NewFont.CreatePointFont(m_nFontSize,m_strFontName.c_str(),pDC); CFont* pOldFont = pDC->SelectObject(&NewFont); CSize MaxSize(0,0); if (m_MaxValue == m_MinValue) { TChartString strLabel = GetTickLabel(m_MinValue); MaxSize = pDC->GetTextExtent(strLabel.c_str(),strLabel.size()); } else { double TickValue = GetFirstTickValue(); do { if (IsLabelOnAxis(TickValue)) { TChartString strLabel = GetTickLabel(TickValue); CSize TextSize = pDC->GetTextExtent(strLabel.c_str(),strLabel.size()); if (TextSize.cy > MaxSize.cy) MaxSize.cy = TextSize.cy; if (TextSize.cx > MaxSize.cx) MaxSize.cx = TextSize.cx; } } while (GetNextTickValue(TickValue, TickValue)); } pDC->SelectObject(pOldFont); NewFont.DeleteObject(); return MaxSize; } void CChartAxis::SetInverted(bool bInverted) { m_bIsInverted = bInverted; RefreshScrollBar(); m_pParentCtrl->RefreshCtrl(); } void CChartAxis::SetAutomatic(bool bAutomatic) { // m_bIsAutomatic = bAutomatic; if (bAutomatic) { m_AutoMode = FullAutomatic; m_MinValue = m_MaxValue = 0; } else m_AutoMode = NotAutomatic; if (RefreshAutoAxis()) m_pParentCtrl->RefreshCtrl(); } void CChartAxis::SetAutomaticMode(EAxisAutoModes AutoMode) { m_AutoMode = AutoMode; if (m_AutoMode != NotAutomatic) m_MinValue = m_MaxValue = 0; if (RefreshAutoAxis()) m_pParentCtrl->RefreshCtrl(); } void CChartAxis::SetDiscrete(bool bDiscrete) { m_bDiscrete = bDiscrete; m_pParentCtrl->RefreshCtrl(); } void CChartAxis::SetMinMax(double Minimum, double Maximum) { ASSERT(Maximum>=Minimum); m_MinValue = m_UnzoomMin = Minimum; m_MaxValue = m_UnzoomMax = Maximum; RefreshScrollBar(); m_pParentCtrl->RefreshCtrl(); } void CChartAxis::RegisterSeries(CChartSerie* pSeries) { // First check if the series is already present in the list SeriesList::iterator iter = m_pRelatedSeries.begin(); for (iter; iter!=m_pRelatedSeries.end(); iter++) { if ( (*iter) == pSeries) return; } m_pRelatedSeries.push_back(pSeries); } void CChartAxis::UnregisterSeries(CChartSerie* pSeries) { SeriesList::iterator iter = m_pRelatedSeries.begin(); for (iter; iter!=m_pRelatedSeries.end(); iter++) { if ( (*iter) == pSeries) { m_pRelatedSeries.erase(iter); return; } } } bool CChartAxis::RefreshAutoAxis() { RefreshScrollBar(); bool bNeedRefresh = false; if (m_AutoMode == NotAutomatic) return bNeedRefresh; double SeriesMin = 0; double SeriesMax = 0; if (m_AutoMode == FullAutomatic) GetSeriesMinMax(SeriesMin, SeriesMax); if (m_AutoMode == ScreenAutomatic) GetSeriesScreenMinMax(SeriesMin, SeriesMax); if ( (SeriesMax!=m_MaxValue) || (SeriesMin!=m_MinValue) ) SetMinMax(SeriesMin,SeriesMax); return bNeedRefresh; } bool CChartAxis::RefreshScreenAutoAxis() { RefreshScrollBar(); bool bNeedRefresh = false; if (m_AutoMode != ScreenAutomatic) return bNeedRefresh; return RefreshAutoAxis(); } void CChartAxis::GetSeriesMinMax(double& Minimum, double& Maximum) { Minimum = 0; Maximum = 0; double TempMin = 0; double TempMax = 0; SeriesList::iterator iter = m_pRelatedSeries.begin(); if (iter != m_pRelatedSeries.end()) { if (m_bIsHorizontal) (*iter)->GetSerieXMinMax(Minimum,Maximum); else (*iter)->GetSerieYMinMax(Minimum,Maximum); } for (iter; iter!=m_pRelatedSeries.end(); iter++) { if (m_bIsHorizontal) (*iter)->GetSerieXMinMax(TempMin,TempMax); else (*iter)->GetSerieYMinMax(TempMin,TempMax); if (TempMin < Minimum) Minimum = TempMin; if (TempMax > Maximum) Maximum = TempMax; } } void CChartAxis::GetSeriesScreenMinMax(double& Minimum, double& Maximum) { Minimum = 0; Maximum = 0; double TempMin = 0; double TempMax = 0; SeriesList::iterator iter = m_pRelatedSeries.begin(); if (iter != m_pRelatedSeries.end()) { if (m_bIsHorizontal) (*iter)->GetSerieXScreenMinMax(Minimum,Maximum); else (*iter)->GetSerieYScreenMinMax(Minimum,Maximum); } for (iter; iter!=m_pRelatedSeries.end(); iter++) { if (m_bIsHorizontal) (*iter)->GetSerieXScreenMinMax(TempMin,TempMax); else (*iter)->GetSerieYScreenMinMax(TempMin,TempMax); if (TempMin < Minimum) Minimum = TempMin; if (TempMax > Maximum) Maximum = TempMax; } } long CChartAxis::ValueToScreen(double Value) const { long Offset = 0; long retVal = 0; if (m_MaxValue==m_MinValue) { Offset = (int)fabs((m_EndPos-m_StartPos)/2.0); if (m_bIsHorizontal) retVal = m_StartPos + Offset; else retVal = m_StartPos - Offset; } else if (!m_bDiscrete) retVal = ValueToScreenStandard(Value); else retVal = ValueToScreenDiscrete(Value); return retVal; } long CChartAxis::ValueToScreenStandard(double Value) const { long Offset = 0; long retVal = 0; Offset = (int)floor( (Value - m_MinValue) * GetAxisLenght()/(m_MaxValue-m_MinValue) ); if (m_bIsHorizontal) { if (!m_bIsInverted) retVal = (m_StartPos + Offset); else retVal = (m_EndPos - Offset); } else { if (!m_bIsInverted) retVal = (m_StartPos - Offset); else retVal = (m_EndPos + Offset); } return retVal; } double CChartAxis::ScreenToValue(long ScreenVal) const { if (m_MaxValue==m_MinValue) return m_MinValue; int AxisOffset = 0; if (!m_bIsHorizontal) { if (m_bIsInverted) AxisOffset = ScreenVal - m_EndPos; else AxisOffset = m_StartPos - ScreenVal; } else { if (!m_bIsInverted) AxisOffset = ScreenVal - m_StartPos; else AxisOffset = m_EndPos - ScreenVal; } return ( (AxisOffset * 1.0 / GetAxisLenght()*(m_MaxValue-m_MinValue)) + m_MinValue); } void CChartAxis::PanAxis(long PanStart, long PanEnd) { double StartVal = ScreenToValue(PanStart); double EndVal = ScreenToValue(PanEnd); double Shift = StartVal - EndVal; SetZoomMinMax(m_MinValue+Shift,m_MaxValue+Shift); } void CChartAxis::SetZoomMinMax(double Minimum, double Maximum) { if (!m_bZoomEnabled) return; if (m_MinValue == m_MaxValue) return; ASSERT(Maximum>=Minimum); m_MinValue = Minimum; if ( (Maximum - Minimum) < m_dZoomLimit) m_MaxValue = m_MinValue + m_dZoomLimit; else m_MaxValue = Maximum; RefreshScrollBar(); } long CChartAxis::GetAxisLenght() const { long Length = (long)fabs( (m_EndPos-m_StartPos) * 1.0); return Length; } void CChartAxis::SetVisible(bool bVisible) { m_bIsVisible = bVisible; m_pParentCtrl->RefreshCtrl(); } void CChartAxis::CreateScrollBar() { m_pScrollBar = new CChartScrollBar(this); m_pScrollBar->CreateScrollBar(m_pParentCtrl->GetPlottingRect()); } void CChartAxis::UpdateScrollBarPos() { CRect PlottingRect = m_pParentCtrl->GetPlottingRect(); PlottingRect.top++; PlottingRect.left++; // TODO: check if other toolbars are already present // on other axes. CRect Temp; m_pScrollBar->GetWindowRect(&Temp); if (m_bIsHorizontal && !m_bIsSecondary) PlottingRect.top = PlottingRect.bottom - Temp.Height(); if (!m_bIsHorizontal && !m_bIsSecondary) PlottingRect.right = PlottingRect.left + Temp.Width(); if (m_bIsHorizontal && m_bIsSecondary) PlottingRect.bottom = PlottingRect.top + Temp.Height(); if (!m_bIsHorizontal && m_bIsSecondary) PlottingRect.left = PlottingRect.right - Temp.Width(); m_pScrollBar->MoveWindow(&PlottingRect); } void CChartAxis::RefreshScrollBar() { if (m_pScrollBar) m_pScrollBar->Refresh(); } void CChartAxis::SetTextColor(COLORREF NewColor) { m_TextColor = NewColor; m_pParentCtrl->RefreshCtrl(); } void CChartAxis::SetAxisColor(COLORREF NewColor) { m_AxisColor = NewColor; m_pParentCtrl->RefreshCtrl(); } void CChartAxis::SetFont(int nPointSize, const TChartString& strFaceName) { m_nFontSize = nPointSize; m_strFontName = strFaceName; m_pParentCtrl->RefreshCtrl(); } void CChartAxis::SetMarginSize(bool bAuto, int iNewSize) { m_bAutoMargin = bAuto; m_iMarginSize = iNewSize; m_pParentCtrl->RefreshCtrl(); } void CChartAxis::EnableScrollBar(bool bEnabled) { if (m_pScrollBar) { m_pScrollBar->SetEnabled(bEnabled); if (bEnabled) m_pScrollBar->ShowWindow(SW_SHOW); else m_pScrollBar->ShowWindow(SW_HIDE); } } void CChartAxis::SetAutoHideScrollBar(bool bAutoHide) { if (m_pScrollBar) m_pScrollBar->SetAutoHide(bAutoHide); } bool CChartAxis::GetAutoHideScrollBar() const { if (m_pScrollBar) return (m_pScrollBar->GetAutoHide()); else return false; } void CChartAxis::UndoZoom() { SetMinMax(m_UnzoomMin,m_UnzoomMax); } void CChartAxis::SetAxisSize(const CRect& ControlRect, const CRect& MarginRect) { if (m_bIsHorizontal) { m_StartPos = MarginRect.left; m_EndPos = MarginRect.right; if (!m_bIsSecondary) { m_AxisRect = ControlRect; m_AxisRect.top = MarginRect.bottom; } else { m_AxisRect = ControlRect; m_AxisRect.bottom = MarginRect.top; } } else { m_StartPos = MarginRect.bottom; m_EndPos = MarginRect.top; if (!m_bIsSecondary) { m_AxisRect = ControlRect; m_AxisRect.right = MarginRect.left; } else { m_AxisRect = ControlRect; m_AxisRect.left = MarginRect.right; } } } int CChartAxis::ClipMargin(CRect ControlRect, CRect& MarginRect,CDC* pDC) { if (!m_bIsVisible) return 0; int Size = 0; CSize TickSize = GetLargestTick(pDC); CSize LabelSize = m_pAxisLabel->GetSize(pDC); if (m_bIsHorizontal) { if (!m_bAutoMargin) Size = m_iMarginSize; else { Size += 4 + 2; //Space above and under the text Size += TickSize.cy; Size += LabelSize.cy; m_iMarginSize = Size; } if (!m_bIsSecondary) { ControlRect.bottom -= Size; ControlRect.right -= TickSize.cx/2+3; if (ControlRect.bottom < MarginRect.bottom) MarginRect.bottom = ControlRect.bottom; if (ControlRect.right < MarginRect.right) MarginRect.right = ControlRect.right; } else { ControlRect.top += Size; ControlRect.right -= TickSize.cx/2+3; if (ControlRect.top > MarginRect.top) MarginRect.top = ControlRect.top; if (ControlRect.right < MarginRect.right) MarginRect.right = ControlRect.right; } } else { if (!m_bAutoMargin) Size = m_iMarginSize; else { Size += 7 + 1; //Space before and after the text + Tick Size += TickSize.cx; Size += LabelSize.cx + 2; m_iMarginSize = Size; } if (!m_bIsSecondary) { ControlRect.left += Size; ControlRect.top += TickSize.cy/2+3; if (ControlRect.top > MarginRect.top) MarginRect.top = ControlRect.top; if (ControlRect.left > MarginRect.left) MarginRect.left = ControlRect.left; } else { ControlRect.right -= Size; ControlRect.top += TickSize.cy/2+3; if (ControlRect.top > MarginRect.top) MarginRect.top = ControlRect.top; if (ControlRect.right < MarginRect.right) MarginRect.right = ControlRect.right; } } return Size; } void CChartAxis::Recalculate() { if (m_bAutoTicks) RefreshTickIncrement(); RefreshFirstTick(); } void CChartAxis::GetScrollbarSteps(int& iTotalSteps, int& iCurrentStep) { double SeriesMin=0, SeriesMax=0; GetSeriesMinMax(SeriesMin,SeriesMax); if ((m_MaxValue-m_MinValue) == 0 || (SeriesMax-SeriesMin)==0 ) { iTotalSteps = 1; iCurrentStep = 1; } else { double dStep = (m_MaxValue - m_MinValue) / 10.0; iTotalSteps = (int)ceil((SeriesMax - SeriesMin)/dStep); iCurrentStep = (int)(iTotalSteps * ((m_MinValue - SeriesMin)/(SeriesMax-SeriesMin))); } } void CChartAxis::SetAxisToScrollStep(int iPreviousStep, int iCurrentStep, bool bScrollInverted) { double dStep = (m_MaxValue - m_MinValue) / 10.0; double dOffset = (iCurrentStep - iPreviousStep) * dStep; if (bScrollInverted) SetZoomMinMax(m_MinValue-dOffset,m_MaxValue-dOffset); else SetZoomMinMax(m_MinValue+dOffset,m_MaxValue+dOffset); m_pParentCtrl->RefreshScreenAutoAxes(); } BOOL CChartAxis::IsPointInside(const CPoint& screenPoint) const { return m_AxisRect.PtInRect(screenPoint); }