zoukankan      html  css  js  c++  java
  • ChartCtrl源码剖析之——CChartAxis类

    CChartAxis类用来绘制波形控件的坐标轴,这个源码相对较复杂,当初阅读的时候耗费了不少精力来理解源码中的一些实现细节。 

    CChartAxis类的头文件。

    #if !defined(AFX_CHARTAXIS_H__063D695C_43CF_4A46_8AA0_C7E00268E0D3__INCLUDED_)
    #define AFX_CHARTAXIS_H__063D695C_43CF_4A46_8AA0_C7E00268E0D3__INCLUDED_
    #if _MSC_VER > 1000
    #pragma once
    #endif // _MSC_VER > 1000
    #include "ChartObject.h"
    #include "ChartScrollBar.h"
    #include "ChartString.h"
    #include <afx.h>
    #include <list>
    class CChartGrid;
    class CChartSerie;
    class CChartAxisLabel;
        
    class CChartAxis : public CChartObject  
    {
        friend CChartCtrl;
        friend CChartGrid;
        friend CChartSerie;
        friend CChartScrollBar;
    public:
        enum AxisType
        {
            atStandard = 0,
            atLogarithmic,
            atDateTime
        };
        void     SetAxisType(AxisType Type);
        AxisType GetAxisType() const         { return m_AxisType; }
        enum TimeInterval
        {
            tiSecond,
            tiMinute,
            tiHour,
            tiDay,
            tiMonth,
            tiYear
        };
        void   SetDateTimeIncrement(bool bAuto, TimeInterval Interval, int Multiplier);
        void   SetTickIncrement(bool bAuto, double Increment);
        double GetTickIncrement() const  { return m_TickIncrement; }
        void SetDateTimeFormat(bool bAutomatic, const TChartString& strFormat);
        int GetPosition();
        void SetInverted(bool bNewValue);
        bool IsInverted() const  { return m_bIsInverted; }
        void SetLogarithmic(bool bNewValue)
        {
            if (bNewValue)
                m_AxisType = atLogarithmic;
            else
                m_AxisType = atStandard;
            RefreshAutoAxis();
        }
        bool IsLogarithmic() const          { return (m_AxisType == atLogarithmic); }
        void SetAutomatic(bool bNewValue);  
        bool IsAutomatic()  const  { return m_bIsAutomatic; }
        void SetMinMax(double Minimum, double Maximum);
        void GetMinMax(double& Minimum, double& Maximum) const
        {
            Minimum = m_MinValue;
            Maximum = m_MaxValue;
        }
        long   ValueToScreen(double Value) const;
        double ScreenToValue(long ScreenVal) const;
      
        void     SetTextColor(COLORREF NewColor);
        COLORREF GetTextColor() const              { return m_TextColor;        }
        void SetFont(int nPointSize, const TChartString& strFaceName);
        CChartAxisLabel* GetLabel() const  { return m_pAxisLabel; }
        CChartGrid*         GetGrid()    const  { return m_pAxisGrid;  }
        CChartAxis(CChartCtrl* pParent,bool bHoriz);
        virtual ~CChartAxis();
        void SetMarginSize(bool bAuto, int iNewSize);
        void SetPanZoomEnabled(bool bEnabled) { m_bZoomEnabled = bEnabled; }
        void SetZoomLimit(double dLimit)   { m_dZoomLimit = dLimit; }
        void EnableScrollBar(bool bEnabled);
        bool ScrollBarEnabled() const  
        {
            if (m_pScrollBar)
                return (m_pScrollBar->GetEnabled());
            else
                return false;
        }
        void SetAutoHideScrollBar(bool bAutoHide);
        bool GetAutoHideScrollBar() const;
    private:
        void CalculateTickIncrement();
        void CalculateFirstTick();
        double GetNextTickValue(double Previous);
        CString GetTickLabel(double Tick);
        CSize GetLargestTick(CDC* pDC);
        void Recalculate();
        COleDateTime AddMonthToDate(const COleDateTime& Date, int iMonthsToAdd);
        void DrawLabel(CDC* pDC);
        void RefreshDTTickFormat();
        void PanAxis(long PanStart, long PanEnd);
        void SetZoomMinMax(double Minimum, double Maximum);
        void UndoZoom();
        void SetDecimals(int NewValue)  { m_DecCount = NewValue; }
        bool IsHorizontal() const  { return m_bIsHorizontal; }
        int  GetAxisLenght() const;
        void SetSecondary(bool bNewVal)  { m_bIsSecondary = bNewVal; }
        bool GetSecondary() const         { return m_bIsSecondary; }
        bool RefreshAutoAxis();
        void GetSeriesMinMax(double& Minimum, double& Maximum);
        void SetAxisSize(CRect ControlRect,CRect MarginRect);
        int ClipMargin(CRect ControlRect,CRect& MarginRect,CDC* pDC);    // Allows to calculate the margin required to displayys ticks and text
        void Draw(CDC* pDC);
        // To register/Unregister series related to this axis
        void RegisterSeries(CChartSerie* pSeries);
        void UnregisterSeries(CChartSerie* pSeries);
        void CreateScrollBar();
        void UpdateScrollBarPos();
        void RefreshScrollBar();
        AxisType m_AxisType;        // Type of the axis (standard, log or date/time)
        bool m_bIsHorizontal;      // Indicates if this is an horizontal or vertical axis
        bool m_bIsInverted;          // Indicates if the axis is inverted
        bool m_bIsAutomatic;      // Indicates if the axis is automatic
        bool m_bIsSecondary;    // If the axis is secondary, it will be positioned to
                                // the right (vertical) or to the top (horizontal)
        double m_MaxValue;        // Maximum value on the axis
        double m_MinValue;        
        double m_UnzoomMin;        // Min and max values of the axis before it has been zoomed
        double m_UnzoomMax;        // (used when we unzoom the chart -> go back to previous state)
        bool   m_bAutoTicks;        // Specify if the tick increment is manual or automatic
        double m_TickIncrement;        // Indicates the space between ticks (in axis value) or for log axis the mult base between two ticks
        double m_FirstTickVal;
        unsigned int m_DecCount;    // Number of decimals to display
        int m_StartPos;                // Start position of the axis
        int m_EndPos;
        int  m_nFontSize;            
        TChartString m_strFontName;
        CChartGrid*            m_pAxisGrid;
        CChartAxisLabel*    m_pAxisLabel;
        typedef std::list<CChartSerie*> SeriesList;
        SeriesList m_pRelatedSeries;        // List containing pointers to series related to this axis
        // The user can specify the size of the margin, instead of
        // having it calculated automatically
        bool m_bAutoMargin;
        int m_iMarginSize;
        COLORREF m_TextColor;
        // Data for the date/time axis type.
        TChartString m_strDTTickFormat;        // Format of the date/time tick labels
        bool m_bAutoTickFormat;
        TimeInterval m_BaseInterval;
        int m_iDTTickIntervalMult;
        bool m_bZoomEnabled;
        double m_dZoomLimit;
        CChartScrollBar* m_pScrollBar;
    };
    #endif // !defined(AFX_CHARTAXIS_H__063D695C_43CF_4A46_8AA0_C7E00268E0D3__INCLUDED_)

    CChartAxis类的源文件。

    #include "stdafx.h"
    #include "ChartAxis.h"
    #include "ChartAxisLabel.h"
    #include "ChartGrid.h"
    #include "ChartCtrl.h"
    #include "Math.h"
    #include <sstream>
    using namespace std;
    #ifdef _DEBUG
    #undef THIS_FILE
    static char THIS_FILE[]=__FILE__;
    #define new DEBUG_NEW
    #endif
    //////////////////////////////////////////////////////////////////////
    // Construction/Destruction
    //////////////////////////////////////////////////////////////////////
    CChartAxis::CChartAxis(CChartCtrl* pParent,bool bHoriz):CChartObject(pParent)
    {
        m_AxisType = atStandard;
        m_bIsHorizontal = bHoriz;
        m_bIsInverted = false;
        m_bIsAutomatic = false;
        m_bIsSecondary = false;
        m_MaxValue = m_UnzoomMax = 10;
        m_MinValue = m_UnzoomMin = 0;
        m_bAutoTicks = true;
        m_TickIncrement = 1;
        m_FirstTickVal = 0;
        m_DecCount = 0;
        m_StartPos = m_EndPos = 0;
        m_nFontSize = 80;
        m_strFontName = _T("Microsoft Sans Serif");
        m_pAxisGrid = new CChartGrid(pParent,bHoriz);
        m_pAxisLabel = new CChartAxisLabel(pParent,bHoriz);
        m_bAutoMargin = true;
        m_iMarginSize = 0;
        m_TextColor = m_ObjectColor;
        m_strDTTickFormat = _T("%d %b");
        m_bAutoTickFormat = true;
        m_BaseInterval = tiDay;
        m_iDTTickIntervalMult = 1;
        m_bZoomEnabled = true;
        m_dZoomLimit = 0.001;
        m_pScrollBar = NULL;
    }
    CChartAxis::~CChartAxis()
    {
        if (m_pAxisGrid)
        {
            delete m_pAxisGrid;
            m_pAxisGrid = NULL;
        }
        if (m_pAxisLabel)
        {
            delete m_pAxisLabel;
            m_pAxisLabel = NULL;
        }
        if (m_pScrollBar)
        {
            delete m_pScrollBar;
            m_pScrollBar = NULL;
        }
    }    
    void CChartAxis::SetAxisType(AxisType Type)  
    {
        m_AxisType = Type;
        m_pParent->RefreshCtrl();
    }
    int CChartAxis::ClipMargin(CRect ControlRect,CRect& MarginRect,CDC* pDC)
    {
        if (!m_bIsVisible)
            return 0;
        int Size = 0;    
        CSize TickSize = GetLargestTick(pDC);
        CSize LabelSize = m_pAxisLabel->GetSize(pDC);
        if (m_bIsHorizontal)
        {
            if (!m_bAutoMargin)
                Size = m_iMarginSize;
            else
            {
                Size += 4 + 2;        //Space above and under the text
                Size += TickSize.cy;
                Size += LabelSize.cy;
                m_iMarginSize = Size;
            }
            if (!m_bIsSecondary)
            {
                ControlRect.bottom -= Size;
                ControlRect.right -= TickSize.cx/2+3;
                if (ControlRect.bottom < MarginRect.bottom)
                    MarginRect.bottom = ControlRect.bottom;
                if (ControlRect.right < MarginRect.right)
                    MarginRect.right = ControlRect.right;
            }
            else
            {
                ControlRect.top += Size;
                ControlRect.right -= TickSize.cx/2+3;
                if (ControlRect.top > MarginRect.top)
                    MarginRect.top = ControlRect.top;
                if (ControlRect.right < MarginRect.right)
                    MarginRect.right = ControlRect.right;
            }
        }
        else
        {
            if (!m_bAutoMargin)
                Size = m_iMarginSize;
            else
            {
                Size += 7 + 1;        //Space before and after the text + Tick
                Size += TickSize.cx;
                Size += LabelSize.cx + 2;
                m_iMarginSize = Size;
            }
            if (!m_bIsSecondary)
            {
                ControlRect.left += Size;
                ControlRect.top += TickSize.cy/2+3;
                if (ControlRect.top > MarginRect.top)
                    MarginRect.top = ControlRect.top;
                if (ControlRect.left > MarginRect.left)
                    MarginRect.left = ControlRect.left;
            }
            else
            {
                ControlRect.right -= Size;
                ControlRect.top += TickSize.cy/2+3;
                if (ControlRect.top > MarginRect.top)
                    MarginRect.top = ControlRect.top;
                if (ControlRect.right < MarginRect.right)
                    MarginRect.right = ControlRect.right;
            }
        }
        return Size;
    }
    int CChartAxis::GetPosition()
    {
        if (m_bIsHorizontal)
        {
            if (m_bIsSecondary)
                return 0;
            else
                return 100;
        }
        else
        {
            if (m_bIsSecondary)
                return 100;
            else
                return 0;
        }
    }
    void CChartAxis::SetAutomatic(bool bNewValue)  
    {
        m_bIsAutomatic = bNewValue;
        if (m_bIsAutomatic)
            m_MinValue = m_MaxValue = 0;
        if (RefreshAutoAxis())
            m_pParent->RefreshCtrl();
    }
    void CChartAxis::SetTickIncrement(bool bAuto, double Increment)
    {
        if (m_AxisType == atDateTime)
            return;
        m_bAutoTicks = bAuto;
        if (!m_bAutoTicks)
            m_TickIncrement = Increment;
        else
            CalculateTickIncrement();
        CalculateFirstTick();
        m_pParent->RefreshCtrl();
    }
    void CChartAxis::SetDateTimeIncrement(bool bAuto, TimeInterval Interval, int Multiplier)
    {
        if (m_AxisType != atDateTime)
            return;
        m_bAutoTicks = bAuto;
        if (!m_bAutoTicks)
        {
            m_BaseInterval = Interval;
            m_iDTTickIntervalMult = Multiplier;
        }
    }
    void CChartAxis::SetDateTimeFormat(bool bAutomatic, const TChartString& strFormat)
    {
        m_bAutoTickFormat = bAutomatic;
        m_strDTTickFormat = strFormat;
        m_pParent->RefreshCtrl();
    }
    void CChartAxis::SetAxisSize(CRect ControlRect,CRect MarginRect)
    {
        if (m_bIsHorizontal)
        {
            m_StartPos = MarginRect.left;    
            m_EndPos = MarginRect.right;
            if (!m_bIsSecondary)
            {
                CRect AxisSize = ControlRect;
                AxisSize.top = MarginRect.bottom;
                SetRect(AxisSize);    
            }
            else
            {
                CRect AxisSize = ControlRect;
                AxisSize.bottom = MarginRect.top;
                SetRect(AxisSize);    
            }
        }
        else
        {
            m_StartPos = MarginRect.bottom;
            m_EndPos = MarginRect.top;
            if (!m_bIsSecondary)
            {
                CRect AxisSize = ControlRect;
                AxisSize.right = MarginRect.left;
                SetRect(AxisSize);
            }
            else
            {
                CRect AxisSize = ControlRect;
                AxisSize.left = MarginRect.right;
                SetRect(AxisSize);
            }
        }
    }
    void CChartAxis::Recalculate()
    {
        CalculateTickIncrement();
        CalculateFirstTick();
    }
    void CChartAxis::Draw(CDC *pDC)
    {
        if (!m_bIsVisible)
            return;
        if (pDC->GetSafeHdc() == NULL)
            return;
        CPen SolidPen(PS_SOLID,0,m_ObjectColor);
        CPen* pOldPen;
        CFont NewFont;
        CFont* pOldFont;
        COLORREF OldTextColor;
        NewFont.CreatePointFont(m_nFontSize,m_strFontName.c_str(),pDC) ;
        pOldPen = pDC->SelectObject(&SolidPen);
        pOldFont = pDC->SelectObject(&NewFont);
        OldTextColor = pDC->SetTextColor(m_TextColor);
        int iPrevMode = pDC->SetBkMode(TRANSPARENT);
        CSize LabelSize = m_pAxisLabel->GetSize(pDC);
        // Draw the axis line
        int Pos = 0;
        if (m_bIsHorizontal)
        {
            if (!m_bIsSecondary)
                Pos = m_ObjectRect.top+1;
            else
                Pos = m_ObjectRect.bottom-1;
            pDC->MoveTo(m_StartPos,Pos);
            pDC->LineTo(m_EndPos,Pos);
        }
        else
        {
            if (!m_bIsSecondary)
                Pos = m_ObjectRect.right-1;
            else
                Pos = m_ObjectRect.left+1;
            pDC->MoveTo(Pos,m_StartPos);
            pDC->LineTo(Pos,m_EndPos);
        }
        // Draw the label
        DrawLabel(pDC);
        m_pAxisGrid->ClearTicks();
        //char szBuffer[255];
        CString strBuffer;
        int TickPos = 0;
        double TickValue = m_FirstTickVal;
        do
        {
            strBuffer = GetTickLabel(TickValue);
            CSize TextSize = pDC->GetTextExtent(strBuffer);
            TickPos = ValueToScreen(TickValue);
            if (m_bIsHorizontal)
            {
                if (!m_bIsSecondary)
                {
                    pDC->MoveTo(TickPos,m_ObjectRect.top+1);
                    pDC->LineTo(TickPos,m_ObjectRect.top+4);
                    pDC->ExtTextOut(TickPos-TextSize.cx/2,m_ObjectRect.top+5,
                                    ETO_CLIPPED|ETO_OPAQUE,NULL,strBuffer,NULL);
                }
                else
                {
                    pDC->MoveTo(TickPos,m_ObjectRect.bottom-1);
                    pDC->LineTo(TickPos,m_ObjectRect.bottom-4);
                    pDC->ExtTextOut(TickPos-TextSize.cx/2,m_ObjectRect.bottom-5-TextSize.cy,
                                    ETO_CLIPPED|ETO_OPAQUE,NULL,strBuffer,NULL);
                }
            }
            else
            {
                if (!m_bIsSecondary)
                {
                    pDC->MoveTo(m_ObjectRect.right-1,TickPos);
                    pDC->LineTo(m_ObjectRect.right-4,TickPos);
                    pDC->ExtTextOut(m_ObjectRect.left+LabelSize.cx+4,TickPos-TextSize.cy/2,
                                    ETO_CLIPPED|ETO_OPAQUE,NULL,strBuffer,NULL);
                }
                else
                {
                    pDC->MoveTo(m_ObjectRect.left+1,TickPos);
                    pDC->LineTo(m_ObjectRect.left+4,TickPos);
                    pDC->ExtTextOut(m_ObjectRect.left+6,TickPos-TextSize.cy/2,
                                    ETO_CLIPPED|ETO_OPAQUE,NULL,strBuffer,NULL);
                }
            }
            m_pAxisGrid->AddTick(TickPos);
            TickValue = GetNextTickValue(TickValue);
        } while ((TickValue < m_MaxValue+0.0000001) && (m_TickIncrement || m_iDTTickIntervalMult) );
        CRect Size = m_pParent->GetPlottingRect();
        m_pAxisGrid->SetRect(Size);
        m_pAxisGrid->Draw(pDC);
        pDC->SelectObject(pOldPen);
        DeleteObject(SolidPen);
        pDC->SelectObject(pOldFont);
        DeleteObject(NewFont);
        pDC->SetTextColor(OldTextColor);
        pDC->SetBkMode(iPrevMode);
    }
    void CChartAxis::DrawLabel(CDC* pDC)
    {
        // Draw the axis label.
        CSize LabelSize = m_pAxisLabel->GetSize(pDC);
        int HalfAxisPos = (int)fabs((m_EndPos + m_StartPos)/2.0);
        int XPos = 0;
        int YPos = 0;
        if (m_bIsHorizontal)
        {
            if (!m_bIsSecondary)
            {
                CString Buffer;
                Buffer.Format(_T("%.*f"),m_DecCount,m_MaxValue);
                CSize TextSize = pDC->GetTextExtent(Buffer);
                
                YPos = m_ObjectRect.top  + TextSize.cy + 2;
                XPos = HalfAxisPos - LabelSize.cx/2;
            }
            else
            {
                YPos = m_ObjectRect.top  + 0;
                XPos = HalfAxisPos - LabelSize.cx/2;
            }
        }
        else
        {
            if (!m_bIsSecondary)
            {
                YPos = HalfAxisPos + LabelSize.cy/2;
                XPos = m_ObjectRect.left + 0;
            }
            else
            {
                YPos = HalfAxisPos + LabelSize.cy/2;
                XPos = m_ObjectRect.right - LabelSize.cx - 2;
            }
        }
        m_pAxisLabel->SetPosition(XPos,YPos,pDC);
        m_pAxisLabel->Draw(pDC);
    }
    void CChartAxis::SetMinMax(double Minimum, double Maximum)
    {
        if (Minimum > Maximum)
        {
            TRACE("Maximum axis value must be > minimum axis value");
            return;
        }
        m_MinValue = m_UnzoomMin = Minimum;
        m_MaxValue = m_UnzoomMax = Maximum;
        RefreshScrollBar();
        m_pParent->RefreshCtrl();
    }
    void CChartAxis::SetInverted(bool bNewValue)
    {
        m_bIsInverted = bNewValue;
        RefreshScrollBar();
        m_pParent->RefreshCtrl();
    }
    int CChartAxis::GetAxisLenght() const
    {
        int Length = (int)fabs( (m_EndPos-m_StartPos) * 1.0);
        return Length;
    }
    long CChartAxis::ValueToScreen(double Value) const
    {
        long Offset = 0;
        if (m_MaxValue==m_MinValue)
        {
            Offset = (int)fabs((m_EndPos-m_StartPos)/2.0);
            if (m_bIsHorizontal)
                return m_StartPos + Offset;
            else
                return m_StartPos - Offset;
        }
        if (m_AxisType != atLogarithmic)
            Offset = (int)floor( (Value - m_MinValue) * GetAxisLenght()/(m_MaxValue-m_MinValue) );
        else
            Offset = (int)floor((log10(Value)-log10(m_MinValue)) * GetAxisLenght()/(log10(m_MaxValue)-log10(m_MinValue)) );
        if (m_bIsHorizontal)
        {
           if (!m_bIsInverted)
              return (m_StartPos + Offset);
           else
               return (m_EndPos - Offset);
         }
         else
         {
            if (!m_bIsInverted)
                return (m_StartPos - Offset);
            else
                return (m_EndPos + Offset);
         }
    }
    double CChartAxis::ScreenToValue(long ScreenVal) const
    {
        if (m_MaxValue==m_MinValue)
            return 0;
        int AxisOffset = 0;
        if (!m_bIsHorizontal)
        {
            if (m_bIsInverted)
                AxisOffset = ScreenVal - m_EndPos;      
            else
                AxisOffset = m_StartPos - ScreenVal;
                
        }
        else
        {
            if (!m_bIsInverted)
               AxisOffset = ScreenVal - m_StartPos;
            else
                AxisOffset = m_EndPos - ScreenVal;
        }
        if (m_AxisType != atLogarithmic)
            return ( (AxisOffset * 1.0 / GetAxisLenght()*(m_MaxValue-m_MinValue)) + m_MinValue);
        else
            return (pow(10.0,(AxisOffset *1.0 / GetAxisLenght()*(log10(m_MaxValue)-log10(m_MinValue)) ) + log10(m_MinValue)) );
    }
       
    void CChartAxis::SetTextColor(COLORREF NewColor)
    {
        m_TextColor = NewColor;
        m_pParent->RefreshCtrl();
    }
    void CChartAxis::SetFont(int nPointSize, const TChartString&  strFaceName)
    {
        m_nFontSize = nPointSize;
        m_strFontName = strFaceName;
        m_pParent->RefreshCtrl();
    }
    void CChartAxis::SetMarginSize(bool bAuto, int iNewSize)
    {
        m_bAutoMargin = bAuto;
        m_iMarginSize = iNewSize;
        m_pParent->RefreshCtrl();
    }
    void CChartAxis::PanAxis(long PanStart, long PanEnd)
    {
        double StartVal = ScreenToValue(PanStart);
        double EndVal = ScreenToValue(PanEnd);
        if (m_AxisType != atLogarithmic)
        {
            double Shift = StartVal - EndVal;
            SetZoomMinMax(m_MinValue+Shift,m_MaxValue+Shift);
        }
        else
        {
            double Factor = StartVal/EndVal;
            SetZoomMinMax(m_MinValue*Factor,m_MaxValue*Factor);
        }
    }
    void CChartAxis::SetZoomMinMax(double Minimum, double Maximum)
    {
        if (!m_bZoomEnabled)
            return;
        if (Minimum > Maximum)
        {
            TRACE("Maximum axis value must be > minimum axis value");
            return;
        }
        m_MinValue = Minimum;
        if ( (Maximum - Minimum) < m_dZoomLimit && m_AxisType!=atLogarithmic)
            m_MaxValue = m_MinValue + m_dZoomLimit;
        else
            m_MaxValue = Maximum;
        RefreshScrollBar();
    }
    void CChartAxis::UndoZoom()
    {
        SetMinMax(m_UnzoomMin,m_UnzoomMax);
    }
    void CChartAxis::RegisterSeries(CChartSerie* pSeries)
    {
        // First check if the series is already present in the list
        SeriesList::iterator iter = m_pRelatedSeries.begin();
        for (iter; iter!=m_pRelatedSeries.end(); iter++)
        {
            if ( (*iter) == pSeries)
                return;
        }
        m_pRelatedSeries.push_back(pSeries);
    }
    void CChartAxis::UnregisterSeries(CChartSerie* pSeries)
    {
        SeriesList::iterator iter = m_pRelatedSeries.begin();
        for (iter; iter!=m_pRelatedSeries.end(); iter++)
        {
            if ( (*iter) == pSeries)
            {
                m_pRelatedSeries.erase(iter);
                return;
            }
        }
    }
    COleDateTime CChartAxis::AddMonthToDate(const COleDateTime& Date, int iMonthsToAdd)
    {
        COleDateTime newDate;
        int nMonths = Date.GetMonth()-1 + iMonthsToAdd;
        int nYear = Date.GetYear() + nMonths/12;
        newDate.SetDateTime(nYear,nMonths%12+1,Date.GetDay(),Date.GetHour(),
            Date.GetMinute(),Date.GetSecond());
        return newDate;
    }
    bool CChartAxis::RefreshAutoAxis()
    {
        RefreshScrollBar();
        bool bNeedRefresh = false;
        if (!m_bIsAutomatic)
            return bNeedRefresh;
        double SeriesMin = 0;
        double SeriesMax = 0;
        GetSeriesMinMax(SeriesMin, SeriesMax);
        if ( (SeriesMax!=m_MaxValue) || (SeriesMin!=m_MinValue) )
            SetMinMax(SeriesMin,SeriesMax);
        return bNeedRefresh;
    }
    void CChartAxis::GetSeriesMinMax(double& Minimum, double& Maximum)
    {
        Minimum = 0;
        Maximum = 0;
        double TempMin = 0;
        double TempMax = 0;
        
        SeriesList::iterator iter = m_pRelatedSeries.begin();
        if (iter != m_pRelatedSeries.end())
        {
            if (m_bIsHorizontal)
                (*iter)->GetSerieXMinMax(Minimum,Maximum);
            else
                (*iter)->GetSerieYMinMax(Minimum,Maximum);
        }
        for (iter; iter!=m_pRelatedSeries.end(); iter++)
        {
            if (m_bIsHorizontal)
                (*iter)->GetSerieXMinMax(TempMin,TempMax);
            else
                (*iter)->GetSerieYMinMax(TempMin,TempMax);
            if (TempMin < Minimum)
                Minimum = TempMin;
            if (TempMax > Maximum)
                Maximum = TempMax;
        }
    }
    void CChartAxis::CalculateTickIncrement()
    {
        if (!m_bAutoTicks)
            return;
        if (m_MaxValue == m_MinValue)
        {
            m_iDTTickIntervalMult = 0;
            m_TickIncrement = 0;
            return;
        }
        int PixelSpace;
        if (m_bIsHorizontal)
        {
            if (m_AxisType == atDateTime)
                PixelSpace = 60;
            else
                PixelSpace = 30;
        }
        else
            PixelSpace = 20;
        int MaxTickNumber = (int)fabs((m_EndPos-m_StartPos)/PixelSpace * 1.0);
        //Calculate the appropriate TickSpace (1 tick every 30 pixel +/-)
        switch (m_AxisType)
        {
        case atLogarithmic:
           m_TickIncrement = 10;
           break;
        case atStandard:
            {
                   //Temporary tick increment
                double TickIncrement = (m_MaxValue-m_MinValue)/MaxTickNumber;
            
                // Calculate appropriate tickSpace (not rounded on 'strange values' but
                // on something like 1, 2 or 5*10^X  where X is optimalized for showing the most
                // significant digits)
                int Zeros = (int)floor(log10(TickIncrement));
                double MinTickIncrement = pow(10.0,Zeros);
            
                int Digits = 0;
                if (Zeros<0)        
                {
                    //We must set decimal places. In the other cases, Digits will be 0.
                    Digits = (int)fabs(Zeros*1.0);
                }
            
                if (MinTickIncrement>=TickIncrement)
                {
                    m_TickIncrement = MinTickIncrement;
                    SetDecimals(Digits);
                }
                else if (MinTickIncrement*2>=TickIncrement)
                {
                    m_TickIncrement = MinTickIncrement*2;
                    SetDecimals(Digits);
                }
                else if (MinTickIncrement*5>=TickIncrement)
                {
                    m_TickIncrement = MinTickIncrement*5;
                    SetDecimals(Digits);
                }
                else if (MinTickIncrement*10>=TickIncrement)
                {
                    m_TickIncrement = MinTickIncrement*10;
                    if (Digits)
                        SetDecimals(Digits-1);
                    else
                        SetDecimals(Digits);
                }
            }
            break;
        case atDateTime:
            {
                COleDateTime StartDate(m_MinValue);
                COleDateTime EndDate(m_MaxValue);
                COleDateTimeSpan minTickInterval = (EndDate - StartDate)/MaxTickNumber;
                double Seconds = minTickInterval.GetTotalSeconds();
                double Minutes = minTickInterval.GetTotalMinutes();
                double Hours = minTickInterval.GetTotalHours();
                double Days = minTickInterval.GetTotalDays();
                if (Seconds < 60)
                {
                    m_BaseInterval = tiSecond;
                    if (Seconds > 30)
                    {
                        m_BaseInterval = tiMinute;
                        m_iDTTickIntervalMult = 1;
                    }
                    else if (Seconds > 10)
                        m_iDTTickIntervalMult = 30;
                    else if (Seconds > 5)
                        m_iDTTickIntervalMult = 10;
                    else if (Seconds > 2)
                        m_iDTTickIntervalMult = 5;
                    else
                        m_iDTTickIntervalMult = 1;
                }
                else if (Minutes < 60)
                {
                    m_BaseInterval = tiMinute;
                    if (Minutes > 30)
                    {
                        m_BaseInterval = tiHour;
                        m_iDTTickIntervalMult = 1;
                    }
                    else if (Minutes > 10)
                        m_iDTTickIntervalMult = 30;
                    else if (Minutes > 5)
                        m_iDTTickIntervalMult = 10;
                    else if (Minutes > 2)
                        m_iDTTickIntervalMult = 5;
                    else
                        m_iDTTickIntervalMult = 2;
                }
                else if (Hours < 24)
                {
                    m_BaseInterval = tiHour;
                    if (Hours > 12)
                    {
                        m_BaseInterval = tiDay;
                        m_iDTTickIntervalMult = 1;
                    }
                    else if (Hours > 6)
                        m_iDTTickIntervalMult = 12;
                    else if (Hours > 2)
                        m_iDTTickIntervalMult = 6;
                    else
                        m_iDTTickIntervalMult = 2;
                }
                else if (Days < 31)
                {
                    m_BaseInterval = tiDay;
                    if (Days > 7)
                    {
                        m_BaseInterval = tiMonth;
                        m_iDTTickIntervalMult = 1;
                    }
                    else if (Days > 2)
                    {
                        m_BaseInterval = tiDay;
                        m_iDTTickIntervalMult = 7;
                    }
                    else
                        m_iDTTickIntervalMult = 2;
                }
                else if (Days < 365)
                {
                    m_BaseInterval = tiMonth;
                    if (Days > 186)     // Approx 6 months
                    {
                        m_BaseInterval = tiYear;
                        m_iDTTickIntervalMult = 1;
                    }
                    else if (Days > 124)
                        m_iDTTickIntervalMult = 6;
                    else if (Days > 62)
                        m_iDTTickIntervalMult = 4;
                    else
                        m_iDTTickIntervalMult = 2;
                }
                else
                {
                    m_BaseInterval = tiYear;
                    m_iDTTickIntervalMult = (int)Days/365 + 1;
                }
                if (m_bAutoTickFormat)
                    RefreshDTTickFormat();
            }
            break;
        }
    }
    void CChartAxis::CalculateFirstTick()
    {
        switch (m_AxisType)
        {
        case atStandard:
            {
                m_FirstTickVal = 0;
                if (m_TickIncrement!=0)
                {
                    if (m_MinValue == 0)
                        m_FirstTickVal = 0;
                    else if (m_MinValue>0)
                    {
                        m_FirstTickVal = (int)(m_MinValue/m_TickIncrement) * m_TickIncrement;
                        while (m_FirstTickVal<m_MinValue)
                            m_FirstTickVal += m_TickIncrement;
                    }
                    else
                    {
                        m_FirstTickVal = (int)(m_MinValue/m_TickIncrement) * m_TickIncrement;
                        while (m_FirstTickVal>m_MinValue)
                            m_FirstTickVal -= m_TickIncrement;
                        if (!(m_FirstTickVal == m_MinValue))
                            m_FirstTickVal += m_TickIncrement;
                    }
                }
                else    // m_TickIncrement!=0
                {
                    m_FirstTickVal = m_MinValue;
                }
            }
            break;
        case atLogarithmic:
            {
                int LogBase = (int)log10(m_MinValue);
                m_FirstTickVal = pow(10.0,LogBase);
            }
            break;
        case atDateTime:
            {
                COleDateTime dtMin((DATE)m_MinValue);
                COleDateTime dtFirstTick(dtMin);
                switch (m_BaseInterval)
                {
                case tiSecond:
                    dtFirstTick.SetDateTime(dtMin.GetYear(),dtMin.GetMonth(),dtMin.GetDay(),
                                            dtMin.GetHour(),dtMin.GetMinute(),dtMin.GetSecond()+1);
                    break;
                case tiMinute:
                    dtFirstTick.SetDateTime(dtMin.GetYear(),dtMin.GetMonth(),dtMin.GetDay(),
                                            dtMin.GetHour(),dtMin.GetMinute(),0);
                    if (dtMin.GetSecond() != 0)
                        dtFirstTick += COleDateTimeSpan(0,0,1,0);                    
                    break;
                case tiHour:
                    dtFirstTick.SetDateTime(dtMin.GetYear(),dtMin.GetMonth(),dtMin.GetDay(),
                                            dtMin.GetHour(),0,0);
                    if ( (dtMin.GetMinute()!=0) || (dtMin.GetSecond()!=0) )
                        dtFirstTick += COleDateTimeSpan(0,1,0,0);                    
                    break;
                case tiDay:
                    dtFirstTick.SetDate(dtMin.GetYear(),dtMin.GetMonth(),dtMin.GetDay());
                    if ( (dtMin.GetHour()!=0) || (dtMin.GetMinute()!=0) ||
                         (dtMin.GetSecond()!=0) )
                    {
                        dtFirstTick += COleDateTimeSpan(1,0,0,0);
                    }
                    break;
                case tiMonth:
                    {
                        dtFirstTick.SetDate(dtMin.GetYear(),dtMin.GetMonth(),1);
                        if ((dtMin.GetDay()!=1) || (dtMin.GetHour()!=0) ||
                            (dtMin.GetMinute()!=0) || (dtMin.GetSecond()!=0) )
                        {
                            dtFirstTick = AddMonthToDate(dtFirstTick,1);
                        }
                    }
                    break;
                case tiYear:
                    break;
                }
                m_FirstTickVal = (DATE)dtFirstTick;
            }
            break;
        }
    }
    double CChartAxis::GetNextTickValue(double Previous)
    {
        double NewTick = 0;
        switch (m_AxisType)
        {
        case atStandard:
            NewTick = Previous + m_TickIncrement;
            break;
        case atLogarithmic:
            NewTick = Previous * m_TickIncrement;
            break;
        case atDateTime:
            {
                COleDateTime dtTick((DATE)Previous);
                COleDateTimeSpan dtSpan;
                switch (m_BaseInterval)
                {
                case tiSecond:
                    dtSpan.SetDateTimeSpan(0,0,0,m_iDTTickIntervalMult);
                    dtTick += dtSpan;
                    break;
                case tiMinute:
                    dtSpan.SetDateTimeSpan(0,0,m_iDTTickIntervalMult,0);
                    dtTick += dtSpan;
                    break;
                case tiHour:
                    dtSpan.SetDateTimeSpan(0,m_iDTTickIntervalMult,0,0);
                    dtTick += dtSpan;
                    break;
                case tiDay:
                    dtSpan.SetDateTimeSpan(m_iDTTickIntervalMult,0,0,0);
                    dtTick += dtSpan;
                    break;
                case tiMonth:
                    {
                        dtTick = AddMonthToDate(dtTick,m_iDTTickIntervalMult);
                    }
                    break;
                case tiYear:
                    break;
                }
                NewTick = (DATE)dtTick;
            }
            break;
        }
        return NewTick;
    }
    CString CChartAxis::GetTickLabel(double TickValue)
    {
        CString strLabel;
        switch (m_AxisType)
        {
        case atStandard:
            strLabel.Format(_T("%.*f"),m_DecCount,TickValue);
        //    ssLabel << setprecision(m_DecCount) << TickValue;
        //    sprintf(szBuffer,"%.*f",m_DecCount,TickValue);
            break;
        case atLogarithmic:
            {
                double fLogDecCount;
                int    nLogDecCount;
                fLogDecCount = log10(TickValue);
                if (fLogDecCount < 0.0)
                    nLogDecCount = (int)(fabs(fLogDecCount) + 0.1);
                else
                    nLogDecCount = 0;
                strLabel.Format(_T("%.*f"), nLogDecCount, TickValue);
            }
            break;
        case atDateTime:
            {
                COleDateTime tickTime((DATE)TickValue);
                strLabel = tickTime.Format(m_strDTTickFormat.c_str());
            //    ssLabel << tickTime.Format(m_strDTTickFormat.c_str());
            //    strcpy(szBuffer,strLabel);
            }
            break;
        }
        return strLabel;
    }
    void CChartAxis::RefreshDTTickFormat()
    {
        switch (m_BaseInterval)
        {
        case tiSecond:
            m_strDTTickFormat = _T("%H:%M:%S");
            break;
        case tiMinute:
            m_strDTTickFormat = _T("%H:%M");
            break;
        case tiHour:
            m_strDTTickFormat = _T("%H:00");
            break;
        case tiDay:
            m_strDTTickFormat = _T("%d %b");
            break;
        case tiMonth:
            m_strDTTickFormat = _T("%b %Y");
            break;
        case tiYear:
            m_strDTTickFormat = _T("%Y");
            break;
        }
    }
    CSize CChartAxis::GetLargestTick(CDC* pDC)
    {
        CSize TickSize;
        CFont* pOldFont;
        CFont NewFont;
        NewFont.CreatePointFont(m_nFontSize,m_strFontName.c_str(),pDC);
        pOldFont = pDC->SelectObject(&NewFont);
        CString strBuffer;
        strBuffer.Format(_T("%.*f"),m_DecCount,m_MaxValue);
        if (m_bIsHorizontal)
            TickSize = pDC->GetTextExtent(strBuffer);
        else
        {
            switch (m_AxisType)
            {
            case atStandard:
                {
                    int MaxChars = abs( (int)log10(fabs(m_MaxValue) )) + 1;
                    int MinChars = abs( (int)log10(fabs(m_MinValue) )) + 1;
                    if (m_MinValue<0)
                        MinChars++;
                    if (m_MaxValue<0)
                        MaxChars++;
                    if (MaxChars>MinChars)
                        strBuffer.Format(_T("%.*f"),m_DecCount,m_MaxValue);
                    else
                        strBuffer.Format(_T("%.*f"),m_DecCount,m_MinValue);
                }
                break;
            case atLogarithmic:
                {
                    CString strBuffMax;
                    CString strBuffMin;
                    int MaxDecCount = (int)log10(m_MaxValue);
                    if (MaxDecCount < 0)
                        MaxDecCount = -MaxDecCount;
                    else
                        MaxDecCount = 0;
                    strBuffMax.Format(_T("%.*f"),MaxDecCount,m_MaxValue);
                    int MinDecCount = (int)log10(m_MinValue);
                    if (MinDecCount < 0)
                        MinDecCount = -MinDecCount;
                    else
                        MinDecCount = 0;
                    strBuffMin.Format(_T("%.*f"),MinDecCount,m_MinValue);
                    if (strBuffMin.GetLength() > strBuffMax.GetLength() )
                        strBuffer = strBuffMin;
                    else
                        strBuffer = strBuffMax;
                }            
                break;
            case atDateTime:
                {
                    double TickValue = m_FirstTickVal;
                    CString strTemp;
                    do
                    {
                        strTemp = GetTickLabel(TickValue);
                        if (strTemp.GetLength() > strBuffer.GetLength() )
                            strBuffer = strTemp;
                        TickValue = GetNextTickValue(TickValue);
                    } while ((TickValue < m_MaxValue+0.0000001) && (m_TickIncrement|| m_iDTTickIntervalMult) );
                }
                break;
            }
            TickSize = pDC->GetTextExtent(strBuffer);
        }
        pDC->SelectObject(pOldFont);
        DeleteObject(NewFont);
        return TickSize;
    }
    void CChartAxis::EnableScrollBar(bool bEnabled)
    {
        if (m_pScrollBar)
        {
            m_pScrollBar->SetEnabled(bEnabled);
            if (bEnabled)
                m_pScrollBar->ShowWindow(SW_SHOW);
            else
                m_pScrollBar->ShowWindow(SW_HIDE);
        }
    }
    void CChartAxis::SetAutoHideScrollBar(bool bAutoHide)
    {
        if (m_pScrollBar)
            m_pScrollBar->SetAutoHide(bAutoHide);
    }
    bool CChartAxis::GetAutoHideScrollBar() const
    {
        if (m_pScrollBar)
            return (m_pScrollBar->GetAutoHide());
        else
            return false;
    }
    void CChartAxis::CreateScrollBar()
    {
        m_pScrollBar = new CChartScrollBar(this);
        m_pScrollBar->CreateScrollBar(m_pParent->GetPlottingRect());
    }
    void CChartAxis::UpdateScrollBarPos()
    {
        CRect PlottingRect = m_pParent->GetPlottingRect();
        PlottingRect.top++; PlottingRect.left++;
        CRect Temp;
        m_pScrollBar->GetWindowRect(&Temp);
        if (m_bIsHorizontal && !m_bIsSecondary)
            PlottingRect.top = PlottingRect.bottom - Temp.Height();
        if (!m_bIsHorizontal && !m_bIsSecondary)
            PlottingRect.right = PlottingRect.left + Temp.Width();
        if (m_bIsHorizontal && m_bIsSecondary)
            PlottingRect.bottom = PlottingRect.top + Temp.Height();
        if (!m_bIsHorizontal && m_bIsSecondary)
            PlottingRect.left = PlottingRect.right - Temp.Width();
        m_pScrollBar->MoveWindow(&PlottingRect);
    }
    void CChartAxis::RefreshScrollBar()
    {
        if (m_pScrollBar)
            m_pScrollBar->Refresh();
    }

    这份源码大概读了有至少四遍,总算把这份源码吃透。回过头来看当初觉得不理解的地方,感觉一开始的心态不正确,没下定决心把它搞定,只走马观花的读肯定行不通。经过反复的阅读,之前一些不理解的地方都慢慢的消化了。一开始不理解的函数有ClipMargin、CalculateTickIncrement、CalculateFirstTick、ValueToScreen、ScreenToValue函数看不懂,ClipMargin函数用来设置轴与控件边缘的间距,CalculateTickIncremen函数用来计算标记间的增量,CalculateFirstTick函数用来计算第一个标记的值,ValueToScreen函数用来将值转化为屏幕中的值,ScreenToValue函数用来将屏幕中的值转化为具体的数值。在读自绘控件相关的源码时,一定要先建立所绘制模块在屏幕中的具体位置这样一个体系,这样在读源码的时候才会有的放矢。

    假如想给横轴、纵轴加箭头和单位,需要在CChartAxis::Draw函数里面进行。调整后代码如下:

        CString strXAxisBuffer("ms"), strYAxisBuffer("Y");
        // Draw the axis line
        int Pos = 0;
        if (m_bIsHorizontal)
        {
            if (!m_bIsSecondary)
                Pos = m_ObjectRect.top+1;
            else
                Pos = m_ObjectRect.bottom-1;
            pDC->MoveTo(m_StartPos,Pos);
            pDC->LineTo(m_EndPos + 25, Pos);
    
            pDC->MoveTo(m_EndPos + 15, Pos - 5);
            pDC->LineTo(m_EndPos + 25, Pos);
            pDC->LineTo(m_EndPos + 15, Pos + 5);
    
            pDC->ExtTextOut(m_EndPos + 15, m_ObjectRect.top + 5, ETO_CLIPPED|ETO_OPAQUE, NULL, strXAxisBuffer, NULL);
        }
        else
        {
            if (!m_bIsSecondary)
                Pos = m_ObjectRect.right-1;
            else
                Pos = m_ObjectRect.left+1;
            pDC->MoveTo(Pos,m_StartPos);
            pDC->LineTo(Pos, m_EndPos - 25);
    
            pDC->MoveTo(Pos - 5, m_EndPos - 15);
            pDC->LineTo(Pos, m_EndPos - 25);
            pDC->LineTo(Pos - 5, m_EndPos - 15);
            pDC->ExtTextOut(Pos + 15, m_EndPos - 20, ETO_CLIPPED|ETO_OPAQUE, NULL, strYAxisBuffer, NULL);
        }

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

    void CChartAxis::DrawLabel(CDC* pDC)
    {
        CSize LabelSize = m_pAxisLabel->GetSize(pDC);
        int XPos = 0;
        int YPos = 0;
        if(m_bIsHorizontal)
        {
            if(!m_bIsSecondary)
            {
                YPos = m_AxisRect.top + 0;
            }
            else
            {
                YPos = m_AxisRect.bottom + 0;
            }
            if(!m_bIsInverted)
            {
                XPos = m_AxisRect.right - LabelSize.cx/2 + 10;
            }
            else
            {
                XPos = m_AxisRect.left - 2 - LabelSize.cx/2;
            }
        }
        else
        {
            CSize TextSize = GetLargestTick(pDC);
    
            if(!m_bIsSecondary)
            {
                XPos = m_AxisRect.right - 5 - LabelSize.cx;
            }
            else
            {
                XPos = m_AxisRect.left + 5;
            }
            YPos = m_AxisRect.top - 2 - LabelSize.cy/2 - 10;
        }
    
        m_pAxisLabel->SetPosition(XPos, YPos, pDC);
        m_pAxisLabel->Draw(pDC);
    }
    作者:常想一二
    出处:http://www.cnblogs.com/wolfmvp/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
    如果文中有什么错误,欢迎指出。以免更多的人被误导。
  • 相关阅读:
    内置函数详解
    lambda函数
    第八章(5)
    第八章(4)
    第八章(3)
    第八章(2)
    第八章(1)
    第七章(3)
    第七章(2)
    第七章(1)
  • 原文地址:https://www.cnblogs.com/wolfmvp/p/7212093.html
Copyright © 2011-2022 走看看