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;elsem_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());elsereturn 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 textvoid Draw(CDC* pDC);// To register/Unregister series related to this axisvoid 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 axisbool m_bIsInverted; // Indicates if the axis is invertedbool m_bIsAutomatic; // Indicates if the axis is automaticbool 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 axisdouble m_MinValue; double m_UnzoomMin; // Min and max values of the axis before it has been zoomeddouble m_UnzoomMax; // (used when we unzoom the chart -> go back to previous state)bool m_bAutoTicks; // Specify if the tick increment is manual or automaticdouble m_TickIncrement; // Indicates the space between ticks (in axis value) or for log axis the mult base between two ticksdouble m_FirstTickVal;unsigned int m_DecCount; // Number of decimals to displayint m_StartPos; // Start position of the axisint 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 automaticallybool m_bAutoMargin;int m_iMarginSize;COLORREF m_TextColor;// Data for the date/time axis type.TChartString m_strDTTickFormat; // Format of the date/time tick labelsbool 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 textSize += 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 + TickSize += 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;elsereturn 100;}else{if (m_bIsSecondary)return 100;elsereturn 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;elseCalculateTickIncrement();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 lineint Pos = 0;if (m_bIsHorizontal){if (!m_bIsSecondary)Pos = m_ObjectRect.top+1;elsePos = m_ObjectRect.bottom-1;pDC->MoveTo(m_StartPos,Pos);pDC->LineTo(m_EndPos,Pos);}else{if (!m_bIsSecondary)Pos = m_ObjectRect.right-1;elsePos = 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;elsereturn m_StartPos - Offset;}if (m_AxisType != atLogarithmic)Offset = (int)floor( (Value - m_MinValue) * GetAxisLenght()/(m_MaxValue-m_MinValue) );elseOffset = (int)floor((log10(Value)-log10(m_MinValue)) * GetAxisLenght()/(log10(m_MaxValue)-log10(m_MinValue)) );if (m_bIsHorizontal){if (!m_bIsInverted)return (m_StartPos + Offset);elsereturn (m_EndPos - Offset);}else{if (!m_bIsInverted)return (m_StartPos - Offset);elsereturn (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; elseAxisOffset = m_StartPos - ScreenVal;}else{if (!m_bIsInverted)AxisOffset = ScreenVal - m_StartPos;elseAxisOffset = m_EndPos - ScreenVal;}if (m_AxisType != atLogarithmic)return ( (AxisOffset * 1.0 / GetAxisLenght()*(m_MaxValue-m_MinValue)) + m_MinValue);elsereturn (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;elsem_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 listSeriesList::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;elsePixelSpace = 30;}elsePixelSpace = 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 incrementdouble 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);elseSetDecimals(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;elsem_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;elsem_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;elsem_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;}elsem_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;elsem_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);elsenLogDecCount = 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);elsestrBuffer.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;elseMaxDecCount = 0;strBuffMax.Format(_T("%.*f"),MaxDecCount,m_MaxValue);int MinDecCount = (int)log10(m_MinValue);if (MinDecCount < 0)MinDecCount = -MinDecCount;elseMinDecCount = 0;strBuffMin.Format(_T("%.*f"),MinDecCount,m_MinValue);if (strBuffMin.GetLength() > strBuffMax.GetLength() )strBuffer = strBuffMin;elsestrBuffer = 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);elsem_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());elsereturn 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 lineint Pos = 0;if (m_bIsHorizontal){if (!m_bIsSecondary)Pos = m_ObjectRect.top+1;elsePos = 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;elsePos = 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); }