Click here to Skip to main content
15,881,248 members
Articles / Multimedia / GDI+
Article

A Simple Pie Chart Control

Rate me:
Please Sign up or sign in to vote.
4.85/5 (28 votes)
5 Mar 2009CPOL4 min read 162.2K   6.6K   125   44
An article written in to describe a simple pie chart control implemented to be used in MFC applications.
Image 1

Introduction

This article is written to describe the simple pie chart control implemented for MFC applications. There are so many chart controls and libraries available online but some of them are lacking with regard to graphics or rich in graphics, and are a bit heavily used on small projects. This pie chart is written for the MFC platform using the graphics library GDI+ and simple in terms of classes used (single class) and the basic functionalities it provides. It has three styles of pie charts. They are:

  1. Doughnut style.
  2. Two dimensional style.
  3. Three dimensional style.

It consists of functionalities like setting colors to each pie chart elements, background and text areas. Setting up fonts for the texts, setting up rotations and setting inclination for 3-D style are also present.

Background

The implementation uses the basic drawing functionalities in GDI+. But there are three properties which provide the visual qualities for the control. They are:

  1. Gradient calculation for the colors.
  2. Flicker-free drawing.
  3. Calculation for 3-D pie.

Calculating Gradient for Different Colors

It uses a simple linear calculation to find the gradient colors. Calculating the gradient for a given color was done as follows:

  1. Take RGB values separately for the given color.
  2. To calculate the dark gradient, they are divided by a common factor, later multiplied by a factor set by the user which will be equal or less than the division factor.

    Ex. for R value, R gradient = (R / division factor)* factor (linear equation)

  3. Then this value is subtracted from original R, calculating the R new value which creates a dark gradient value for the corresponding color.

For the division factor the value used is 255. The reason to choose 255 is that for any RGB value, the maximum it can get is 255.

To calculate the light gradient, each RGB values are subtracted from 255 and then divided by 255 to find the gradient.

This value is multiplied by a factor and added to original RGB values.

C++
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

Flicker-free Drawing

The double buffering in GDI+ is done using the Bitmap and CachedBitmap objects. The way I used this is by creating a Graphics object using Graphics::FromImage. The Bitmap passed is created in the size of Cwnd area. Then all the drawing operations were done on the Graphics object. Finally this Graphics object is freed and the Bitmap is used in creating a CachedBitmap which will build from the graphics member created on device context. One technique is to save the Bitmap object and set it dirty on resizing or when the drawing changes. At other times the saved bitmap will be used directly on the CachedBitmap. But in this I only create and destroy the Bitmap when onPaint calls.

C++
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;
}

Calculation for 3-D Object

In the case of a 3-D pie chart, it has some transformation to do on its drawing parameters based on the inclination angle. So when the inclination angle is set, the circle pie shape goes to an elliptic shape resulting in visual changes in the element pie areas. These areas are transformed purely according to the changes in the pie angle per each element. This angle is calculated as follows:

PieChart4.jpg

So the calculation is:

New Point y' = y + x * sin (α). Using the new location of the point and the center point of the ellipse, the new angle α is calculated.

C++
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;	
	}	
}

Using the Control

This control can be easily used by Creating a CPieChartwnd member on the Client Frame.

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

There are various functions provided to set visual qualities like colors, pie style, fonts and color gradients. By setting up start angle in timers can create rotating effects and setting up incline angle can create inclination effects on 3-D style chart. Also it has some functionality to sort the elements and to reverse back to the added order.

Pie chart element structure

C++
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;
		
	};

The pie chart element has two unique keys. One is a string key which users can define when an element is added. Another one is an integer identifier. This is added by the control it self. The string key has to be unique and not an empty string.

Add element:

The method InsertItem is defined to add elements to the chart. If the insertion was succeeded, it will return a pointer to the element which is the defined data type for the pie_chart_element. The return pointer is in type PIECHARTITEM

Removal of elements can be done using the two keys or using a PIECHARTITEM pointer

C++
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);

Points of Interest

This control provides only basic functionalities which a pie chart control should hold but it will be useful for small MFC applications. Also this might help as a learning material for GDI+ graphics too.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Student
Canada Canada
Prasad Perera is a software developer who's interested in parallel and distributed computing and graphics programming!

Currently he is following his masters in Concordia University, Montreal.

Comments and Discussions

 
QuestionRefresh CPIeChartWnd Pin
Member 1459428512-Dec-21 23:57
Member 1459428512-Dec-21 23:57 
AnswerRe: Refresh CPIeChartWnd Pin
Member 1459428513-Dec-21 3:54
Member 1459428513-Dec-21 3:54 
QuestionDeleting the Created Pie Chart Pin
Member 1378911825-Apr-18 19:43
Member 1378911825-Apr-18 19:43 
QuestionDamn cool Pin
wei-sun-ding15-Jun-12 0:17
wei-sun-ding15-Jun-12 0:17 
Questionnice work Pin
xunonxyz27-Apr-12 18:05
xunonxyz27-Apr-12 18:05 
GeneralMy vote of 5 Pin
Abu Mami22-Jul-11 0:33
Abu Mami22-Jul-11 0:33 
GeneralRe: My vote of 5 Pin
PrasadPerera24-Jul-11 9:36
PrasadPerera24-Jul-11 9:36 
GeneralMy vote of 5 Pin
François Gasnier4-Jan-11 0:56
François Gasnier4-Jan-11 0:56 
GeneralRe: My vote of 5 Pin
PrasadPerera4-Jan-11 7:05
PrasadPerera4-Jan-11 7:05 
GeneralNeed Help regarding the inclination angle calculation Pin
Deepak.M14-Oct-09 6:11
Deepak.M14-Oct-09 6:11 
GeneralRe: Need Help regarding the inclination angle calculation Pin
PrasadPerera17-Oct-09 20:47
PrasadPerera17-Oct-09 20:47 
GeneralImproved 3-D pie chart Pin
PrasadPerera21-Apr-09 10:28
PrasadPerera21-Apr-09 10:28 
Generalwell done Pin
Ahmed Safan21-Apr-09 1:37
professionalAhmed Safan21-Apr-09 1:37 
GeneralRe: well done Pin
PrasadPerera21-Apr-09 10:25
PrasadPerera21-Apr-09 10:25 
Generalsome bugs happens when i change the project from unicode to multi-byte; Pin
yejiang1257-Apr-09 23:14
yejiang1257-Apr-09 23:14 
GeneralRe: some bugs happens when i change the project from unicode to multi-byte; Pin
PrasadPerera8-Apr-09 11:50
PrasadPerera8-Apr-09 11:50 
GeneralBuild Error error C2660 Pin
samal.subhashree30-Mar-09 2:30
samal.subhashree30-Mar-09 2:30 
GeneralRe: Build Error error C2660 Pin
PrasadPerera30-Mar-09 7:18
PrasadPerera30-Mar-09 7:18 
GeneralRe: Build Error error C2660 Pin
samal.subhashree30-Mar-09 17:03
samal.subhashree30-Mar-09 17:03 
GeneralRe: Build Error error C2660 Pin
PrasadPerera31-Mar-09 4:24
PrasadPerera31-Mar-09 4:24 
GeneralRe: Build Error error C2660 Pin
samal.subhashree31-Mar-09 17:25
samal.subhashree31-Mar-09 17:25 
GeneralRe: Build Error error C2660 Pin
PrasadPerera31-Mar-09 17:31
PrasadPerera31-Mar-09 17:31 
GeneralRe: Build Error error C2660 Pin
samal.subhashree1-Apr-09 0:57
samal.subhashree1-Apr-09 0:57 
GeneralRe: Build Error error C2660 Pin
PrasadPerera1-Apr-09 6:32
PrasadPerera1-Apr-09 6:32 
GeneralRe: Build Error error C2660 Pin
samal.subhashree1-Apr-09 21:02
samal.subhashree1-Apr-09 21:02 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.