65.9K
CodeProject 正在变化。 阅读更多。
Home

简单的饼图控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (27投票s)

2009 年 3 月 5 日

CPOL

4分钟阅读

viewsIcon

168374

downloadIcon

6665

本文旨在介绍一个用于 MFC 应用程序的简单饼图控件。

引言

本文旨在介绍一个用于 MFC 应用程序的简单饼图控件。网上有很多图表控件和库,但有些在图形方面功能不足或过于复杂,对于小型项目来说会有些负担。这个饼图是使用 GDI+ 图形库为 MFC 平台编写的,它使用的类(单个类)和提供的基本功能都很简单。它有三种饼图样式。它们是:

  1. 甜甜圈样式。
  2. 二维样式。
  3. 三维样式。

它包含了为每个饼图元素、背景和文本区域设置颜色等功能。还提供了设置文本字体、设置旋转以及为 3D 样式设置倾斜度的功能。

背景

该实现使用了 GDI+ 的基本绘图功能。但有三个属性提供了控件的视觉效果。它们是:

  1. 颜色的渐变计算。
  2. 无闪烁绘制。
  3. 3D 饼图计算。

计算不同颜色的渐变

它使用简单的线性计算来查找渐变颜色。计算给定颜色的渐变如下:

  1. 单独获取给定颜色的 RGB 值。
  2. 要计算深色渐变,将 RGB 值除以一个公共因子,然后乘以用户设置的因子,该因子等于或小于除法因子。

    例如,对于 R 值,R 渐变 = (R / 除法因子) * 因子(线性方程)

  3. 然后,将此值从原始 R 中减去,计算出 R 新值,从而为相应的颜色创建深色渐变值。

除法因子使用 255。选择 255 的原因是,对于任何 RGB 值,其最大值都可以是 255。

要计算浅色渐变,将每个 RGB 值减去 255,然后除以 255 来找到渐变。

将此值乘以一个因子并加到原始 RGB 值上。

Color CPieChartWnd::CalculateGradientLight(Color crBase, float fGradVal)
{

	BYTE r = crBase.GetR();
	BYTE g = crBase.GetG();
	BYTE b = crBase.GetB();

	float fact = 255.0f;
	float rGrad = (255 - r) / fact;
	float gGrad = (255 - g) / fact;
	float bGrad = (255 - b) / fact;
	
	r =  BYTE(min(r + rGrad * fGradVal, 255));
	b =  BYTE(min(b + bGrad * fGradVal, 255));
	g =  BYTE(min(g + gGrad * fGradVal, 255));

	return Color(r, g, b);
}

Color CPieChartWnd::CalculateGradientDark(Color crBase, float fGradVal)
{

	BYTE r = crBase.GetR();
	BYTE g = crBase.GetG();
	BYTE b = crBase.GetB();

	float fact = 255.0f;
	float rGrad = r / fact;
	float gGrad = g / fact;
	float bGrad = b / fact;

	r = BYTE(max(r - rGrad * fGradVal, 0));
	b = BYTE(max(b - bGrad * fGradVal, 0));
	g = BYTE(max(g - gGrad * fGradVal, 0));

	return Color(r, g, b);
}
PieChart2.jpgPieChart3.JPG

无闪烁绘制

GDI+ 中的双缓冲是通过 `Bitmap` 和 `CachedBitmap` 对象实现的。我的使用方法是使用 `Graphics::FromImage` 创建一个 `Graphics` 对象。传入的 `Bitmap` 的大小与 Cwnd 区域相同。然后,所有绘图操作都在 `Graphics` 对象上进行。最后,释放此 `Graphics` 对象,并将 `Bitmap` 用于创建 `CachedBitmap`,它将从设备上下文中创建的图形成员构建。一种技术是保存 `Bitmap` 对象,并在调整大小时或绘图更改时将其设置为脏。其他时候,保存的位图将直接在 `CachedBitmap` 上使用。但在本文中,我只在 `onPaint` 调用时创建和销毁 `Bitmap`。

    Bitmap* mBtmap = new Bitmap(rect.Width(), rect.Height());
    graphics = Graphics::FromImage(mBtmap);
	
    //
    //rest of the drawings done on graphics object
    //
    
    delete graphics;
    graphics = NULL;

    Graphics gr(pDc->m_hDC);
    gr.SetSmoothingMode(SmoothingModeHighQuality);
    CachedBitmap* btmp = new CachedBitmap(mBtmap, &gr);

    if (mBtmap){
	    delete mBtmap;
	    mBtmap = NULL;
    }
    gr.DrawCachedBitmap(btmp, rect.left, rect.top);	
    if (btmp){
	    delete btmp;
	    btmp = NULL;
    }

3D 对象计算

对于 3D 饼图,它需要根据倾斜角度对绘图参数进行一些变换。因此,当设置倾斜角度时,圆形饼图会变成椭圆形,导致元素饼图区域发生视觉变化。这些区域完全根据每个元素的饼图角度变化进行变换。该角度计算如下:

PieChart4.jpg

因此,计算是:

新点 y' = y + x * sin (α)。使用点的新的位置和椭圆的中心点,计算新的角度 α。

void CPieChartWnd::UpdatePiechartPoints(void)
{
	//The calculated pie element points are relocated according to the
         //incline angle and the resulting formations of angles were calculated and set.
	CRect rectBnd;
	GetBoundRect(rectBnd);
	//Calculate (set) the original locations for the points prior to the
         //circle it bounds.
	CalcuatePieElemetPoints();
	
	float flStart = fl_startAngle;
	float flStartIncline = 0;
	PointF ptStart;
	CRect rectBtm, rectTop;
	Get3DBounds(rectTop, rectBtm);

	long rectClip = long(fl_InclineAngle * rectBnd.Height() / 180);
	
	rectTop.top += long(rectClip * f_depth / 2);
	rectTop.bottom += long(rectClip * f_depth / 2);

	REAL xPoint = REAL(rectBnd.CenterPoint().x + 
				(rectBnd.Width() / 2) * cos(PI * (flStart)/ 180));
	REAL yPoint = REAL(rectBnd.CenterPoint().y + 
				(rectBnd.Height() / 2) * sin(PI * (flStart)/ 180));

	ptStart.X = xPoint;
	//Relocate the start angle y cordinate according to the inclination
	ptStart.Y = yPoint - REAL(rectClip * sin((flStart) * PI / 180));

	flStartIncline = CacluateInclineAngle(ptStart, rectTop);
	fl_startAngleIncline = flStartIncline;
	
	map<int, pie_chart_element*>::reverse_iterator iter = map_pChart.rbegin();
	//Relocate the y coordinates according to the incline angle and recalculate
         //the angles for elements
	for (; iter != map_pChart.rend(); iter++){
		pie_chart_element* ele = iter->second;
		ele->pie_3d_props.pt_InPie.Y -= REAL(
                      rectClip * sin((flStart + ele->f_angle) * PI / 180));
		float inClineAngle = CacluateInclineAngle(ele->pie_3d_props.pt_InPie,
                      rectTop);
	
		if(inClineAngle == flStartIncline && ele->f_angle == 360)
                  // Two points lies in the same location and the angle is 360
				inClineAngle = 360;	
		else if (inClineAngle >= flStartIncline)
				inClineAngle -= flStartIncline;
		else
			inClineAngle += (360 - flStartIncline);
	
		ele->pie_3d_props.f_InclineAngle = inClineAngle;
		flStartIncline += inClineAngle;
		flStart += ele->f_angle;	
	}	
}

使用控件

通过在客户端框架上创建一个 `CPieChartwnd` 成员,可以轻松使用此控件。

BOOL CPieChartWnd::Create(LPCTSTR lpCaption, const RECT& rect, CWnd* pParentWnd,
    UINT nID)

提供了各种函数来设置颜色、饼图样式、字体和颜色渐变等视觉属性。通过在计时器中设置起始角度可以创建旋转效果,通过设置倾斜角度可以为 3D 样式图表创建倾斜效果。此外,它还具有一些对元素进行排序以及恢复到添加顺序的功能。

饼图元素结构

struct pie_3d_properties{
	float f_InclineAngle; //The transformed angle for 3-D pie chart
	PointF pt_InPie;	    //The location point for a single element on the face
                               //of pie
	GraphicsPath path;	    //The visible path object from  side view
};

struct pie_chart_element{
		double d_value;
		float f_percentage;
		float f_angle;	
		float f_ColorGradL;
		float f_ColorGradD;
		Color cr_GradientL;
		Color cr_GradientD;
		Color cr_Base;
		CString s_label;
		CString s_element;
		pie_3d_properties pie_3d_props;
		int i_ID;
		BOOL b_select;
		
	};

饼图元素有两个唯一的键。一个是字符串键,用户可以在添加元素时定义。另一个是整数标识符。这是由控件本身添加的。字符串键必须是唯一的且不能为空字符串。

添加元素

定义了 `InsertItem` 方法来向图表中添加元素。如果插入成功,它将返回一个指向该元素的指针,该元素是 `pie_chart_element` 的定义数据类型。返回的指针类型是 `PIECHARTITEM`。

可以使用两个键或使用 `PIECHARTITEM` 指针删除元素。

PIECHARTITEM InsertItem(CString sElement, CString sLabel, double dValue, Color crColor);
	
//Remove item functions
BOOL RemoveItem(CString sElement);
BOOL RemoveItem(int iElementID);
BOOL RemoveItem(PIECHARTITEM pItem);

关注点

此控件仅提供饼图控件应有的基本功能,但对于小型 MFC 应用程序会很有用。此外,这也可以作为 GDI+ 图形学习材料。

© . All rights reserved.