ChartCtrl源码剖析之——CChartAxis类
CChartAxis类用来绘制波形控件的坐标轴,这个源码相对较复杂,当初阅读的时候耗费了不少精力来理解源码中的一些实现细节。
CChartAxis类的头文件。
#if !defined(AFX_CHARTAXIS_H__063D695C_43CF_4A46_8AA0_C7E00268E0D3__INCLUDED_) #define AFX_CHARTAXIS_H__063D695C_43CF_4A46_8AA0_C7E00268E0D3__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include "ChartObject.h" #include "ChartScrollBar.h" #include "ChartString.h" #include <afx.h> #include <list> class CChartGrid; class CChartSerie; class CChartAxisLabel; class CChartAxis : public CChartObject { friend CChartCtrl; friend CChartGrid; friend CChartSerie; friend CChartScrollBar; public: enum AxisType { atStandard = 0, atLogarithmic, atDateTime }; void SetAxisType(AxisType Type); AxisType GetAxisType() const { return m_AxisType; } enum TimeInterval { tiSecond, tiMinute, tiHour, tiDay, tiMonth, tiYear }; void SetDateTimeIncrement(bool bAuto, TimeInterval Interval, int Multiplier); void SetTickIncrement(bool bAuto, double Increment); double GetTickIncrement() const { return m_TickIncrement; } void SetDateTimeFormat(bool bAutomatic, const TChartString& strFormat); int GetPosition(); void SetInverted(bool bNewValue); bool IsInverted() const { return m_bIsInverted; } void SetLogarithmic(bool bNewValue) { if (bNewValue) m_AxisType = atLogarithmic; else m_AxisType = atStandard; RefreshAutoAxis(); } bool IsLogarithmic() const { return (m_AxisType == atLogarithmic); } void SetAutomatic(bool bNewValue); bool IsAutomatic() const { return m_bIsAutomatic; } void SetMinMax(double Minimum, double Maximum); void GetMinMax(double& Minimum, double& Maximum) const { Minimum = m_MinValue; Maximum = m_MaxValue; } long ValueToScreen(double Value) const; double ScreenToValue(long ScreenVal) const; void SetTextColor(COLORREF NewColor); COLORREF GetTextColor() const { return m_TextColor; } void SetFont(int nPointSize, const TChartString& strFaceName); CChartAxisLabel* GetLabel() const { return m_pAxisLabel; } CChartGrid* GetGrid() const { return m_pAxisGrid; } CChartAxis(CChartCtrl* pParent,bool bHoriz); virtual ~CChartAxis(); void SetMarginSize(bool bAuto, int iNewSize); void SetPanZoomEnabled(bool bEnabled) { m_bZoomEnabled = bEnabled; } void SetZoomLimit(double dLimit) { m_dZoomLimit = dLimit; } void EnableScrollBar(bool bEnabled); bool ScrollBarEnabled() const { if (m_pScrollBar) return (m_pScrollBar->GetEnabled()); else return false; } void SetAutoHideScrollBar(bool bAutoHide); bool GetAutoHideScrollBar() const; private: void CalculateTickIncrement(); void CalculateFirstTick(); double GetNextTickValue(double Previous); CString GetTickLabel(double Tick); CSize GetLargestTick(CDC* pDC); void Recalculate(); COleDateTime AddMonthToDate(const COleDateTime& Date, int iMonthsToAdd); void DrawLabel(CDC* pDC); void RefreshDTTickFormat(); void PanAxis(long PanStart, long PanEnd); void SetZoomMinMax(double Minimum, double Maximum); void UndoZoom(); void SetDecimals(int NewValue) { m_DecCount = NewValue; } bool IsHorizontal() const { return m_bIsHorizontal; } int GetAxisLenght() const; void SetSecondary(bool bNewVal) { m_bIsSecondary = bNewVal; } bool GetSecondary() const { return m_bIsSecondary; } bool RefreshAutoAxis(); void GetSeriesMinMax(double& Minimum, double& Maximum); void SetAxisSize(CRect ControlRect,CRect MarginRect); int ClipMargin(CRect ControlRect,CRect& MarginRect,CDC* pDC); // Allows to calculate the margin required to displayys ticks and text void Draw(CDC* pDC); // To register/Unregister series related to this axis void RegisterSeries(CChartSerie* pSeries); void UnregisterSeries(CChartSerie* pSeries); void CreateScrollBar(); void UpdateScrollBarPos(); void RefreshScrollBar(); AxisType m_AxisType; // Type of the axis (standard, log or date/time) bool m_bIsHorizontal; // Indicates if this is an horizontal or vertical axis bool m_bIsInverted; // Indicates if the axis is inverted bool m_bIsAutomatic; // Indicates if the axis is automatic bool m_bIsSecondary; // If the axis is secondary, it will be positioned to // the right (vertical) or to the top (horizontal) double m_MaxValue; // Maximum value on the axis double m_MinValue; double m_UnzoomMin; // Min and max values of the axis before it has been zoomed double m_UnzoomMax; // (used when we unzoom the chart -> go back to previous state) bool m_bAutoTicks; // Specify if the tick increment is manual or automatic double m_TickIncrement; // Indicates the space between ticks (in axis value) or for log axis the mult base between two ticks double m_FirstTickVal; unsigned int m_DecCount; // Number of decimals to display int m_StartPos; // Start position of the axis int m_EndPos; int m_nFontSize; TChartString m_strFontName; CChartGrid* m_pAxisGrid; CChartAxisLabel* m_pAxisLabel; typedef std::list<CChartSerie*> SeriesList; SeriesList m_pRelatedSeries; // List containing pointers to series related to this axis // The user can specify the size of the margin, instead of // having it calculated automatically bool m_bAutoMargin; int m_iMarginSize; COLORREF m_TextColor; // Data for the date/time axis type. TChartString m_strDTTickFormat; // Format of the date/time tick labels bool m_bAutoTickFormat; TimeInterval m_BaseInterval; int m_iDTTickIntervalMult; bool m_bZoomEnabled; double m_dZoomLimit; CChartScrollBar* m_pScrollBar; }; #endif // !defined(AFX_CHARTAXIS_H__063D695C_43CF_4A46_8AA0_C7E00268E0D3__INCLUDED_)
CChartAxis类的源文件。
#include "stdafx.h" #include "ChartAxis.h" #include "ChartAxisLabel.h" #include "ChartGrid.h" #include "ChartCtrl.h" #include "Math.h" #include <sstream> using namespace std; #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// CChartAxis::CChartAxis(CChartCtrl* pParent,bool bHoriz):CChartObject(pParent) { m_AxisType = atStandard; m_bIsHorizontal = bHoriz; m_bIsInverted = false; m_bIsAutomatic = false; m_bIsSecondary = false; m_MaxValue = m_UnzoomMax = 10; m_MinValue = m_UnzoomMin = 0; m_bAutoTicks = true; m_TickIncrement = 1; m_FirstTickVal = 0; m_DecCount = 0; m_StartPos = m_EndPos = 0; m_nFontSize = 80; m_strFontName = _T("Microsoft Sans Serif"); m_pAxisGrid = new CChartGrid(pParent,bHoriz); m_pAxisLabel = new CChartAxisLabel(pParent,bHoriz); m_bAutoMargin = true; m_iMarginSize = 0; m_TextColor = m_ObjectColor; m_strDTTickFormat = _T("%d %b"); m_bAutoTickFormat = true; m_BaseInterval = tiDay; m_iDTTickIntervalMult = 1; m_bZoomEnabled = true; m_dZoomLimit = 0.001; m_pScrollBar = NULL; } 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; } } void CChartAxis::SetAxisType(AxisType Type) { m_AxisType = Type; m_pParent->RefreshCtrl(); } 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; } 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::SetAutomatic(bool bNewValue) { m_bIsAutomatic = bNewValue; if (m_bIsAutomatic) m_MinValue = m_MaxValue = 0; if (RefreshAutoAxis()) m_pParent->RefreshCtrl(); } void CChartAxis::SetTickIncrement(bool bAuto, double Increment) { if (m_AxisType == atDateTime) return; m_bAutoTicks = bAuto; if (!m_bAutoTicks) m_TickIncrement = Increment; else CalculateTickIncrement(); CalculateFirstTick(); m_pParent->RefreshCtrl(); } void CChartAxis::SetDateTimeIncrement(bool bAuto, TimeInterval Interval, int Multiplier) { if (m_AxisType != atDateTime) return; m_bAutoTicks = bAuto; if (!m_bAutoTicks) { m_BaseInterval = Interval; m_iDTTickIntervalMult = Multiplier; } } void CChartAxis::SetDateTimeFormat(bool bAutomatic, const TChartString& strFormat) { m_bAutoTickFormat = bAutomatic; m_strDTTickFormat = strFormat; m_pParent->RefreshCtrl(); } void CChartAxis::SetAxisSize(CRect ControlRect,CRect MarginRect) { if (m_bIsHorizontal) { m_StartPos = MarginRect.left; m_EndPos = MarginRect.right; if (!m_bIsSecondary) { CRect AxisSize = ControlRect; AxisSize.top = MarginRect.bottom; SetRect(AxisSize); } else { CRect AxisSize = ControlRect; AxisSize.bottom = MarginRect.top; SetRect(AxisSize); } } else { m_StartPos = MarginRect.bottom; m_EndPos = MarginRect.top; if (!m_bIsSecondary) { CRect AxisSize = ControlRect; AxisSize.right = MarginRect.left; SetRect(AxisSize); } else { CRect AxisSize = ControlRect; AxisSize.left = MarginRect.right; SetRect(AxisSize); } } } void CChartAxis::Recalculate() { CalculateTickIncrement(); CalculateFirstTick(); } void CChartAxis::Draw(CDC *pDC) { if (!m_bIsVisible) return; if (pDC->GetSafeHdc() == NULL) return; CPen SolidPen(PS_SOLID,0,m_ObjectColor); CPen* pOldPen; CFont NewFont; CFont* pOldFont; COLORREF OldTextColor; NewFont.CreatePointFont(m_nFontSize,m_strFontName.c_str(),pDC) ; pOldPen = pDC->SelectObject(&SolidPen); pOldFont = pDC->SelectObject(&NewFont); OldTextColor = pDC->SetTextColor(m_TextColor); int iPrevMode = pDC->SetBkMode(TRANSPARENT); CSize LabelSize = m_pAxisLabel->GetSize(pDC); // Draw the axis line int Pos = 0; if (m_bIsHorizontal) { if (!m_bIsSecondary) Pos = m_ObjectRect.top+1; else Pos = m_ObjectRect.bottom-1; pDC->MoveTo(m_StartPos,Pos); pDC->LineTo(m_EndPos,Pos); } else { if (!m_bIsSecondary) Pos = m_ObjectRect.right-1; else Pos = m_ObjectRect.left+1; pDC->MoveTo(Pos,m_StartPos); pDC->LineTo(Pos,m_EndPos); } // Draw the label DrawLabel(pDC); m_pAxisGrid->ClearTicks(); //char szBuffer[255]; CString strBuffer; int TickPos = 0; double TickValue = m_FirstTickVal; do { strBuffer = GetTickLabel(TickValue); CSize TextSize = pDC->GetTextExtent(strBuffer); TickPos = ValueToScreen(TickValue); if (m_bIsHorizontal) { if (!m_bIsSecondary) { pDC->MoveTo(TickPos,m_ObjectRect.top+1); pDC->LineTo(TickPos,m_ObjectRect.top+4); pDC->ExtTextOut(TickPos-TextSize.cx/2,m_ObjectRect.top+5, ETO_CLIPPED|ETO_OPAQUE,NULL,strBuffer,NULL); } else { pDC->MoveTo(TickPos,m_ObjectRect.bottom-1); pDC->LineTo(TickPos,m_ObjectRect.bottom-4); pDC->ExtTextOut(TickPos-TextSize.cx/2,m_ObjectRect.bottom-5-TextSize.cy, ETO_CLIPPED|ETO_OPAQUE,NULL,strBuffer,NULL); } } else { if (!m_bIsSecondary) { pDC->MoveTo(m_ObjectRect.right-1,TickPos); pDC->LineTo(m_ObjectRect.right-4,TickPos); pDC->ExtTextOut(m_ObjectRect.left+LabelSize.cx+4,TickPos-TextSize.cy/2, ETO_CLIPPED|ETO_OPAQUE,NULL,strBuffer,NULL); } else { pDC->MoveTo(m_ObjectRect.left+1,TickPos); pDC->LineTo(m_ObjectRect.left+4,TickPos); pDC->ExtTextOut(m_ObjectRect.left+6,TickPos-TextSize.cy/2, ETO_CLIPPED|ETO_OPAQUE,NULL,strBuffer,NULL); } } m_pAxisGrid->AddTick(TickPos); TickValue = GetNextTickValue(TickValue); } while ((TickValue < m_MaxValue+0.0000001) && (m_TickIncrement || m_iDTTickIntervalMult) ); CRect Size = m_pParent->GetPlottingRect(); m_pAxisGrid->SetRect(Size); m_pAxisGrid->Draw(pDC); pDC->SelectObject(pOldPen); DeleteObject(SolidPen); pDC->SelectObject(pOldFont); DeleteObject(NewFont); pDC->SetTextColor(OldTextColor); pDC->SetBkMode(iPrevMode); } void CChartAxis::DrawLabel(CDC* pDC) { // Draw the axis label. CSize LabelSize = m_pAxisLabel->GetSize(pDC); int HalfAxisPos = (int)fabs((m_EndPos + m_StartPos)/2.0); int XPos = 0; int YPos = 0; if (m_bIsHorizontal) { if (!m_bIsSecondary) { CString Buffer; Buffer.Format(_T("%.*f"),m_DecCount,m_MaxValue); CSize TextSize = pDC->GetTextExtent(Buffer); YPos = m_ObjectRect.top + TextSize.cy + 2; XPos = HalfAxisPos - LabelSize.cx/2; } else { YPos = m_ObjectRect.top + 0; XPos = HalfAxisPos - LabelSize.cx/2; } } else { if (!m_bIsSecondary) { YPos = HalfAxisPos + LabelSize.cy/2; XPos = m_ObjectRect.left + 0; } else { YPos = HalfAxisPos + LabelSize.cy/2; XPos = m_ObjectRect.right - LabelSize.cx - 2; } } m_pAxisLabel->SetPosition(XPos,YPos,pDC); m_pAxisLabel->Draw(pDC); } void CChartAxis::SetMinMax(double Minimum, double Maximum) { if (Minimum > Maximum) { TRACE("Maximum axis value must be > minimum axis value"); return; } m_MinValue = m_UnzoomMin = Minimum; m_MaxValue = m_UnzoomMax = Maximum; RefreshScrollBar(); m_pParent->RefreshCtrl(); } void CChartAxis::SetInverted(bool bNewValue) { m_bIsInverted = bNewValue; RefreshScrollBar(); m_pParent->RefreshCtrl(); } int CChartAxis::GetAxisLenght() const { int Length = (int)fabs( (m_EndPos-m_StartPos) * 1.0); return Length; } long CChartAxis::ValueToScreen(double Value) const { long Offset = 0; if (m_MaxValue==m_MinValue) { Offset = (int)fabs((m_EndPos-m_StartPos)/2.0); if (m_bIsHorizontal) return m_StartPos + Offset; else return m_StartPos - Offset; } if (m_AxisType != atLogarithmic) Offset = (int)floor( (Value - m_MinValue) * GetAxisLenght()/(m_MaxValue-m_MinValue) ); else Offset = (int)floor((log10(Value)-log10(m_MinValue)) * GetAxisLenght()/(log10(m_MaxValue)-log10(m_MinValue)) ); if (m_bIsHorizontal) { if (!m_bIsInverted) return (m_StartPos + Offset); else return (m_EndPos - Offset); } else { if (!m_bIsInverted) return (m_StartPos - Offset); else return (m_EndPos + Offset); } } double CChartAxis::ScreenToValue(long ScreenVal) const { if (m_MaxValue==m_MinValue) return 0; 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; } if (m_AxisType != atLogarithmic) return ( (AxisOffset * 1.0 / GetAxisLenght()*(m_MaxValue-m_MinValue)) + m_MinValue); else return (pow(10.0,(AxisOffset *1.0 / GetAxisLenght()*(log10(m_MaxValue)-log10(m_MinValue)) ) + log10(m_MinValue)) ); } void CChartAxis::SetTextColor(COLORREF NewColor) { m_TextColor = NewColor; m_pParent->RefreshCtrl(); } void CChartAxis::SetFont(int nPointSize, const TChartString& strFaceName) { m_nFontSize = nPointSize; m_strFontName = strFaceName; m_pParent->RefreshCtrl(); } void CChartAxis::SetMarginSize(bool bAuto, int iNewSize) { m_bAutoMargin = bAuto; m_iMarginSize = iNewSize; m_pParent->RefreshCtrl(); } void CChartAxis::PanAxis(long PanStart, long PanEnd) { double StartVal = ScreenToValue(PanStart); double EndVal = ScreenToValue(PanEnd); if (m_AxisType != atLogarithmic) { double Shift = StartVal - EndVal; SetZoomMinMax(m_MinValue+Shift,m_MaxValue+Shift); } else { double Factor = StartVal/EndVal; SetZoomMinMax(m_MinValue*Factor,m_MaxValue*Factor); } } void CChartAxis::SetZoomMinMax(double Minimum, double Maximum) { if (!m_bZoomEnabled) return; if (Minimum > Maximum) { TRACE("Maximum axis value must be > minimum axis value"); return; } m_MinValue = Minimum; if ( (Maximum - Minimum) < m_dZoomLimit && m_AxisType!=atLogarithmic) m_MaxValue = m_MinValue + m_dZoomLimit; else m_MaxValue = Maximum; RefreshScrollBar(); } void CChartAxis::UndoZoom() { SetMinMax(m_UnzoomMin,m_UnzoomMax); } 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; } } } COleDateTime CChartAxis::AddMonthToDate(const COleDateTime& Date, int iMonthsToAdd) { COleDateTime newDate; int nMonths = Date.GetMonth()-1 + iMonthsToAdd; int nYear = Date.GetYear() + nMonths/12; newDate.SetDateTime(nYear,nMonths%12+1,Date.GetDay(),Date.GetHour(), Date.GetMinute(),Date.GetSecond()); return newDate; } bool CChartAxis::RefreshAutoAxis() { RefreshScrollBar(); bool bNeedRefresh = false; if (!m_bIsAutomatic) return bNeedRefresh; double SeriesMin = 0; double SeriesMax = 0; GetSeriesMinMax(SeriesMin, SeriesMax); if ( (SeriesMax!=m_MaxValue) || (SeriesMin!=m_MinValue) ) SetMinMax(SeriesMin,SeriesMax); return bNeedRefresh; } 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::CalculateTickIncrement() { if (!m_bAutoTicks) return; if (m_MaxValue == m_MinValue) { m_iDTTickIntervalMult = 0; m_TickIncrement = 0; return; } int PixelSpace; if (m_bIsHorizontal) { if (m_AxisType == atDateTime) PixelSpace = 60; else PixelSpace = 30; } else PixelSpace = 20; int MaxTickNumber = (int)fabs((m_EndPos-m_StartPos)/PixelSpace * 1.0); //Calculate the appropriate TickSpace (1 tick every 30 pixel +/-) switch (m_AxisType) { case atLogarithmic: m_TickIncrement = 10; break; case atStandard: { //Temporary tick increment double TickIncrement = (m_MaxValue-m_MinValue)/MaxTickNumber; // Calculate appropriate tickSpace (not rounded on 'strange values' but // on something like 1, 2 or 5*10^X where X is optimalized for showing the most // significant digits) int Zeros = (int)floor(log10(TickIncrement)); double MinTickIncrement = pow(10.0,Zeros); int Digits = 0; if (Zeros<0) { //We must set decimal places. In the other cases, Digits will be 0. Digits = (int)fabs(Zeros*1.0); } if (MinTickIncrement>=TickIncrement) { m_TickIncrement = MinTickIncrement; SetDecimals(Digits); } else if (MinTickIncrement*2>=TickIncrement) { m_TickIncrement = MinTickIncrement*2; SetDecimals(Digits); } else if (MinTickIncrement*5>=TickIncrement) { m_TickIncrement = MinTickIncrement*5; SetDecimals(Digits); } else if (MinTickIncrement*10>=TickIncrement) { m_TickIncrement = MinTickIncrement*10; if (Digits) SetDecimals(Digits-1); else SetDecimals(Digits); } } break; case atDateTime: { COleDateTime StartDate(m_MinValue); COleDateTime EndDate(m_MaxValue); COleDateTimeSpan minTickInterval = (EndDate - StartDate)/MaxTickNumber; double Seconds = minTickInterval.GetTotalSeconds(); double Minutes = minTickInterval.GetTotalMinutes(); double Hours = minTickInterval.GetTotalHours(); double Days = minTickInterval.GetTotalDays(); if (Seconds < 60) { m_BaseInterval = tiSecond; if (Seconds > 30) { m_BaseInterval = tiMinute; m_iDTTickIntervalMult = 1; } else if (Seconds > 10) m_iDTTickIntervalMult = 30; else if (Seconds > 5) m_iDTTickIntervalMult = 10; else if (Seconds > 2) m_iDTTickIntervalMult = 5; else m_iDTTickIntervalMult = 1; } else if (Minutes < 60) { m_BaseInterval = tiMinute; if (Minutes > 30) { m_BaseInterval = tiHour; m_iDTTickIntervalMult = 1; } else if (Minutes > 10) m_iDTTickIntervalMult = 30; else if (Minutes > 5) m_iDTTickIntervalMult = 10; else if (Minutes > 2) m_iDTTickIntervalMult = 5; else m_iDTTickIntervalMult = 2; } else if (Hours < 24) { m_BaseInterval = tiHour; if (Hours > 12) { m_BaseInterval = tiDay; m_iDTTickIntervalMult = 1; } else if (Hours > 6) m_iDTTickIntervalMult = 12; else if (Hours > 2) m_iDTTickIntervalMult = 6; else m_iDTTickIntervalMult = 2; } else if (Days < 31) { m_BaseInterval = tiDay; if (Days > 7) { m_BaseInterval = tiMonth; m_iDTTickIntervalMult = 1; } else if (Days > 2) { m_BaseInterval = tiDay; m_iDTTickIntervalMult = 7; } else m_iDTTickIntervalMult = 2; } else if (Days < 365) { m_BaseInterval = tiMonth; if (Days > 186) // Approx 6 months { m_BaseInterval = tiYear; m_iDTTickIntervalMult = 1; } else if (Days > 124) m_iDTTickIntervalMult = 6; else if (Days > 62) m_iDTTickIntervalMult = 4; else m_iDTTickIntervalMult = 2; } else { m_BaseInterval = tiYear; m_iDTTickIntervalMult = (int)Days/365 + 1; } if (m_bAutoTickFormat) RefreshDTTickFormat(); } break; } } void CChartAxis::CalculateFirstTick() { switch (m_AxisType) { case atStandard: { m_FirstTickVal = 0; if (m_TickIncrement!=0) { if (m_MinValue == 0) m_FirstTickVal = 0; else if (m_MinValue>0) { m_FirstTickVal = (int)(m_MinValue/m_TickIncrement) * m_TickIncrement; while (m_FirstTickVal<m_MinValue) m_FirstTickVal += m_TickIncrement; } else { m_FirstTickVal = (int)(m_MinValue/m_TickIncrement) * m_TickIncrement; while (m_FirstTickVal>m_MinValue) m_FirstTickVal -= m_TickIncrement; if (!(m_FirstTickVal == m_MinValue)) m_FirstTickVal += m_TickIncrement; } } else // m_TickIncrement!=0 { m_FirstTickVal = m_MinValue; } } break; case atLogarithmic: { int LogBase = (int)log10(m_MinValue); m_FirstTickVal = pow(10.0,LogBase); } break; case atDateTime: { COleDateTime dtMin((DATE)m_MinValue); COleDateTime dtFirstTick(dtMin); switch (m_BaseInterval) { case tiSecond: dtFirstTick.SetDateTime(dtMin.GetYear(),dtMin.GetMonth(),dtMin.GetDay(), dtMin.GetHour(),dtMin.GetMinute(),dtMin.GetSecond()+1); break; case tiMinute: dtFirstTick.SetDateTime(dtMin.GetYear(),dtMin.GetMonth(),dtMin.GetDay(), dtMin.GetHour(),dtMin.GetMinute(),0); if (dtMin.GetSecond() != 0) dtFirstTick += COleDateTimeSpan(0,0,1,0); break; case tiHour: dtFirstTick.SetDateTime(dtMin.GetYear(),dtMin.GetMonth(),dtMin.GetDay(), dtMin.GetHour(),0,0); if ( (dtMin.GetMinute()!=0) || (dtMin.GetSecond()!=0) ) dtFirstTick += COleDateTimeSpan(0,1,0,0); break; case tiDay: dtFirstTick.SetDate(dtMin.GetYear(),dtMin.GetMonth(),dtMin.GetDay()); if ( (dtMin.GetHour()!=0) || (dtMin.GetMinute()!=0) || (dtMin.GetSecond()!=0) ) { dtFirstTick += COleDateTimeSpan(1,0,0,0); } break; case tiMonth: { dtFirstTick.SetDate(dtMin.GetYear(),dtMin.GetMonth(),1); if ((dtMin.GetDay()!=1) || (dtMin.GetHour()!=0) || (dtMin.GetMinute()!=0) || (dtMin.GetSecond()!=0) ) { dtFirstTick = AddMonthToDate(dtFirstTick,1); } } break; case tiYear: break; } m_FirstTickVal = (DATE)dtFirstTick; } break; } } double CChartAxis::GetNextTickValue(double Previous) { double NewTick = 0; switch (m_AxisType) { case atStandard: NewTick = Previous + m_TickIncrement; break; case atLogarithmic: NewTick = Previous * m_TickIncrement; break; case atDateTime: { COleDateTime dtTick((DATE)Previous); COleDateTimeSpan dtSpan; switch (m_BaseInterval) { case tiSecond: dtSpan.SetDateTimeSpan(0,0,0,m_iDTTickIntervalMult); dtTick += dtSpan; break; case tiMinute: dtSpan.SetDateTimeSpan(0,0,m_iDTTickIntervalMult,0); dtTick += dtSpan; break; case tiHour: dtSpan.SetDateTimeSpan(0,m_iDTTickIntervalMult,0,0); dtTick += dtSpan; break; case tiDay: dtSpan.SetDateTimeSpan(m_iDTTickIntervalMult,0,0,0); dtTick += dtSpan; break; case tiMonth: { dtTick = AddMonthToDate(dtTick,m_iDTTickIntervalMult); } break; case tiYear: break; } NewTick = (DATE)dtTick; } break; } return NewTick; } CString CChartAxis::GetTickLabel(double TickValue) { CString strLabel; switch (m_AxisType) { case atStandard: strLabel.Format(_T("%.*f"),m_DecCount,TickValue); // ssLabel << setprecision(m_DecCount) << TickValue; // sprintf(szBuffer,"%.*f",m_DecCount,TickValue); break; case atLogarithmic: { double fLogDecCount; int nLogDecCount; fLogDecCount = log10(TickValue); if (fLogDecCount < 0.0) nLogDecCount = (int)(fabs(fLogDecCount) + 0.1); else nLogDecCount = 0; strLabel.Format(_T("%.*f"), nLogDecCount, TickValue); } break; case atDateTime: { COleDateTime tickTime((DATE)TickValue); strLabel = tickTime.Format(m_strDTTickFormat.c_str()); // ssLabel << tickTime.Format(m_strDTTickFormat.c_str()); // strcpy(szBuffer,strLabel); } break; } return strLabel; } void CChartAxis::RefreshDTTickFormat() { switch (m_BaseInterval) { case tiSecond: m_strDTTickFormat = _T("%H:%M:%S"); break; case tiMinute: m_strDTTickFormat = _T("%H:%M"); break; case tiHour: m_strDTTickFormat = _T("%H:00"); break; case tiDay: m_strDTTickFormat = _T("%d %b"); break; case tiMonth: m_strDTTickFormat = _T("%b %Y"); break; case tiYear: m_strDTTickFormat = _T("%Y"); break; } } CSize CChartAxis::GetLargestTick(CDC* pDC) { CSize TickSize; CFont* pOldFont; CFont NewFont; NewFont.CreatePointFont(m_nFontSize,m_strFontName.c_str(),pDC); pOldFont = pDC->SelectObject(&NewFont); CString strBuffer; strBuffer.Format(_T("%.*f"),m_DecCount,m_MaxValue); if (m_bIsHorizontal) TickSize = pDC->GetTextExtent(strBuffer); else { switch (m_AxisType) { case atStandard: { int MaxChars = abs( (int)log10(fabs(m_MaxValue) )) + 1; int MinChars = abs( (int)log10(fabs(m_MinValue) )) + 1; if (m_MinValue<0) MinChars++; if (m_MaxValue<0) MaxChars++; if (MaxChars>MinChars) strBuffer.Format(_T("%.*f"),m_DecCount,m_MaxValue); else strBuffer.Format(_T("%.*f"),m_DecCount,m_MinValue); } break; case atLogarithmic: { CString strBuffMax; CString strBuffMin; int MaxDecCount = (int)log10(m_MaxValue); if (MaxDecCount < 0) MaxDecCount = -MaxDecCount; else MaxDecCount = 0; strBuffMax.Format(_T("%.*f"),MaxDecCount,m_MaxValue); int MinDecCount = (int)log10(m_MinValue); if (MinDecCount < 0) MinDecCount = -MinDecCount; else MinDecCount = 0; strBuffMin.Format(_T("%.*f"),MinDecCount,m_MinValue); if (strBuffMin.GetLength() > strBuffMax.GetLength() ) strBuffer = strBuffMin; else strBuffer = strBuffMax; } break; case atDateTime: { double TickValue = m_FirstTickVal; CString strTemp; do { strTemp = GetTickLabel(TickValue); if (strTemp.GetLength() > strBuffer.GetLength() ) strBuffer = strTemp; TickValue = GetNextTickValue(TickValue); } while ((TickValue < m_MaxValue+0.0000001) && (m_TickIncrement|| m_iDTTickIntervalMult) ); } break; } TickSize = pDC->GetTextExtent(strBuffer); } pDC->SelectObject(pOldFont); DeleteObject(NewFont); return TickSize; } 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::CreateScrollBar() { m_pScrollBar = new CChartScrollBar(this); m_pScrollBar->CreateScrollBar(m_pParent->GetPlottingRect()); } void CChartAxis::UpdateScrollBarPos() { CRect PlottingRect = m_pParent->GetPlottingRect(); PlottingRect.top++; PlottingRect.left++; 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(); }
这份源码大概读了有至少四遍,总算把这份源码吃透。回过头来看当初觉得不理解的地方,感觉一开始的心态不正确,没下定决心把它搞定,只走马观花的读肯定行不通。经过反复的阅读,之前一些不理解的地方都慢慢的消化了。一开始不理解的函数有ClipMargin、CalculateTickIncrement、CalculateFirstTick、ValueToScreen、ScreenToValue函数看不懂,ClipMargin函数用来设置轴与控件边缘的间距,CalculateTickIncremen函数用来计算标记间的增量,CalculateFirstTick函数用来计算第一个标记的值,ValueToScreen函数用来将值转化为屏幕中的值,ScreenToValue函数用来将屏幕中的值转化为具体的数值。在读自绘控件相关的源码时,一定要先建立所绘制模块在屏幕中的具体位置这样一个体系,这样在读源码的时候才会有的放矢。
假如想给横轴、纵轴加箭头和单位,需要在CChartAxis::Draw函数里面进行。调整后代码如下:
CString strXAxisBuffer("ms"), strYAxisBuffer("Y"); // Draw the axis line int Pos = 0; if (m_bIsHorizontal) { if (!m_bIsSecondary) Pos = m_ObjectRect.top+1; else Pos = m_ObjectRect.bottom-1; pDC->MoveTo(m_StartPos,Pos); pDC->LineTo(m_EndPos + 25, Pos); pDC->MoveTo(m_EndPos + 15, Pos - 5); pDC->LineTo(m_EndPos + 25, Pos); pDC->LineTo(m_EndPos + 15, Pos + 5); pDC->ExtTextOut(m_EndPos + 15, m_ObjectRect.top + 5, ETO_CLIPPED|ETO_OPAQUE, NULL, strXAxisBuffer, NULL); } else { if (!m_bIsSecondary) Pos = m_ObjectRect.right-1; else Pos = m_ObjectRect.left+1; pDC->MoveTo(Pos,m_StartPos); pDC->LineTo(Pos, m_EndPos - 25); pDC->MoveTo(Pos - 5, m_EndPos - 15); pDC->LineTo(Pos, m_EndPos - 25); pDC->LineTo(Pos - 5, m_EndPos - 15); pDC->ExtTextOut(Pos + 15, m_EndPos - 20, ETO_CLIPPED|ETO_OPAQUE, NULL, strYAxisBuffer, NULL); }
在添加轴单位这块还有另外一种做法,直接使用CChartAxisLabel::SetText函数也可以实现同样的效果,但需要将CChartAxis::DrawLabel函数调整如下:
void CChartAxis::DrawLabel(CDC* pDC) { CSize LabelSize = m_pAxisLabel->GetSize(pDC); int XPos = 0; int YPos = 0; if(m_bIsHorizontal) { if(!m_bIsSecondary) { YPos = m_AxisRect.top + 0; } else { YPos = m_AxisRect.bottom + 0; } if(!m_bIsInverted) { XPos = m_AxisRect.right - LabelSize.cx/2 + 10; } else { XPos = m_AxisRect.left - 2 - LabelSize.cx/2; } } else { CSize TextSize = GetLargestTick(pDC); if(!m_bIsSecondary) { XPos = m_AxisRect.right - 5 - LabelSize.cx; } else { XPos = m_AxisRect.left + 5; } YPos = m_AxisRect.top - 2 - LabelSize.cy/2 - 10; } m_pAxisLabel->SetPosition(XPos, YPos, pDC); m_pAxisLabel->Draw(pDC); }
作者:常想一二 出处:http://www.cnblogs.com/wolfmvp/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 如果文中有什么错误,欢迎指出。以免更多的人被误导。 |