vc 画图(饼图)收集

// 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 functions
protected:
 //{{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_FILE
static 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;
 }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值