vc 画图(饼图)收集

    技术2022-05-20  31

    // MyGraph.h

    #if !defined(MYGRAPHH__9DB68B4D_3C7C_47E2_9F72_EEDA5D2CDBB0__INCLUDED_)#define MYGRAPHH__9DB68B4D_3C7C_47E2_9F72_EEDA5D2CDBB0__INCLUDED_#pragma once

    /// MyGraphSeries

    class MyGraphSeries : public CObject{ friend class MyGraph;

    // Construction.public: MyGraphSeries(const CString& sLabel = ""); virtual ~MyGraphSeries();

    // Declared but not defined.private: MyGraphSeries(const MyGraphSeries& rhs); MyGraphSeries& operator=(const MyGraphSeries& rhs);

    // Operations.public: void  SetLabel(const CString& sLabel); CString GetLabel() const; void  SetData(int nGroup, int nValue); int  GetData(int nGroup) const;

    // Implementation.private: int  GetMaxDataValue() const; int  GetNonZeroElementCount() const; int  GetDataTotal() const; void  SetTipRegion(int nGroup, const CRect& rc); void  SetTipRegion(int nGroup, const CRgn* prgn); int  HitTest(const CPoint& pt) const; CString GetTipText(int nGroup) const;

    // Data.private: CString   m_sLabel;          // Series label. CDWordArray  m_dwaValues;         // Values array. CObArray   m_oaRegions;         // Tooltip regions.};

    /// MyGraph

    class MyGraph : public CStatic{// Enum.public: enum GraphType  { Bar, Line, Pie };

    // Construction.public: MyGraph(GraphType eGraphType = MyGraph::Pie); virtual ~MyGraph();

    // Declared but not defined.private: MyGraph(const MyGraph& rhs); MyGraph& operator=(const MyGraph& rhs);

    // Operations.public: void  AddSeries(MyGraphSeries& rMyGraphSeries); void  SetXAxisLabel(const CString& sLabel); void  SetYAxisLabel(const CString& sLabel); int  AppendGroup(const CString& sLabel); void  SetLegend(int nGroup, const CString& sLabel); void  SetGraphType(GraphType eType); void  SetGraphTitle(const CString& sTitle); int  LookupLabel(const CString& sLabel) const;

    // Implementation.private: void  DrawGraph(CDC& dc); void  DrawTitle(CDC& dc); void  SetupAxes(CDC& dc); void  DrawAxes(CDC& dc) const; void  DrawLegend(CDC& dc); void  DrawSeriesBar(CDC& dc) const; void  DrawSeriesLine(CDC& dc) const; void  DrawSeriesPie(CDC& dc) const;

     int  GetMaxLegendLabelLength(CDC& dc) const; int  GetMaxSeriesSize() const; int  GetMaxNonZeroSeriesSize() const; int  GetMaxDataValue() const; int  GetNonZeroSeriesCount() const;

     CString GetTipText() const;

     int  OnToolHitTest(CPoint point, TOOLINFO* pTI) const;

     CPoint WedgeEndFromDegrees(int nDegrees, const CPoint& ptCenter,     int nRadius) const;

     static UINT   SpinTheMessageLoop(bool bNoDrawing = false,        bool bOnlyDrawing = false,        UINT uiMsgAllowed = WM_NULL);

     static void   RGBtoHLS(COLORREF crRGB, WORD& wH, WORD& wL, WORD& wS); static COLORREF HLStoRGB(WORD wH, WORD wL, WORD wS); static WORD   HueToRGB(WORD w1, WORD w2, WORD wH);

     // Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(MyGraph) protected: virtual void PreSubclassWindow(); //}}AFX_VIRTUAL

    // Generated message map functionsprotected: //{{AFX_MSG(MyGraph) afx_msg void OnPaint(); afx_msg void OnSize(UINT nType, int cx, int cy); //}}AFX_MSG afx_msg bool OnNeedText(UINT uiId, NMHDR* pNMHDR, LRESULT* pResult); DECLARE_MESSAGE_MAP()

    // Data.private: int    m_nXAxisWidth; int    m_nYAxisHeight; CPoint   m_ptOrigin; CRect    m_rcGraph; CRect    m_rcLegend; CRect    m_rcTitle; CString   m_sXAxisLabel; CString   m_sYAxisLabel; CString   m_sTitle; CDWordArray  m_dwaColors; CStringArray m_saLegendLabels; CObList   m_olMyGraphSeries; GraphType  m_eGraphType;};

    //{{AFX_INSERT_LOCATION}}// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

    #endif // !defined(MYGRAPHH__9DB68B4D_3C7C_47E2_9F72_EEDA5D2CDBB0__INCLUDED_)

     

     

     

     

     

     

     

     

    // MyGraph.cpp

    #include "stdafx.h"#include "MyGraph.h"

    #include "math.h"

    #ifdef _DEBUG#define new DEBUG_NEW#undef THIS_FILEstatic char THIS_FILE[] = __FILE__;#endif

    /// This macro can be called at the beginning and ending of every// method.  It is identical to saying "ASSERT_VALID(); ASSERT_KINDOF();"// but is written like this so that VALIDATE can be a macro.  It is useful// as an "early warning" that something has gone wrong with "this" object.#ifndef VALIDATE #ifdef _DEBUG  #define VALIDATE  ::AfxAssertValidObject(this, __FILE__ , __LINE__ ); /           _ASSERTE(IsKindOf(GetRuntimeClass())); #else  #define VALIDATE #endif#endif

    /// Constants.

    #define TICK_PIXELS         4   // Size of tick marks.#define GAP_PIXELS         6   // Better if an even value.#define LEGEND_COLOR_BAR_WIDTH_PIXELS  50   // Width of color bar.#define LEGEND_COLOR_BAR_GAP_PIXELS    1   // Space between color bars.#define Y_AXIS_MAX_TICK_COUNT      5   // How many ticks on y axis.

    #define INTERSERIES_PERCENT_USED           0.85  // How much of the graph is                  // used for bars/pies (the                  // rest is for inter-series                 // spacing).

    #define TITLE_DIVISOR        5   // Scale font to graph width.#define LEGEND_DIVISOR        8   // Scale font to graph width.#define X_AXIS_LABEL_DIVISOR     10   // Scale font to graph width.#define Y_AXIS_LABEL_DIVISOR      6   // Scale font to graph width.

    #define PI            3.1415926535897932384626433832795

    /// MyGraphSeries

    // Constructor.MyGraphSeries::MyGraphSeries(const CString& sLabel /* = "" */ ) : m_sLabel(sLabel){}

    // Destructor./* virtual */ MyGraphSeries::~MyGraphSeries(){ for (int nGroup = 0; nGroup < m_oaRegions.GetSize(); ++nGroup) {  delete (CRgn*) m_oaRegions.GetAt(nGroup); }}

    //void MyGraphSeries::SetLabel(const CString& sLabel){ VALIDATE; _ASSERTE(! sLabel.IsEmpty()); _ASSERTE(m_dwaValues.GetSize() == m_oaRegions.GetSize());

     m_sLabel = sLabel;}

    //void MyGraphSeries::SetData(int nGroup, int nValue){ VALIDATE; _ASSERTE(0 <= nGroup);

     m_dwaValues.SetAtGrow(nGroup, nValue);}

    //void MyGraphSeries::SetTipRegion(int nGroup, const CRect& rc){ VALIDATE;  CRgn* prgnNew = new CRgn; ASSERT_VALID(prgnNew);

     VERIFY(prgnNew->CreateRectRgnIndirect(rc)); SetTipRegion(nGroup, prgnNew);}

    //void MyGraphSeries::SetTipRegion(int nGroup, const CRgn* prgn){ VALIDATE; _ASSERTE(0 <= nGroup); ASSERT_VALID(prgn);

     // If there is an existing resgion, delete it. CRgn* prgnOld = NULL;

     if (nGroup < m_oaRegions.GetSize()) {  prgnOld = static_cast<CRgn*> (m_oaRegions.GetAt(nGroup));  ASSERT_NULL_OR_POINTER(prgnOld, CRgn); }

     if (prgnOld) {  delete prgnOld;  prgnOld = NULL; }

     // Add the new region. m_oaRegions.SetAtGrow(nGroup, (CObject*) prgn);

     _ASSERTE(m_oaRegions.GetSize() <= m_dwaValues.GetSize());}

    //CString MyGraphSeries::GetLabel() const{ VALIDATE;

     return m_sLabel;}

    //int MyGraphSeries::GetData(int nGroup) const{ VALIDATE; _ASSERTE(0 <= nGroup); _ASSERTE(m_dwaValues.GetSize() > nGroup);

     return m_dwaValues[nGroup];}

    // Returns the largest data value in this series.int MyGraphSeries::GetMaxDataValue() const{ VALIDATE;

     int nMax(0);

     for (int nGroup = 0; nGroup < m_dwaValues.GetSize(); ++nGroup) {  nMax = max(nMax, static_cast<int> (m_dwaValues.GetAt(nGroup))); }

     return nMax;}

    // Returns the number of data points that are not zero.int MyGraphSeries::GetNonZeroElementCount() const{ VALIDATE;

     int nCount(0);

     for (int nGroup = 0; nGroup < m_dwaValues.GetSize(); ++nGroup) {    if (m_dwaValues.GetAt(nGroup)) {   ++nCount;  } }

     return nCount;}

    // Returns the sum of the data points for this series.int MyGraphSeries::GetDataTotal() const{ VALIDATE;

     int nTotal(0);

     for (int nGroup = 0; nGroup < m_dwaValues.GetSize(); ++nGroup) {  nTotal += m_dwaValues.GetAt(nGroup); }

     return nTotal;}

    // Returns which group (if any) the sent point lies within in this series.int MyGraphSeries::HitTest(const CPoint& pt) const{ VALIDATE;

     for (int nGroup = 0; nGroup < m_oaRegions.GetSize(); ++nGroup) {  CRgn* prgnData = static_cast<CRgn*> (m_oaRegions.GetAt(nGroup));  ASSERT_NULL_OR_POINTER(prgnData, CRgn);

      if (prgnData  &&  prgnData->PtInRegion(pt)) {   return nGroup;  } }

     return -1;}

    // Get the series portion of the tip for this group in this series.CString MyGraphSeries::GetTipText(int nGroup) const{ VALIDATE; _ASSERTE(0 <= nGroup); _ASSERTE(m_oaRegions.GetSize() <= m_dwaValues.GetSize());  CString sTip;

     sTip.Format("%d (%d%%)", m_dwaValues.GetAt(nGroup),   (int) (100.0 * (double) m_dwaValues.GetAt(nGroup) /   (double) GetDataTotal()));

     return sTip;}

    /// MyGraph

    // Constructor.MyGraph::MyGraph(GraphType eGraphType /* = MyGraph::Pie */ ) : m_nXAxisWidth(0) , m_nYAxisHeight(0) , m_eGraphType(eGraphType){ m_ptOrigin.x = m_ptOrigin.y = 0; m_rcGraph.SetRectEmpty(); m_rcLegend.SetRectEmpty(); m_rcTitle.SetRectEmpty();}

    // Destructor./* virtual */ MyGraph::~MyGraph(){}

    BEGIN_MESSAGE_MAP(MyGraph, CStatic) //{{AFX_MSG_MAP(MyGraph) ON_WM_PAINT() ON_WM_SIZE() //}}AFX_MSG_MAP ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnNeedText) ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnNeedText)END_MESSAGE_MAP()

    // Called by the framework to allow other necessary subclassing to occur // before the window is subclassed.void MyGraph::PreSubclassWindow() { VALIDATE;

     CStatic::PreSubclassWindow();

     VERIFY(EnableToolTips(true));}

    /// MyGraph message handlers

    // Handle the tooltip messages.  Returns true to mean message was handled.bool MyGraph::OnNeedText(UINT uiId, NMHDR* pNMHDR, LRESULT* pResult) { _ASSERTE(pNMHDR  &&  "Bad parameter passed"); _ASSERTE(pResult  &&  "Bad parameter passed");

     bool bReturn(false); UINT uiID(pNMHDR->idFrom);

     // Notification in NT from automatically created tooltip. if (0U != uiID) {  bReturn = true;

      // Need to handle both ANSI and UNICODE versions of the message.  TOOLTIPTEXTA* pTTTA = reinterpret_cast<TOOLTIPTEXTA*> (pNMHDR);  ASSERT_POINTER(pTTTA, TOOLTIPTEXTA);

      TOOLTIPTEXTW* pTTTW = reinterpret_cast<TOOLTIPTEXTW*> (pNMHDR);  ASSERT_POINTER(pTTTW, TOOLTIPTEXTW);

       #ifndef _UNICODE   CString sTipText(GetTipText());

      if (TTN_NEEDTEXTA == pNMHDR->code) {   lstrcpyn(pTTTA->szText, sTipText, sizeof(pTTTA->szText));  }  else {   _mbstowcsz(pTTTW->szText, sTipText, sizeof(pTTTA->szText));  }  #else  if (pNMHDR->code == TTN_NEEDTEXTA) {   _wcstombsz(pTTTA->szText, sTipText, sizeof(pTTTA->szText));  }  else {   lstrcpyn(pTTTW->szText, sTipText, sizeof(pTTTA->szText));  }  #endif   *pResult = 0; }

     return bReturn;}

    // The framework calls this member function to detemine whether a point is in// the bounding rectangle of the specified tool.int MyGraph::OnToolHitTest(CPoint point, TOOLINFO* pTI) const{ _ASSERTE(pTI  &&  "Bad parameter passed");

     // This works around the problem of the tip remaining visible when you move  // the mouse to various positions over this control. int nReturn(0); static bTipPopped(false); static CPoint ptPrev(-1,-1);

     if (point != ptPrev) {  ptPrev = point;

      if (bTipPopped) {   bTipPopped = false;   nReturn = -1;  }  else {   ::Sleep(50);   bTipPopped = true;

       pTI->hwnd = m_hWnd;   pTI->uId = (UINT) m_hWnd;   pTI->lpszText = LPSTR_TEXTCALLBACK;

       CRect rcWnd;   GetClientRect(&rcWnd);   pTI->rect = rcWnd;   nReturn = 1;  } } else {  nReturn = 1; }

     MyGraph::SpinTheMessageLoop();

     return nReturn;}

    // Build the tip text for the part of the graph that the mouse is currently// over.CString MyGraph::GetTipText() const{ VALIDATE;

     CString sTip;

     // Get the position of the mouse. CPoint pt; VERIFY(::GetCursorPos(&pt)); ScreenToClient(&pt);

     // Ask each part of the graph to check and see if the mouse is over it. if (m_rcLegend.PtInRect(pt)) {  sTip = "Legend"; } else if (m_rcTitle.PtInRect(pt)) {  sTip = "Title"; } else {  POSITION pos(m_olMyGraphSeries.GetHeadPosition());

      while (pos) {   MyGraphSeries* pSeries =     static_cast<MyGraphSeries*> (m_olMyGraphSeries.GetNext(pos));   ASSERT_VALID(pSeries);

       int nGroup(pSeries->HitTest(pt));

       if (-1 != nGroup) {    sTip = m_saLegendLabels.GetAt(nGroup) + ": ";    sTip += pSeries->GetTipText(nGroup);    break;   }  } }

     return sTip;} // Handle WM_PAINT.void MyGraph::OnPaint() { VALIDATE;

     CPaintDC dc(this); DrawGraph(dc);}

    // Handle WM_SIZE.void MyGraph::OnSize(UINT nType, int cx, int cy) { VALIDATE;

     CStatic::OnSize(nType, cx, cy);  Invalidate(); }

    // Change the type of the graph; the caller should call Invalidate() on this// window to make the effect of this change visible.void MyGraph::SetGraphType(GraphType e){ VALIDATE;

     m_eGraphType = e;}

    // Calculate the current max legend label length in pixels.int MyGraph::GetMaxLegendLabelLength(CDC& dc) const{ VALIDATE; ASSERT_VALID(&dc);

     CString sMax; int nMaxChars(-1); CSize siz(-1,-1);

     // First get max number of characters. for (int nGroup = 0; nGroup < m_saLegendLabels.GetSize(); ++nGroup) {  int nLabelLength(m_saLegendLabels.GetAt(nGroup).GetLength());

      if (nMaxChars < nLabelLength) {   nMaxChars = nLabelLength;   sMax = m_saLegendLabels.GetAt(nGroup);  } }

     // Now calculate the pixels. siz = dc.GetTextExtent(sMax);

     _ASSERTE(-1 < siz.cx);

     return siz.cx;}

    // Returns the largest number of data points in any series.int MyGraph::GetMaxSeriesSize() const{ VALIDATE;

     int nMax(0); POSITION pos(m_olMyGraphSeries.GetHeadPosition());

     while (pos) {  MyGraphSeries* pSeries =    static_cast<MyGraphSeries*> (m_olMyGraphSeries.GetNext(pos));  ASSERT_VALID(pSeries);

      nMax = max(nMax, pSeries->m_dwaValues.GetSize()); }

     return nMax;}

    // Returns the largest number of non-zero data points in any series.int MyGraph::GetMaxNonZeroSeriesSize() const{ VALIDATE;

     int nMax(0); POSITION pos(m_olMyGraphSeries.GetHeadPosition());

     while (pos) {  MyGraphSeries* pSeries =    static_cast<MyGraphSeries*> (m_olMyGraphSeries.GetNext(pos));  ASSERT_VALID(pSeries);

      nMax = max(nMax, pSeries->GetNonZeroElementCount()); }

     return nMax;}

    // Get the largest data value in all series.int MyGraph::GetMaxDataValue() const{ VALIDATE;

     int nMax(0); POSITION pos(m_olMyGraphSeries.GetHeadPosition());

     while (pos) {  MyGraphSeries* pSeries =    static_cast<MyGraphSeries*> (m_olMyGraphSeries.GetNext(pos));  ASSERT_VALID(pSeries);

      nMax = max(nMax, pSeries->GetMaxDataValue()); }

     return nMax;}

    // How many series are populated?int MyGraph::GetNonZeroSeriesCount() const{ VALIDATE;

     int nCount(0); POSITION pos(m_olMyGraphSeries.GetHeadPosition());

     while (pos) {  MyGraphSeries* pSeries =    static_cast<MyGraphSeries*> (m_olMyGraphSeries.GetNext(pos));  ASSERT_VALID(pSeries);

      if (0 < pSeries->GetNonZeroElementCount()) {   ++nCount;  } }

     return nCount;}

    // Returns the group number for the sent label; -1 if not found.int MyGraph::LookupLabel(const CString& sLabel) const{ VALIDATE; _ASSERTE(! sLabel.IsEmpty());  for (int nGroup = 0; nGroup < m_saLegendLabels.GetSize(); ++nGroup) {

      if (0 == sLabel.CompareNoCase(m_saLegendLabels.GetAt(nGroup))) {   return nGroup;  } }

     return -1;}

    //void MyGraph::AddSeries(MyGraphSeries& rMyGraphSeries){ VALIDATE; ASSERT_VALID(&rMyGraphSeries); _ASSERTE(m_saLegendLabels.GetSize() == rMyGraphSeries.m_dwaValues.GetSize());  m_olMyGraphSeries.AddTail(&rMyGraphSeries);}

    //void MyGraph::SetXAxisLabel(const CString& sLabel){ VALIDATE; _ASSERTE(! sLabel.IsEmpty());

     m_sXAxisLabel = sLabel;}

    //void MyGraph::SetYAxisLabel(const CString& sLabel){ VALIDATE; _ASSERTE(! sLabel.IsEmpty());

     m_sYAxisLabel = sLabel;}

    // Returns the group number added.  Also, makes sure that all the series have // this many elements.int MyGraph::AppendGroup(const CString& sLabel){ VALIDATE; _ASSERTE(! sLabel.IsEmpty());

     // Add the group. int nGroup(m_saLegendLabels.GetSize()); SetLegend(nGroup, sLabel);

     // Make sure that all series have this element. POSITION pos(m_olMyGraphSeries.GetHeadPosition());

     while (pos) {

      MyGraphSeries* pSeries =   static_cast<MyGraphSeries*> (m_olMyGraphSeries.GetNext(pos));  ASSERT_VALID(pSeries);

      if (nGroup >= pSeries->m_dwaValues.GetSize()) {   pSeries->m_dwaValues.SetAtGrow(nGroup, 0);  } }

     return nGroup;}

    // Set this value to the legend.void MyGraph::SetLegend(int nGroup, const CString& sLabel){ VALIDATE; _ASSERTE(0 <= nGroup); _ASSERTE(! sLabel.IsEmpty());

     m_saLegendLabels.SetAtGrow(nGroup, sLabel);}

    //void MyGraph::SetGraphTitle(const CString& sTitle){ VALIDATE; _ASSERTE(! sTitle.IsEmpty());

     m_sTitle = sTitle;}

    //void MyGraph::DrawGraph(CDC& dc){ VALIDATE; ASSERT_VALID(&dc);

     if (GetMaxSeriesSize()) {  dc.SetBkMode(TRANSPARENT);

      // Populate the colors as a group of evenly spaced colors of maximum  // saturation.  int nColorsDelta(240 / GetMaxSeriesSize());    for (int nGroup = 0; nGroup < GetMaxSeriesSize(); ++nGroup) {   COLORREF cr(MyGraph::HLStoRGB(nColorsDelta * nGroup, 120, 240));   m_dwaColors.SetAtGrow(nGroup, cr);  }

      // Reduce the graphable area by the frame window and status bar.  We will  // leave GAP_PIXELS pixels blank on all sides of the graph.  So top-left   // side of graph is at GAP_PIXELS,GAP_PIXELS and the bottom-right side   // of graph is at (m_rcGraph.Height() - GAP_PIXELS), (m_rcGraph.Width() -  // GAP_PIXELS).  These settings are altered by axis labels and legends.  CRect rcWnd;  GetClientRect(&rcWnd);  m_rcGraph.left = GAP_PIXELS;  m_rcGraph.top = GAP_PIXELS;  m_rcGraph.right = rcWnd.Width() - GAP_PIXELS;  m_rcGraph.bottom = rcWnd.Height() - GAP_PIXELS;

      CBrush br;  VERIFY(br.CreateSolidBrush(::GetSysColor(COLOR_WINDOW)));  dc.FillRect(rcWnd, &br);

      // Draw graph title.  DrawTitle(dc);

      // Set the axes and origin values.  SetupAxes(dc);

      // Draw legend if there is one.  if (m_saLegendLabels.GetSize()) {   DrawLegend(dc);  }

      // Draw axes unless it's a pie.  if (m_eGraphType != MyGraph::Pie) {   DrawAxes(dc);  }

      // Draw series data and labels.  switch (m_eGraphType) {   case MyGraph::Bar:  DrawSeriesBar(dc);  break;   case MyGraph::Line: DrawSeriesLine(dc); break;   case MyGraph::Pie:  DrawSeriesPie(dc);  break;   default: _ASSERTE(! "Bad default case"); break;  } }}

    // Draw graph title; size is proportionate to width.void MyGraph::DrawTitle(CDC& dc){ VALIDATE; ASSERT_VALID(&dc);

     // Create the title font. CFont fontTitle; VERIFY(fontTitle.CreatePointFont(m_rcGraph.Width() / TITLE_DIVISOR,  "Arial", &dc)); CFont* pFontOld = static_cast<CFont*> (dc.SelectObject(&fontTitle)); ASSERT_VALID(pFontOld);

     // Draw the title. m_rcTitle.SetRect(GAP_PIXELS, GAP_PIXELS, m_rcGraph.Width() + GAP_PIXELS,  m_rcGraph.Height() + GAP_PIXELS);

     dc.DrawText(m_sTitle, m_rcTitle, DT_CENTER | DT_NOPREFIX | DT_SINGLELINE |  DT_TOP | DT_CALCRECT);

     m_rcTitle.right = m_rcGraph.Width() + GAP_PIXELS;

     dc.DrawText(m_sTitle, m_rcTitle, DT_CENTER | DT_NOPREFIX | DT_SINGLELINE |   DT_TOP);

     VERIFY(dc.SelectObject(pFontOld));}

    // Set the axes and origin values.void MyGraph::SetupAxes(CDC& dc){ VALIDATE; ASSERT_VALID(&dc);

     // Since pie has no axis lines, set to full size minus GAP_PIXELS on each  // side.  These are needed for legend to plot itself. if (MyGraph::Pie == m_eGraphType) {  m_nXAxisWidth = m_rcGraph.Width() - (GAP_PIXELS * 2);  m_nYAxisHeight = m_rcGraph.Height() - m_rcTitle.bottom;  m_ptOrigin.x = GAP_PIXELS;  m_ptOrigin.y = m_rcGraph.Height() - GAP_PIXELS; } else {  // Bar and Line graphs.  CString sTickLabel;  sTickLabel.Format("%d", GetMaxDataValue());  CSize sizTickLabel(dc.GetTextExtent(sTickLabel));

      // Determine axis specifications.  Assume tick label and axes label   // fonts are about the same size.  m_ptOrigin.x = GAP_PIXELS + sizTickLabel.cx + GAP_PIXELS +    sizTickLabel.cy + GAP_PIXELS + TICK_PIXELS;  m_ptOrigin.y = m_rcGraph.Height() - sizTickLabel.cy - GAP_PIXELS -    sizTickLabel.cy - GAP_PIXELS - TICK_PIXELS;  m_nYAxisHeight = m_ptOrigin.y - m_rcTitle.bottom - (2 * GAP_PIXELS);  m_nXAxisWidth = (m_rcGraph.Width() - GAP_PIXELS) - m_ptOrigin.x; }}

    //void MyGraph::DrawLegend(CDC& dc){ VALIDATE; ASSERT_VALID(&dc);

     // Create the legend font. CFont fontLegend; VERIFY(fontLegend.CreatePointFont(m_rcGraph.Height() / LEGEND_DIVISOR,   "Arial", &dc)); CFont* pFontOld = static_cast<CFont*> (dc.SelectObject(&fontLegend)); ASSERT_VALID(pFontOld);

     // Get the height of each label. LOGFONT lf; ::ZeroMemory(&lf, sizeof(lf)); VERIFY(fontLegend.GetLogFont(&lf)); int nLabelHeight(abs(lf.lfHeight));

     // Determine size of legend.  A buffer of (GAP_PIXELS / 2) on each side,  // plus the height of each label based on the pint size of the font. int nLegendHeight((GAP_PIXELS / 2) + (GetMaxSeriesSize() * nLabelHeight) +  (GAP_PIXELS / 2));  // Draw the legend border.  Allow LEGEND_COLOR_BAR_PIXELS pixels for // display of label bars. m_rcLegend.top = (m_rcGraph.Height() / 2) - (nLegendHeight / 2); m_rcLegend.bottom = m_rcLegend.top + nLegendHeight; m_rcLegend.right = m_rcGraph.Width() - GAP_PIXELS; m_rcLegend.left = m_rcLegend.right - GetMaxLegendLabelLength(dc) -   LEGEND_COLOR_BAR_WIDTH_PIXELS; VERIFY(dc.Rectangle(m_rcLegend));

     // Draw each group's label and bar. for (int nGroup = 0; nGroup < GetMaxSeriesSize(); ++nGroup) {

      int nLabelTop(m_rcLegend.top + (nGroup * nLabelHeight) +   (GAP_PIXELS / 2));

      // Draw the label.  VERIFY(dc.TextOut(m_rcLegend.left + GAP_PIXELS, nLabelTop,   m_saLegendLabels.GetAt(nGroup)));

      // Determine the bar.  CRect rcBar;  rcBar.left = m_rcLegend.left + GAP_PIXELS + GetMaxLegendLabelLength(dc) +   GAP_PIXELS;  rcBar.top = nLabelTop + LEGEND_COLOR_BAR_GAP_PIXELS;  rcBar.right = m_rcLegend.right - GAP_PIXELS;  rcBar.bottom = rcBar.top + nLabelHeight - LEGEND_COLOR_BAR_GAP_PIXELS;  VERIFY(dc.Rectangle(rcBar));

      // Draw bar for group.  COLORREF crBar(m_dwaColors.GetAt(nGroup));  CBrush br(crBar);

      CBrush* pBrushOld = dc.SelectObject(&br);  ASSERT_VALID(pBrushOld);

      dc.SelectObject(&pBrushOld);  rcBar.DeflateRect(LEGEND_COLOR_BAR_GAP_PIXELS, LEGEND_COLOR_BAR_GAP_PIXELS);  dc.FillRect(rcBar, &br); }

     VERIFY(dc.SelectObject(pFontOld));}

    //void MyGraph::DrawAxes(CDC& dc) const{ VALIDATE; ASSERT_VALID(&dc); _ASSERTE(MyGraph::Pie != m_eGraphType);

     dc.SetTextColor(::GetSysColor(COLOR_WINDOWTEXT));

     // Draw y axis. dc.MoveTo(m_ptOrigin);   VERIFY(dc.LineTo(m_ptOrigin.x, m_ptOrigin.y - m_nYAxisHeight));

     // Draw x axis. dc.MoveTo(m_ptOrigin); 

     if (m_saLegendLabels.GetSize()) {

      VERIFY(dc.LineTo(m_ptOrigin.x +    (m_nXAxisWidth - m_rcLegend.Width() - (GAP_PIXELS * 2)),    m_ptOrigin.y)); } else {  VERIFY(dc.LineTo(m_ptOrigin.x + m_nXAxisWidth, m_ptOrigin.y)); }

     // Create the y-axis label font and draw it. CFont fontYAxes;

     VERIFY(fontYAxes.CreateFont(   /* nHeight */ m_rcGraph.Width() / 10 / Y_AXIS_LABEL_DIVISOR,  /* nWidth */ 0, /* nEscapement */ 90 * 10, /* nOrientation */ 0,  /* nWeight */ FW_DONTCARE, /* bItalic */ false, /* bUnderline */ false,  /* cStrikeOut */ 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS,  CLIP_DEFAULT_PRECIS, PROOF_QUALITY, VARIABLE_PITCH | FF_DONTCARE,   "Arial"));

     CFont* pFontOld = static_cast<CFont*> (dc.SelectObject(&fontYAxes)); ASSERT_VALID(pFontOld); CSize sizYLabel(dc.GetTextExtent(m_sYAxisLabel)); VERIFY(dc.TextOut(GAP_PIXELS, (m_rcGraph.Height() - sizYLabel.cy) / 2,  m_sYAxisLabel));

     // Create the x-axis label font and draw it. CFont fontXAxes; VERIFY(fontXAxes.CreatePointFont(m_rcGraph.Width() / X_AXIS_LABEL_DIVISOR,  "Arial", &dc)); VERIFY(dc.SelectObject(&fontXAxes)); CSize sizXLabel(dc.GetTextExtent(m_sXAxisLabel));

     VERIFY(dc.TextOut(m_ptOrigin.x + (m_nXAxisWidth - sizXLabel.cx) / 2,  m_rcGraph.Height() - GAP_PIXELS - sizXLabel.cy, m_sXAxisLabel));

     // We hardwire TITLE_DIVISOR y-axis ticks here for simplicity. int nTickCount(min(Y_AXIS_MAX_TICK_COUNT, GetMaxDataValue())); int nTickSpace(m_nYAxisHeight / nTickCount);

     for (int nTick = 0; nTick < nTickCount; ++nTick) {  int nTickYLocation(m_ptOrigin.y - (nTickSpace * (nTick + 1)));  dc.MoveTo(m_ptOrigin.x - TICK_PIXELS, nTickYLocation);  VERIFY(dc.LineTo(m_ptOrigin.x + TICK_PIXELS, nTickYLocation));

      // Draw tick label.  CString sTickLabel;  sTickLabel.Format("%d", (GetMaxDataValue() * (nTick + 1)) / nTickCount);  CSize sizTickLabel(dc.GetTextExtent(sTickLabel));    VERIFY(dc.TextOut(m_ptOrigin.x - GAP_PIXELS - sizTickLabel.cx - TICK_PIXELS,   nTickYLocation - sizTickLabel.cy, sTickLabel)); }

     // Draw X axis tick marks. POSITION pos(m_olMyGraphSeries.GetHeadPosition()); int nSeries(0);

     while (pos) {    MyGraphSeries* pSeries =   static_cast<MyGraphSeries*> (m_olMyGraphSeries.GetNext(pos));  ASSERT_VALID(pSeries);

      // Ignore unpopulated series if bar chart.  if (m_eGraphType != MyGraph::Bar  ||    0 < pSeries->GetNonZeroElementCount()) {

       // Get the spacing of the series.   _ASSERTE(GetNonZeroSeriesCount()  &&  "Div by zero coming");   int nSeriesSpace(0);

       if (m_saLegendLabels.GetSize()) {

        nSeriesSpace =     (m_nXAxisWidth - m_rcLegend.Width() - (GAP_PIXELS * 2)) /     (m_eGraphType == MyGraph::Bar ?     GetNonZeroSeriesCount() : m_olMyGraphSeries.GetCount());   }   else {    nSeriesSpace = m_nXAxisWidth / (m_eGraphType == MyGraph::Bar ?     GetNonZeroSeriesCount() : m_olMyGraphSeries.GetCount());   }

       int nTickXLocation(m_ptOrigin.x + ((nSeries + 1) * nSeriesSpace) -    (nSeriesSpace / 2));

       dc.MoveTo(nTickXLocation, m_ptOrigin.y - TICK_PIXELS);   VERIFY(dc.LineTo(nTickXLocation, m_ptOrigin.y + TICK_PIXELS));

       // Draw x-axis tick label.   CString sTickLabel(pSeries->GetLabel());   CSize sizTickLabel(dc.GetTextExtent(sTickLabel));

       VERIFY(dc.TextOut(nTickXLocation - (sizTickLabel.cx / 2),    m_ptOrigin.y + sizTickLabel.cy, sTickLabel));

       ++nSeries;  } }

     VERIFY(dc.SelectObject(pFontOld));}

    //void MyGraph::DrawSeriesBar(CDC& dc) const{ VALIDATE; ASSERT_VALID(&dc);

     // How much space does each series get (includes interseries space)? // We ignore series whose members are all zero. int nSeriesSpace(0);

     if (m_saLegendLabels.GetSize()) {

      nSeriesSpace = (m_nXAxisWidth - m_rcLegend.Width() - (GAP_PIXELS * 2)) /   GetNonZeroSeriesCount(); } else {  nSeriesSpace = m_nXAxisWidth / GetNonZeroSeriesCount(); }

     // Determine width of bars.  Data points with a value of zero are assumed  // to be empty.  This is a bad assumption. int nBarWidth(nSeriesSpace / GetMaxNonZeroSeriesSize());

     if (1 < GetNonZeroSeriesCount()) {  nBarWidth = (int) ((double) nBarWidth * INTERSERIES_PERCENT_USED); }

     // This is the width of the largest series (no interseries space). int nMaxSeriesPlotSize(GetMaxNonZeroSeriesSize() * nBarWidth);

     // Iterate the series. POSITION pos(m_olMyGraphSeries.GetHeadPosition()); int nSeries(0);

     while (pos) {    MyGraphSeries* pSeries =   static_cast<MyGraphSeries*> (m_olMyGraphSeries.GetNext(pos));  ASSERT_VALID(pSeries);

      // Ignore unpopulated series.  if (0 < pSeries->GetNonZeroElementCount()) {

       // Draw each bar; empty bars are not drawn.   int nRunningLeft(m_ptOrigin.x + ((nSeries + 1) * nSeriesSpace) -     nMaxSeriesPlotSize);

       for (int nGroup = 0; nGroup < GetMaxSeriesSize(); ++nGroup) {

        if (pSeries->GetData(nGroup)) {

         CRect rcBar;     rcBar.left = nRunningLeft;      rcBar.top = m_ptOrigin.y - (m_nYAxisHeight *      pSeries->GetData(nGroup)) / GetMaxDataValue();     rcBar.right = rcBar.left + nBarWidth;     rcBar.bottom = m_ptOrigin.y;

         pSeries->SetTipRegion(nGroup, rcBar);

         COLORREF crBar(m_dwaColors.GetAt(nGroup));     CBrush br(crBar);     CBrush* pBrushOld = dc.SelectObject(&br);     ASSERT_VALID(pBrushOld);

         VERIFY(dc.Rectangle(rcBar));     dc.SelectObject(&pBrushOld);

         nRunningLeft += nBarWidth;    }   }

       ++nSeries;  } }}

    //void MyGraph::DrawSeriesLine(CDC& dc) const{ VALIDATE; ASSERT_VALID(&dc);

     // Iterate the groups. CPoint ptLastLoc(0,0);

     for (int nGroup = 0; nGroup < GetMaxSeriesSize(); nGroup++) {

      // How much space does each series get (includes interseries space)?  int nSeriesSpace(0);

      if (m_saLegendLabels.GetSize()) {

       nSeriesSpace = (m_nXAxisWidth - m_rcLegend.Width() - (GAP_PIXELS * 2)) /    m_olMyGraphSeries.GetCount();  }  else {   nSeriesSpace = m_nXAxisWidth / m_olMyGraphSeries.GetCount();  }

      // Determine width of bars.  int nBarWidth(nSeriesSpace / GetMaxSeriesSize());

      if (1 < m_olMyGraphSeries.GetCount()) {   nBarWidth = (int) ((double) nBarWidth * INTERSERIES_PERCENT_USED);  }

      // This is the width of the largest series (no interseries space).  int nMaxSeriesPlotSize(GetMaxSeriesSize() * nBarWidth);

      // Iterate the series.  POSITION pos(m_olMyGraphSeries.GetHeadPosition());   for (int nSeries = 0; nSeries < m_olMyGraphSeries.GetCount(); ++nSeries) {

       MyGraphSeries* pSeries =    static_cast<MyGraphSeries*> (m_olMyGraphSeries.GetNext(pos));   ASSERT_VALID(pSeries);      // Get x and y location of center of ellipse.   CPoint ptLoc(0,0);      ptLoc.x = m_ptOrigin.x + (((nSeries + 1) * nSeriesSpace) -     (nSeriesSpace / 2));      double dLineHeight(pSeries->GetData(nGroup) * m_nYAxisHeight /    GetMaxDataValue());      ptLoc.y = (int) ((double) m_ptOrigin.y - dLineHeight);      // Build objects.   COLORREF crLine(m_dwaColors.GetAt(nGroup));   CBrush br(crLine);   CBrush* pBrushOld = dc.SelectObject(&br);   ASSERT_VALID(pBrushOld);

       // Draw line back to last data member.   if (nSeries > 0) {    CPen penLine(PS_SOLID, 1, crLine);    CPen* pPenOld = dc.SelectObject(&penLine);    ASSERT_VALID(pPenOld);

        dc.MoveTo(ptLastLoc.x + 2, ptLastLoc.y - 1);    VERIFY(dc.LineTo(ptLoc.x - 3, ptLoc.y - 1));    VERIFY(dc.SelectObject(pPenOld));   }

       // Now draw ellipse.   CRect rcEllipse(ptLoc.x - 3, ptLoc.y - 3, ptLoc.x + 3, ptLoc.y + 3);   VERIFY(dc.Ellipse(rcEllipse));

       pSeries->SetTipRegion(nGroup, rcEllipse);   dc.SelectObject(&pBrushOld);   ptLastLoc = ptLoc;  } }}

    //void MyGraph::DrawSeriesPie(CDC& dc) const{ VALIDATE; ASSERT_VALID(&dc); _ASSERTE(0 < GetNonZeroSeriesCount()  &&  "Div by zero");

     // Determine width of pie display area (pie and space). int nSeriesSpace(0);

     if (m_saLegendLabels.GetSize()) {    int nPieAndSpaceWidth((m_nXAxisWidth - m_rcLegend.Width() -    (GAP_PIXELS * 2)) / GetNonZeroSeriesCount());

      // Height is limiting factor.  if (nPieAndSpaceWidth > m_nYAxisHeight - (GAP_PIXELS * 2)) {   nSeriesSpace = (m_nYAxisHeight - (GAP_PIXELS * 2)) /    GetNonZeroSeriesCount();  }  else {   // Width is limiting factor.   nSeriesSpace = nPieAndSpaceWidth;  } } else {  // No legend box.

      // Height is limiting factor.  if (m_nXAxisWidth > m_nYAxisHeight) {   nSeriesSpace = m_nYAxisHeight / GetNonZeroSeriesCount();  }  else {   // Width is limiting factor.   nSeriesSpace = m_nXAxisWidth / GetNonZeroSeriesCount();  } }

     // Draw each pie. int nPie(0); int nRadius((int) (nSeriesSpace * INTERSERIES_PERCENT_USED / 2.0)); POSITION pos(m_olMyGraphSeries.GetHeadPosition());

     while (pos) {

      MyGraphSeries* pSeries =   static_cast<MyGraphSeries*> (m_olMyGraphSeries.GetNext(pos));  ASSERT_VALID(pSeries);

      // Don't leave a space for empty pies.  if (0 < pSeries->GetNonZeroElementCount()) {

       // Locate this pie.   CRect rcPie;   rcPie.left = m_ptOrigin.x + GAP_PIXELS + (nSeriesSpace * nPie);   rcPie.right = rcPie.left + (2 * nRadius);   rcPie.top = (m_nYAxisHeight / 2) - nRadius;   rcPie.bottom = (m_nYAxisHeight / 2) + nRadius;

       CPoint ptCenter((rcPie.left + rcPie.right) / 2,    (rcPie.top + rcPie.bottom) / 2);

       // Draw series label.   CSize sizPieLabel(dc.GetTextExtent(pSeries->GetLabel()));      VERIFY(dc.TextOut((rcPie.left + nRadius) - (sizPieLabel.cx / 2),     ptCenter.y + nRadius + GAP_PIXELS, pSeries->GetLabel()));      // How much do the wedges total to?   double dPieTotal(pSeries->GetDataTotal());

       // Draw each wedge in this pie.   CPoint ptStart(rcPie.left, ptCenter.y);   double dRunningWedgeTotal(0.0);      for (int nGroup = 0; nGroup < m_saLegendLabels.GetSize(); ++nGroup) {

        // Ignore empty wedges.    if (0 < pSeries->GetData(nGroup)) {

         // Get the degrees of this wedge.     dRunningWedgeTotal += pSeries->GetData(nGroup);     double dPercent(dRunningWedgeTotal * 100.0 / dPieTotal);     int nDegrees((int) (360.0 * dPercent / 100.0));

         // Find the location of the wedge's endpoint.     CPoint ptEnd(WedgeEndFromDegrees(nDegrees, ptCenter, nRadius));

         // Special case: a wedge that takes up the whole pie would     // otherwise be confused with an empty wedge.     if (1 == pSeries->GetNonZeroElementCount()) {      _ASSERTE(360 == nDegrees  &&  ptStart == ptEnd  &&  "This is the problem we're correcting");      --ptEnd.y;     }

         // If the wedge is of zero size, don't paint it!     if (ptStart != ptEnd) {

          // Draw wedge.      COLORREF crWedge(m_dwaColors.GetAt(nGroup));      CBrush br(crWedge);      CBrush* pBrushOld = dc.SelectObject(&br);      ASSERT_VALID(pBrushOld);      VERIFY(dc.Pie(rcPie, ptStart, ptEnd));

          // Create a region from the path we create.      VERIFY(dc.BeginPath());      VERIFY(dc.Pie(rcPie, ptStart, ptEnd));      VERIFY(dc.EndPath());      CRgn* prgnWedge = new CRgn;      VERIFY(prgnWedge->CreateFromPath(&dc));      pSeries->SetTipRegion(nGroup, prgnWedge);

          // Cleanup.      dc.SelectObject(pBrushOld);      ptStart = ptEnd;     }    }   }

       ++nPie;  } }}

    // Convert degrees to x and y coords.CPoint MyGraph::WedgeEndFromDegrees(int nDegrees, const CPoint& ptCenter,            int nRadius) const{ VALIDATE;

     CPoint pt;

     pt.x = (int) ((double) nRadius * cos((double) nDegrees / 360.0 * PI * 2.0)); pt.x = ptCenter.x - pt.x;

     pt.y = (int) ((double) nRadius * sin((double) nDegrees / 360.0 * PI * 2.0)); pt.y = ptCenter.y + pt.y;

     return pt;}

    // Spin The Message Loop: C++ version.  See "Advanced Windows Programming", // M. Heller, p. 153, and the MS TechNet CD, PSS ID Number: Q99999./* static */ UINT MyGraph::SpinTheMessageLoop(bool bNoDrawing /* = false */ ,                bool bOnlyDrawing /* = false */ ,                UINT uiMsgAllowed /* = WM_NULL */ ){ MSG msg; ::ZeroMemory(&msg, sizeof(msg));

     while (::PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {

      // Do painting only.  if (bOnlyDrawing  &&  WM_PAINT == msg.message)  {   ::TranslateMessage(&msg);   ::DispatchMessage(&msg);

       // Update user interface.   AfxGetApp()->OnIdle(0);  }  // Do everything *but* painting.  else if (bNoDrawing  &&  WM_PAINT == msg.message)  {   break;  }  // Special handling for this message.  else if (WM_QUIT == msg.message) {   ::PostQuitMessage(msg.wParam);   break;  }  // Allow one message (like WM_LBUTTONDOWN).  else if (uiMsgAllowed == msg.message    &&  ! AfxGetApp()->PreTranslateMessage(&msg)) {   ::TranslateMessage(&msg);   ::DispatchMessage(&msg);   break;  }  // This is the general case.  else if (! bOnlyDrawing  &&  ! AfxGetApp()->PreTranslateMessage(&msg)) {   ::TranslateMessage(&msg);   ::DispatchMessage(&msg);

       // Update user interface, then free temporary objects.   AfxGetApp()->OnIdle(0);   AfxGetApp()->OnIdle(1);  } }

     return msg.message;}

    /// Conversion routines: RGB to HLS (Red-Green-Blue to Hue-Luminosity-Saturation).// See Microsoft KnowledgeBase article Q29240.

    #define  HLSMAX   240  // H,L, and S vary over 0-HLSMAX#define  RGBMAX   255  // R,G, and B vary over 0-RGBMAX         // HLSMAX BEST IF DIVISIBLE BY 6         // RGBMAX, HLSMAX must each fit in a byte (255).

    #define  UNDEFINED  (HLSMAX * 2 / 3)  // Hue is undefined if Saturation is 0               // (grey-scale).  This value determines               // where the Hue scrollbar is initially               // set for achromatic colors.

    // Convert HLS to RGB./* static */ COLORREF MyGraph::HLStoRGB(WORD wH, WORD wL, WORD wS){ _ASSERTE(0 <= wH  &&  240 >= wH  &&  "Illegal hue value"); _ASSERTE(0 <= wL  &&  240 >= wL  &&  "Illegal lum value"); _ASSERTE(0 <= wS  &&  240 >= wS  &&  "Illegal sat value");

     WORD wR(0); WORD wG(0); WORD wB(0);  // Achromatic case. if (0 == wS) {  wR = wG = wB = (wL * RGBMAX) / HLSMAX;

      if (UNDEFINED != wH) {   _ASSERTE(! "ERROR");  } } else {  // Chromatic case.  WORD Magic1(0);  WORD Magic2(0);

      // Set up magic numbers.  if (wL <= HLSMAX / 2) {   Magic2 = (wL * (HLSMAX + wS) + (HLSMAX / 2)) / HLSMAX;  }  else {   Magic2 = wL + wS - ((wL * wS) + (HLSMAX / 2)) / HLSMAX;  }

      Magic1 = 2 * wL - Magic2;    // Get RGB, change units from HLSMAX to RGBMAX.  wR = (HueToRGB(Magic1, Magic2, wH + (HLSMAX / 3)) * RGBMAX + (HLSMAX / 2)) / HLSMAX;  wG = (HueToRGB(Magic1, Magic2, wH)                * RGBMAX + (HLSMAX / 2)) / HLSMAX;  wB = (HueToRGB(Magic1, Magic2, wH - (HLSMAX / 3)) * RGBMAX + (HLSMAX / 2)) / HLSMAX; }  return RGB(wR,wG,wB);}

    // Utility routine for HLStoRGB./* static */ WORD MyGraph::HueToRGB(WORD w1, WORD w2, WORD wH){ // Range check: note values passed add/subtract thirds of range. if (wH < 0) {  wH += HLSMAX; }

     if (wH > HLSMAX) {  wH -= HLSMAX; }

     // Return r, g, or b value from this tridrant. if (wH < HLSMAX / 6) {  return w1 + (((w2 - w1) * wH + (HLSMAX / 12)) / (HLSMAX / 6)); }

     if (wH < HLSMAX / 2) {  return w2; }

     if (wH < (HLSMAX * 2) / 3) {  return w1 + (((w2 - w1) * (((HLSMAX * 2) / 3) - wH) + (HLSMAX / 12)) / (HLSMAX / 6)); } else {  return w1; }}

     


    最新回复(0)