www.gusucode.com > VC++自定义图表控件及使用例子源码程序 > VC++自定义图表控件及使用例子源码程序\code\ChartCtrl_source\ChartLineSerie.cpp

    //Download by http://www.NewXing.com
/*
 *
 *	ChartLineSerie.cpp
 *
 *	Written by C閐ric Moonen (cedric_moonen@hotmail.com)
 *
 *
 *
 *	This code may be used for any non-commercial and commercial purposes in a compiled form.
 *	The code may be redistributed as long as it remains unmodified and providing that the 
 *	author name and this disclaimer remain intact. The sources can be modified WITH the author 
 *	consent only.
 *	
 *	This code is provided without any garanties. I cannot be held responsible for the damage or
 *	the loss of time it causes. Use it at your own risks
 *
 *	An e-mail to notify me that you are using this code is appreciated also.
 *
 *	History:
 *		- 25/03/2008: Line series with a width > 1 can now have a style other than solid
 *					  (thanks to Bruno Lavier).
 *		- 12/08/2008: Performance fix: pen use the PS_GEOMETRIC style only when necessary
 *					  (thanks to Nick Holgate).
 *
 *
 */

#include "stdafx.h"
#include "ChartLineSerie.h"
#include "ChartCtrl.h"

#include "Math.h"

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

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CChartLineSerie::CChartLineSerie(CChartCtrl* pParent) : CChartXYSerie(pParent)
{
	m_iLineWidth = 1;
	m_iPenStyle = PS_SOLID;
	m_bSmooth = false;
	m_bShadow = false;
}

CChartLineSerie::~CChartLineSerie()
{

}

void CChartLineSerie::SetPenStyle(int NewStyle)  
{ 
	m_iPenStyle = NewStyle; 
	m_pParentCtrl->RefreshCtrl();
}

void CChartLineSerie::SetWidth(int PenWidth)  
{ 
	m_iLineWidth = PenWidth; 
	m_pParentCtrl->RefreshCtrl();
}

void CChartLineSerie::SetSmooth(bool bSmooth)
{
	m_bSmooth = bSmooth; 
	m_pParentCtrl->RefreshCtrl();
}

void CChartLineSerie::DrawAll(CDC *pDC)
{
	if (!m_bIsVisible)
		return;
	if (!pDC->GetSafeHdc())
		return;

	unsigned uFirst=0, uLast=0;
	if (!GetVisiblePoints(uFirst,uLast))
		return;

	if (uFirst>0)
		uFirst--;
	if (uLast<GetPointsCount()-1)
		uLast++;
	if (uLast-uFirst < 1)
		return;

	CPen NewPen;
	CPen ShadowPen;
	if (m_iPenStyle != PS_SOLID)
	{
		LOGBRUSH lb;
		lb.lbStyle = BS_SOLID;
		lb.lbColor = m_SerieColor;
		NewPen.CreatePen(PS_GEOMETRIC | m_iPenStyle, m_iLineWidth, &lb);
		lb.lbColor = m_ShadowColor;
		ShadowPen.CreatePen(PS_GEOMETRIC | m_iPenStyle, m_iLineWidth, &lb);
	}
	else
	{
		NewPen.CreatePen(m_iPenStyle, m_iLineWidth, m_SerieColor);
		ShadowPen.CreatePen(m_iPenStyle, m_iLineWidth, m_ShadowColor);
	}
	CPen* pOldPen;

	pDC->SetBkMode(TRANSPARENT);
	//To have lines limited in the drawing rectangle :
	pDC->IntersectClipRect(m_PlottingRect);
	pOldPen = pDC->SelectObject(&NewPen);

	if (m_bSmooth)
	{
		// For a Bezier curve, all points must be drawn.
		uFirst = 0;
		uLast = GetPointsCount() - 1;
		SChartXYPoint* pKnots = NULL;
		SChartXYPoint* pFirstControlPts = NULL;
		SChartXYPoint* pSecondControlPts = NULL;
		GetBezierControlPoints(uFirst,uLast,pKnots,pFirstControlPts,pSecondControlPts);

		unsigned Count = uLast - uFirst;
		CPoint* pBezierPts = new CPoint[3*(Count-1)+1];
		CPoint* pShadowPts = NULL;
		if (m_bShadow)
			pShadowPts = new CPoint[3*(Count-1)+1];
		
		unsigned index = 0;
		for (unsigned n=0; n<Count-1; n++)
		{
			ValueToScreen(pKnots[n].X, pKnots[n].Y, pBezierPts[index]);
			ValueToScreen(pFirstControlPts[n].X, pFirstControlPts[n].Y, pBezierPts[index+1]);
			ValueToScreen(pSecondControlPts[n].X, pSecondControlPts[n].Y, pBezierPts[index+2]);
			
			if (m_bShadow)
			{
				pShadowPts[index] = pBezierPts[index];
				pShadowPts[index].Offset(m_iShadowDepth,m_iShadowDepth);
				pShadowPts[index+1] = pBezierPts[index+1];
				pShadowPts[index+1].Offset(m_iShadowDepth,m_iShadowDepth);
				pShadowPts[index+2] = pBezierPts[index+2];
				pShadowPts[index+2].Offset(m_iShadowDepth,m_iShadowDepth);
			}
			index += 3;
		}
		ValueToScreen(pKnots[Count-1].X, pKnots[Count-1].Y, pBezierPts[index]);
		if (m_bShadow)
		{
			pShadowPts[index] = pBezierPts[index];
			pShadowPts[index].Offset(m_iShadowDepth,m_iShadowDepth);
			pDC->SelectObject(&ShadowPen);
			pDC->PolyBezier(pShadowPts,3*(Count-1)+1);
			pDC->SelectObject(&NewPen);
			delete[] pShadowPts;
		}
		pDC->PolyBezier(pBezierPts,3*(Count-1)+1);

		delete[] pKnots;
		delete[] pFirstControlPts;
		delete[] pSecondControlPts;
		delete[] pBezierPts;
	}
	else	// Non-smoothed curve
	{	
		if (uLast-uFirst >= 1)
		{
			CPoint* pPoints = new CPoint[uLast-uFirst+1];
			CPoint* pShadow = NULL;
			if (m_bShadow)
				pShadow = new CPoint[uLast-uFirst+1];

			unsigned long pointsCount = 0;
			CPoint LastScreenPoint;
			for (m_uLastDrawnPoint=uFirst;m_uLastDrawnPoint<=uLast;m_uLastDrawnPoint++)
			{
				//We don't draw a line between the origin and the first point -> we must have
				// a least 2 points before begining drawing
				SChartXYPoint Point = GetPoint(m_uLastDrawnPoint);
				CPoint ScreenPoint;
				ValueToScreen(Point.X, Point.Y, ScreenPoint);

				if(LastScreenPoint != ScreenPoint)
				{
					//Only collate the unique points 
					pPoints[pointsCount] = ScreenPoint;
					LastScreenPoint = ScreenPoint;
					
					if (m_bShadow) 
					{
						ScreenPoint.Offset(m_iShadowDepth,m_iShadowDepth);
						pShadow[pointsCount] = ScreenPoint;
					}
					pointsCount++;
				}
			}

			// We have to do that in order for the Draw function to work properly.
			m_uLastDrawnPoint--;
			if (m_bShadow)
			{
				pDC->SelectObject(&ShadowPen);
				pDC->Polyline(pShadow, pointsCount);
			}
			pDC->SelectObject(&NewPen);
			pDC->Polyline(pPoints, pointsCount);

			delete[] pPoints;
			delete[] pShadow;
		}
	}

	pDC->SelectClipRgn(NULL);
	pDC->SelectObject(pOldPen);
	NewPen.DeleteObject();
	ShadowPen.DeleteObject();
}

void CChartLineSerie::Draw(CDC* pDC)
{
	if (!m_bIsVisible)
		return;

	// If shadow or smooth is enabled, then the complete series 
	// must be redrawn.
	if (m_bShadow || m_bSmooth)
	{
		DrawAll(pDC);
		return;
	}

	if (pDC->GetSafeHdc())
	{
		CPen NewPen;
		if (m_iPenStyle != PS_SOLID)
		{
			LOGBRUSH lb;
			lb.lbStyle = BS_SOLID;
			lb.lbColor = m_SerieColor;
			NewPen.CreatePen(PS_GEOMETRIC | m_iPenStyle, m_iLineWidth, &lb);
		}
		else
		{
			NewPen.CreatePen(m_iPenStyle, m_iLineWidth, m_SerieColor);
		}
		CPen* pOldPen;

		pDC->SetBkMode(TRANSPARENT);
		//To have lines limited in the drawing rectangle :
		pDC->IntersectClipRect(m_pParentCtrl->GetPlottingRect());
		pOldPen = pDC->SelectObject(&NewPen);

		//Draw all points that haven't been drawn yet
		for (m_uLastDrawnPoint;m_uLastDrawnPoint<GetPointsCount()-1;m_uLastDrawnPoint++)
		{
			SChartXYPoint Point = GetPoint(m_uLastDrawnPoint);
			CPoint ScreenPoint;
			ValueToScreen(Point.X, Point.Y, ScreenPoint);
			pDC->MoveTo(ScreenPoint.x,ScreenPoint.y);

			Point = GetPoint(m_uLastDrawnPoint+1);
			ValueToScreen(Point.X, Point.Y, ScreenPoint);
			pDC->LineTo(ScreenPoint.x,ScreenPoint.y);
		}

		pDC->SelectClipRgn(NULL);
		pDC->SelectObject(pOldPen);
		DeleteObject(NewPen);
	}
}

void CChartLineSerie::DrawLegend(CDC *pDC, const CRect& rectBitmap) const
{
	if (m_strSerieName== _T(""))
		return;

	//Draw line:
	LOGBRUSH lb;
	lb.lbStyle = BS_SOLID;
	lb.lbColor = m_SerieColor;
	CPen NewPen(PS_GEOMETRIC | m_iPenStyle,m_iLineWidth,&lb);
	CPen* pOldPen = pDC->SelectObject(&NewPen);
	pDC->MoveTo(rectBitmap.left,rectBitmap.CenterPoint().y);
	pDC->LineTo(rectBitmap.right,rectBitmap.CenterPoint().y);
	pDC->SelectObject(pOldPen);
	DeleteObject(NewPen);
}

bool CChartLineSerie::IsPointOnSerie(const CPoint& screenPoint, unsigned& uIndex) const
{
	uIndex = INVALID_POINT;
	if (!m_bIsVisible)
        return false;

	unsigned uFirst=0, uLast=0;
	if (!GetVisiblePoints(uFirst, uLast))
		return false;
	if (uFirst>0)
		uFirst--;
	if (uLast<GetPointsCount()-1)
		uLast++;

	bool bResult = false;
	for (unsigned i=uFirst ; i < uLast ; i++)
	{
		SChartXYPoint PointOrig = GetPoint(i);
		SChartXYPoint PointDest = GetPoint(i+1);
		CPoint ScreenPointOrig, ScreenPointDest;
		ValueToScreen(PointOrig.X, PointOrig.Y, ScreenPointOrig);
		ValueToScreen(PointDest.X, PointDest.Y, ScreenPointDest);

		if (IsNearLine(ScreenPointOrig.x, ScreenPointOrig.y, ScreenPointDest.x, ScreenPointDest.y, screenPoint.x, screenPoint.y))
		{
			// Check if the click is close to one of the two points.
			int xDist = abs(screenPoint.x - ScreenPointOrig.x);
			int yDist = abs(screenPoint.y - ScreenPointOrig.y);
			if (xDist<=5 && yDist<=5)
				uIndex = i;
			xDist = abs(screenPoint.x - ScreenPointDest.x);
			yDist = abs(screenPoint.y - ScreenPointDest.y);
			if (xDist<=5 && yDist<=5)
				uIndex = i+1;

			bResult = true;
			break;
		}
    }
    return bResult;
}

bool CChartLineSerie::IsNearLine(long Axl, long Ayl, long Bxl, 
								 long Byl, long Cxl, long Cyl) const
{
    double Ax = Axl;
    double Ay = Ayl;
    double Bx = Bxl;
    double By = Byl;
    double Cx = Cxl;
    double Cy = Cyl;

    // Make a perpendicular projection of point C on line AB
    // algorithm from http://www.exaflop.org/docs/cgafaq/cga1.html#Subject%201.02:%20How%20do%20I%20find%20the%20distance%20from%20a%20point%20to%20a%20line?
    double L = sqrt((Bx-Ax)*(Bx-Ax) + (By-Ay)*(By-Ay));
    double r = (Ay-Cy)*(Ay-By)-(Ax-Cx)*(Bx-Ax);
    r = r /(L*L);
    if ((0 <= r) && (r <= 1))
    {
        double Px = Ax + r*(Bx-Ax);
        double Py = Ay + r*(By-Ay);
        if ((abs(Cx - Px) <= 3.0) &&
            (abs(Cy - Py) <= 3.0))
        {
            return true;
        }
    }
    return false;
}