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 = ,
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 = ;
m_MinValue = m_UnzoomMin = ;
m_bAutoTicks = true;
m_TickIncrement = ;
m_FirstTickVal = ;
m_DecCount = ;
m_StartPos = m_EndPos = ;
m_nFontSize = ;
m_strFontName = _T("Microsoft Sans Serif");
m_pAxisGrid = new CChartGrid(pParent,bHoriz);
m_pAxisLabel = new CChartAxisLabel(pParent,bHoriz);
m_bAutoMargin = true;
m_iMarginSize = ;
m_TextColor = m_ObjectColor;
m_strDTTickFormat = _T("%d %b");
m_bAutoTickFormat = true;
m_BaseInterval = tiDay;
m_iDTTickIntervalMult = ;
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 ;
int Size = ;
CSize TickSize = GetLargestTick(pDC);
CSize LabelSize = m_pAxisLabel->GetSize(pDC);
if (m_bIsHorizontal)
{
if (!m_bAutoMargin)
Size = m_iMarginSize;
else
{
Size += + ; //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/+;
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/+;
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 += + ; //Space before and after the text + Tick
Size += TickSize.cx;
Size += LabelSize.cx + ;
m_iMarginSize = Size;
}
if (!m_bIsSecondary)
{
ControlRect.left += Size;
ControlRect.top += TickSize.cy/+;
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/+;
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 ;
else
return ;
}
else
{
if (m_bIsSecondary)
return ;
else
return ;
}
}
void CChartAxis::SetAutomatic(bool bNewValue)
{
m_bIsAutomatic = bNewValue;
if (m_bIsAutomatic)
m_MinValue = m_MaxValue = ;
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,,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 = ;
if (m_bIsHorizontal)
{
if (!m_bIsSecondary)
Pos = m_ObjectRect.top+;
else
Pos = m_ObjectRect.bottom-;
pDC->MoveTo(m_StartPos,Pos);
pDC->LineTo(m_EndPos,Pos);
}
else
{
if (!m_bIsSecondary)
Pos = m_ObjectRect.right-;
else
Pos = m_ObjectRect.left+;
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 = ;
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+);
pDC->LineTo(TickPos,m_ObjectRect.top+);
pDC->ExtTextOut(TickPos-TextSize.cx/,m_ObjectRect.top+,
ETO_CLIPPED|ETO_OPAQUE,NULL,strBuffer,NULL);
}
else
{
pDC->MoveTo(TickPos,m_ObjectRect.bottom-);
pDC->LineTo(TickPos,m_ObjectRect.bottom-);
pDC->ExtTextOut(TickPos-TextSize.cx/,m_ObjectRect.bottom--TextSize.cy,
ETO_CLIPPED|ETO_OPAQUE,NULL,strBuffer,NULL);
}
}
else
{
if (!m_bIsSecondary)
{
pDC->MoveTo(m_ObjectRect.right-,TickPos);
pDC->LineTo(m_ObjectRect.right-,TickPos);
pDC->ExtTextOut(m_ObjectRect.left+LabelSize.cx+,TickPos-TextSize.cy/,
ETO_CLIPPED|ETO_OPAQUE,NULL,strBuffer,NULL);
}
else
{
pDC->MoveTo(m_ObjectRect.left+,TickPos);
pDC->LineTo(m_ObjectRect.left+,TickPos);
pDC->ExtTextOut(m_ObjectRect.left+,TickPos-TextSize.cy/,
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 = ;
int YPos = ;
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 + ;
XPos = HalfAxisPos - LabelSize.cx/;
}
else
{
YPos = m_ObjectRect.top + ;
XPos = HalfAxisPos - LabelSize.cx/;
}
}
else
{
if (!m_bIsSecondary)
{
YPos = HalfAxisPos + LabelSize.cy/;
XPos = m_ObjectRect.left + ;
}
else
{
YPos = HalfAxisPos + LabelSize.cy/;
XPos = m_ObjectRect.right - LabelSize.cx - ;
}
}
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 = ;
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 ;
int AxisOffset = ;
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()- + iMonthsToAdd;
int nYear = Date.GetYear() + nMonths/;
newDate.SetDateTime(nYear,nMonths%+,Date.GetDay(),Date.GetHour(),
Date.GetMinute(),Date.GetSecond());
return newDate;
}
bool CChartAxis::RefreshAutoAxis()
{
RefreshScrollBar();
bool bNeedRefresh = false;
if (!m_bIsAutomatic)
return bNeedRefresh;
double SeriesMin = ;
double SeriesMax = ;
GetSeriesMinMax(SeriesMin, SeriesMax);
if ( (SeriesMax!=m_MaxValue) || (SeriesMin!=m_MinValue) )
SetMinMax(SeriesMin,SeriesMax);
return bNeedRefresh;
}
void CChartAxis::GetSeriesMinMax(double& Minimum, double& Maximum)
{
Minimum = ;
Maximum = ;
double TempMin = ;
double TempMax = ; 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 = ;
m_TickIncrement = ;
return;
}
int PixelSpace;
if (m_bIsHorizontal)
{
if (m_AxisType == atDateTime)
PixelSpace = ;
else
PixelSpace = ;
}
else
PixelSpace = ;
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 = ;
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 = ;
if (Zeros<)
{
//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*>=TickIncrement)
{
m_TickIncrement = MinTickIncrement*;
SetDecimals(Digits);
}
else if (MinTickIncrement*>=TickIncrement)
{
m_TickIncrement = MinTickIncrement*;
SetDecimals(Digits);
}
else if (MinTickIncrement*>=TickIncrement)
{
m_TickIncrement = MinTickIncrement*;
if (Digits)
SetDecimals(Digits-);
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 < )
{
m_BaseInterval = tiSecond;
if (Seconds > )
{
m_BaseInterval = tiMinute;
m_iDTTickIntervalMult = ;
}
else if (Seconds > )
m_iDTTickIntervalMult = ;
else if (Seconds > )
m_iDTTickIntervalMult = ;
else if (Seconds > )
m_iDTTickIntervalMult = ;
else
m_iDTTickIntervalMult = ;
}
else if (Minutes < )
{
m_BaseInterval = tiMinute;
if (Minutes > )
{
m_BaseInterval = tiHour;
m_iDTTickIntervalMult = ;
}
else if (Minutes > )
m_iDTTickIntervalMult = ;
else if (Minutes > )
m_iDTTickIntervalMult = ;
else if (Minutes > )
m_iDTTickIntervalMult = ;
else
m_iDTTickIntervalMult = ;
}
else if (Hours < )
{
m_BaseInterval = tiHour;
if (Hours > )
{
m_BaseInterval = tiDay;
m_iDTTickIntervalMult = ;
}
else if (Hours > )
m_iDTTickIntervalMult = ;
else if (Hours > )
m_iDTTickIntervalMult = ;
else
m_iDTTickIntervalMult = ;
}
else if (Days < )
{
m_BaseInterval = tiDay;
if (Days > )
{
m_BaseInterval = tiMonth;
m_iDTTickIntervalMult = ;
}
else if (Days > )
{
m_BaseInterval = tiDay;
m_iDTTickIntervalMult = ;
}
else
m_iDTTickIntervalMult = ;
}
else if (Days < )
{
m_BaseInterval = tiMonth;
if (Days > ) // Approx 6 months
{
m_BaseInterval = tiYear;
m_iDTTickIntervalMult = ;
}
else if (Days > )
m_iDTTickIntervalMult = ;
else if (Days > )
m_iDTTickIntervalMult = ;
else
m_iDTTickIntervalMult = ;
}
else
{
m_BaseInterval = tiYear;
m_iDTTickIntervalMult = (int)Days/ + ;
}
if (m_bAutoTickFormat)
RefreshDTTickFormat();
}
break;
}
}
void CChartAxis::CalculateFirstTick()
{
switch (m_AxisType)
{
case atStandard:
{
m_FirstTickVal = ;
if (m_TickIncrement!=)
{
if (m_MinValue == )
m_FirstTickVal = ;
else if (m_MinValue>)
{
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()+);
break;
case tiMinute:
dtFirstTick.SetDateTime(dtMin.GetYear(),dtMin.GetMonth(),dtMin.GetDay(),
dtMin.GetHour(),dtMin.GetMinute(),);
if (dtMin.GetSecond() != )
dtFirstTick += COleDateTimeSpan(,,,);
break;
case tiHour:
dtFirstTick.SetDateTime(dtMin.GetYear(),dtMin.GetMonth(),dtMin.GetDay(),
dtMin.GetHour(),,);
if ( (dtMin.GetMinute()!=) || (dtMin.GetSecond()!=) )
dtFirstTick += COleDateTimeSpan(,,,);
break;
case tiDay:
dtFirstTick.SetDate(dtMin.GetYear(),dtMin.GetMonth(),dtMin.GetDay());
if ( (dtMin.GetHour()!=) || (dtMin.GetMinute()!=) ||
(dtMin.GetSecond()!=) )
{
dtFirstTick += COleDateTimeSpan(,,,);
}
break;
case tiMonth:
{
dtFirstTick.SetDate(dtMin.GetYear(),dtMin.GetMonth(),);
if ((dtMin.GetDay()!=) || (dtMin.GetHour()!=) ||
(dtMin.GetMinute()!=) || (dtMin.GetSecond()!=) )
{
dtFirstTick = AddMonthToDate(dtFirstTick,);
}
}
break;
case tiYear:
break;
}
m_FirstTickVal = (DATE)dtFirstTick;
}
break;
}
}
double CChartAxis::GetNextTickValue(double Previous)
{
double NewTick = ;
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(,,,m_iDTTickIntervalMult);
dtTick += dtSpan;
break;
case tiMinute:
dtSpan.SetDateTimeSpan(,,m_iDTTickIntervalMult,);
dtTick += dtSpan;
break;
case tiHour:
dtSpan.SetDateTimeSpan(,m_iDTTickIntervalMult,,);
dtTick += dtSpan;
break;
case tiDay:
dtSpan.SetDateTimeSpan(m_iDTTickIntervalMult,,,);
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 = ;
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) )) + ;
int MinChars = abs( (int)log10(fabs(m_MinValue) )) + ;
if (m_MinValue<)
MinChars++;
if (m_MaxValue<)
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 < )
MaxDecCount = -MaxDecCount;
else
MaxDecCount = ;
strBuffMax.Format(_T("%.*f"),MaxDecCount,m_MaxValue);
int MinDecCount = (int)log10(m_MinValue);
if (MinDecCount < )
MinDecCount = -MinDecCount;
else
MinDecCount = ;
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 = ;
if (m_bIsHorizontal)
{
if (!m_bIsSecondary)
Pos = m_ObjectRect.top+;
else
Pos = m_ObjectRect.bottom-;
pDC->MoveTo(m_StartPos,Pos);
pDC->LineTo(m_EndPos + , Pos); pDC->MoveTo(m_EndPos + , Pos - );
pDC->LineTo(m_EndPos + , Pos);
pDC->LineTo(m_EndPos + , Pos + ); pDC->ExtTextOut(m_EndPos + , m_ObjectRect.top + , ETO_CLIPPED|ETO_OPAQUE, NULL, strXAxisBuffer, NULL);

}
else
{
if (!m_bIsSecondary)
Pos = m_ObjectRect.right-;
else
Pos = m_ObjectRect.left+;
pDC->MoveTo(Pos,m_StartPos);
pDC->LineTo(Pos, m_EndPos - ); pDC->MoveTo(Pos - , m_EndPos - );
pDC->LineTo(Pos, m_EndPos - );
pDC->LineTo(Pos - , m_EndPos - );
pDC->ExtTextOut(Pos + , m_EndPos - , ETO_CLIPPED|ETO_OPAQUE, NULL, strYAxisBuffer, NULL);

}

在添加轴单位这块还有另外一种做法,直接使用CChartAxisLabel::SetText函数也可以实现同样的效果,但需要将CChartAxis::DrawLabel函数调整如下:

void CChartAxis::DrawLabel(CDC* pDC)
{
CSize LabelSize = m_pAxisLabel->GetSize(pDC);
int XPos = ;
int YPos = ;
if(m_bIsHorizontal)
{
if(!m_bIsSecondary)
{
YPos = m_AxisRect.top + ;
}
else
{
YPos = m_AxisRect.bottom + ;
}
if(!m_bIsInverted)
{
XPos = m_AxisRect.right - LabelSize.cx/ + ;
}
else
{
XPos = m_AxisRect.left - - LabelSize.cx/;
}
}
else
{
CSize TextSize = GetLargestTick(pDC); if(!m_bIsSecondary)
{
XPos = m_AxisRect.right - - LabelSize.cx;
}
else
{
XPos = m_AxisRect.left + ;
}
YPos = m_AxisRect.top - - LabelSize.cy/ - ;
} m_pAxisLabel->SetPosition(XPos, YPos, pDC);
m_pAxisLabel->Draw(pDC);
}

ChartCtrl源码剖析之——CChartAxis类的更多相关文章

  1. ChartCtrl源码剖析之——CChartObject类

    首先,做一些简单的铺垫,目前针对ChartCtrl源码的剖析只针对V.15版本.名义上说是剖析,倒不如说是记录下自己针对该控件的理解,非常感谢Cedric Moonen大神,一切的功劳与掌声都该赠予给 ...

  2. ChartCtrl源码剖析之——CChartScrollBar类

    CChartScrollBar类用来针对每个轴的数据进行滚动,将那些不在当前区域内的数据通过滚动展示出来. CChartScrollBar类的头文件. #pragma once class CChar ...

  3. ChartCtrl源码剖析之——CChartTitle类

    CChartTitle类顾名思义,该类用来绘制波形控件的标题,它处于该控件的区域,如下图所示: CChartTitle类的头文件. #if !defined(AFX_CHARTTITLE_H__499 ...

  4. ChartCtrl源码剖析之——CChartAxisLabel类

    CChartAxisLabel类用来绘制轴标签,上.下.左.右都可以根据实际需要设置对应的轴标签.它处于该控件的区域,如下图所示: CChartAxisLabel类的头文件. #if !defined ...

  5. ChartCtrl源码剖析之——CChartGrid类

    CChartGrid类用来绘制波形区域中的表格,当绘制波形时波形就显示在这些表格上面.它处于该控件的区域,如下图所示: CChartGrid类的头文件. #if !defined(AFX_CHARTG ...

  6. ChartCtrl源码剖析之——CChartLegend类

    CChartLegend类用来绘制每一个波形的描述信息,它处于该控件的区域,如下图所示: CChartLegend类的头文件. #if !defined(AFX_CHARTLEGEND_H__CD72 ...

  7. PART(Persistent Adaptive Radix Tree)的Java实现源码剖析

    论文地址 Adaptive Radix Tree: https://db.in.tum.de/~leis/papers/ART.pdf Persistent Adaptive Radix Tree: ...

  8. 老李推荐:第6章3节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-命令翻译类

    老李推荐:第6章3节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-命令翻译类   每个来自网络的字串命令都需要进行解析执行,只是有些是在解析的过程中直接执行 ...

  9. WorldWind源码剖析系列:影像存储类ImageStore、Nlt影像存储类NltImageStore和WMS影像存储类WmsImageStore

    影像存储类ImageStore 影像存储类ImageStore提供了计算本地影像路径和远程影像影像URL访问的各种接口,是WmsImageStore类和NltImageStore类的基类.当划分完层次 ...

随机推荐

  1. 路由选择(codevs 1062)

    题目描述 Description 在网络通信中,经常需要求最短路径.但完全用最短路径传输有这样一个问题:如果最终在两个终端节点之间给出的最短路径只有一条.则在该路径中的任一个节点或链路出现故障时,信号 ...

  2. 汕头市赛srm1X T3

    给n<=100000个点的树,每个点有一个01串,长度m<=200,串的可以随时01取反,串的每一位对应权Vi,从根节点到某个节点经过决定哪些串取反后取得的最大价值为某个点的权值,求:在这 ...

  3. BZOJ2038 (莫队)

    BZOJ2038: 小Z的袜子 Problem : N只袜子排成一排,每次询问一个区间内的袜子种随机拿两只袜子颜色相同的概率. Solution : 莫队算法真的是简单易懂又暴力. 莫队算法用来离线处 ...

  4. 前端学习之-- JavaScript

    JavaScript笔记 参考:http://www.cnblogs.com/wupeiqi/articles/5602773.html javaScript是一门独立的语言,游览器都具有js解释器 ...

  5. [WinForm]DataGridView列头右键菜单

    [WinForm]DataGridView列头右键菜单 前言 继续"不误正业" - - #,记录一下.有时候有这样的需求:DataGridView的列头菜单可以选择具体显示哪些列, ...

  6. 关于Chrome谷歌浏览器开发者工具网络Network中返回无数据的问题

    1.如图所示,对于有些js文件,响应中无返回数据,Failed to load response data,当然本来是应该有数据,你用火狐浏览器看,就是有的,或者直接在浏览器地址栏里输入url,也可以 ...

  7. 如何在Win7 x64上的配置32位的PostgreSQL ODBC数据源

    在Win7 x64下安装最新版的PostgreSQL 9.x 后,从其官网下载最新的 ODBC驱动,分为普通的32位和64位版本,正常安装后,从已安装软件列表里可以看到两个版本的驱动都已经正确显示出来 ...

  8. Excel中INDEX函数的使用

    1 函数INDEX( )有两种形式: 数组形式——INDEX(array,row_num,column_num)和 引用形式——INDEX(reference,row_num,column_num,a ...

  9. the attribute buffer size is too small 解决方法

    在进行查询的时候引发The attribute buffer size is too small错误解决 http://bbs.esrichina-bj.cn/esri/viewthread.php? ...

  10. 如何删除Windows 7的保留分区

    Windows 7的保留分区可以删除,但是必须小心.启动到Windows 7,运行具有管理员权限的CMD.exe,然后输入:diskpartsel disk 0list volsel vol 0 (你 ...