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;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);
}

 

转载于:https://www.cnblogs.com/wolfmvp/p/7212093.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/283042.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于.net开发的自助餐饮系统

本文系 EMQ&Intel 联合举办的首届“中国物联网数据基础设施最佳案例评选大赛“个人开发者赛道一等奖作品。项目简介智能餐饮自助结算系统是一个由称重系统、显示屏、自助扫码盒和 Intel CPU 组成的智能自助结算终端&#xff0c;将装有菜品的托盘放到秤盘上结算&#xff0c;…

java打包维护_java打包详解

from yahh2008的blog: http://www.matrix.org.cn/blog/yahh2008/兄弟&#xff0c;对java着迷吗&#xff0c;或者是为了自己的生计&#xff0c;不论怎样都欢迎你进入精彩java世界&#xff0c;welcome&#xff01;可能你刚刚对每个人说&#xff1a;Hello World&#xff01;也或者…

Linux高级文本处理之sed(三)

sed高级命令sed允许将多行内容读取到模式空间&#xff0c;这样你就可以匹配跨越多行的内容。本篇笔记主要介绍这些命令&#xff0c;它们能够创建多行模式空间并且处理之。其中&#xff0c;N/D/P这三个多行命令分别对应于小写的n/d/p命令&#xff0c;后者我们在上一篇已经介绍。…

如何在 C# 程序中注入恶意 DLL ?

一&#xff1a;背景 前段时间在训练营上课的时候就有朋友提到一个问题&#xff0c;为什么 Windbg 附加到 C# 程序后&#xff0c;程序就处于中断状态了&#xff1f;它到底是如何实现的&#xff1f;其实简而言之就是线程的远程注入&#xff0c;这一篇就展开说一下。二&#xff1a…

练习题|网络编程-socket开发

原文&#xff1a;https://www.cnblogs.com/shengyang17/p/8822745.html 1、什么是C/S架构&#xff1f; C指的是client&#xff08;客户端软件&#xff09;&#xff0c;S指的是Server&#xff08;服务端软件&#xff09;&#xff0c;C/S架构的软件&#xff0c;实现服务端软件与客…

ABP vNext微服务架构详细教程(补充篇)——单层模板(上)

简介在之前的《ABP vNext微服务架构详细教程》系列中&#xff0c;我们已经构建了完整的微服务架构实例&#xff0c;但是在开发过程中&#xff0c;我们会发现每个基础服务都包含10个类库&#xff0c;这是给予DDD四层架构下ABP的实现方案&#xff0c;但是实际使用中我们会发现&am…

mybatis源码学习(三):MappedStatement的解析过程

我们之前介绍过MappedStatement表示的是XML中的一个SQL。类当中的很多字段都是SQL中对应的属性。我们先来了解一下这个类的属性&#xff1a; public final class MappedStatement {private String resource;private Configuration configuration;//sql的IDprivate String id;//…

C# 二十年语法变迁之 C# 8参考

C# 二十年语法变迁之 C# 8参考自从 C# 于 2000 年推出以来&#xff0c;该语言的规模已经大大增加&#xff0c;我不确定任何人是否有可能在任何时候都对每一种语言特性都有深入的了解。因此&#xff0c;我想写一系列快速参考文章&#xff0c;总结自 C# 2.0 以来所有主要的新语言…

windows 提权 cve-2018-8897

windows 提权 cve-2018-8897影响范围&#xff1a;基本上是全版本具体影响范围看详情&#xff1a;https://portal.msrc.microsoft.co … isory/CVE-2018-8897http://www.o2oxy.cn/wp-content/uploads/2018/06/cve-2018-8897.rar转载于:https://blog.51cto.com/9861015/2126608

java servlet练习测试

步骤&#xff1a; 0、首先创建web project&#xff0c;工程名&#xff1a;test_servlet 1、编写Servlet&#xff0c;TestServlet.java文件内容&#xff1a; package com.ouyang.servlet;import java.io.IOException; import java.sql.Connection; import java.sql.DriverManage…

《ASP.NET Core 6框架揭秘》实例演示[19]:数据加解密与哈希

数据保护&#xff08;Data Protection&#xff09;框架旨在解决数据在传输与持久化存储过程中的一致性&#xff08;Integrity&#xff09;和机密性&#xff08;confidentiality&#xff09;问题&#xff0c;前者用于检验接收到的数据是否经过篡改&#xff0c;后者通过对原始的数…

如何在ABAP Netweaver和CloudFoundry里记录并查看日志

Netweaver 要记录日志需要有一个checkpoint group&#xff0c;可以自行创建也可以使用标准的。这里我重用标准的group&#xff1a;DEMO_CHECKPOINT_GROUP。 tcode SAAB&#xff0c;点Display <->Activate进入编辑模式&#xff0c;将Logpoints设置为"Log"&#…

如何成为有效学习的高手(许岑)——思维导图

总结自许岑精品课《如何成为有效学习的高手》&#xff0c;图片看不清的可以看下面。 最后有彩蛋&#xff01;最后有彩蛋&#xff01;最后有彩蛋&#xff01; 定义 高效学习的定义&#xff1a;找到最适合自己的学习手法&#xff0c;在相对短的时间内集中注意力&#xff0c;以解决…

WPF Canvas 平滑笔迹

WPF Canvas 平滑笔迹控件名&#xff1a;CanvasHandWriting作者&#xff1a;小封&#xff08;邝攀升&#xff09;原文链接&#xff1a; https://github.com/WPFDevelopersOrg/WPFDevelopers编辑&#xff1a;驚鏵完整的思路如下收集路径点集。平均采样路径点集。将路径点集转为…

NetSpeed

NetSpeed公司提供的NOC包括三部分&#xff0c;可以通过NocStudio进行配置生成。 1)NetSpeed Orion&#xff0c;面向快速SoC design的可综合平台。 2)Linley NetSpeed NoC面向复杂的interconnect实现&#xff0c;同时优化内部physical implementation和timing closure. NoC是基于…

js ajax java传参_ajax参数传递与后台接收

ajax参数传递与后台接收Servlet中读取http参数的方法Enumeration getParameterNames() 返回一个 String 对象的枚举&#xff0c;包含在该请求中包含的参数的名称String getParameter(String name) 以字符串形式返回请求参数的值&#xff0c;或者如果参数不存在则返回 null。Str…

init 访问器只能初始化时赋值,是真的吗?

前言C# 提供的 init 关键字用于在属性中定义访问器方法&#xff0c;可以让属性仅能在对象初始化的时候被赋值&#xff0c;其他时候只能为只读属性的形式。例如下面代码可以正常执行&#xff1a;public class Demo {public string Name { get; init; } }var demo new Demo { Na…

eclipse实现代码块折叠-类似于VS中的#region……#endregion

背 景 刚才在写代码的时候&#xff0c;写了十几行可以说是重复的代码&#xff1a; 如果整个方法或类中代码多了&#xff0c;感觉它们太TM占地方了&#xff0c;给读者在阅读代码上造成很大的困难&#xff0c;于是想到能不能把他们“浓缩”成一行&#xff0c;脑子里第一个闪现出的…

java定义基础变量语句_java语言基础-变量

一丶变量的基本概念1.什么是变量(1).内存中的一个存储区域(2).该区域有自己的名称(变量名),和类型(数据类型)(3.)该区域的数据可以在同一类型范围内不断变化(定义变量的主要目的是因为数据的不确定性)2.为什么要定义变量用来不断存放同一类型的常量&#xff0c;并可以重复使用3…

C# WPF MVVM模式[经典]案例

01—前言Caliburn.Micro(简称CM)一经推出便备受推崇&#xff0c;作为一款MVVM开发模式的经典框架&#xff0c;越来越多的受到wpf开发者的青睐.我们看一下官方的描述&#xff1a;Caliburn是一个为Xaml平台设计的小型但功能强大的框架。Micro实现了各种UI模式&#xff0c;用于解决…