Page 1 of 1

C++ Windows Charting Library Part 1 Rate Topic: -----

#1 snoopy11  Icon User is offline

  • Engineering ● Software
  • member icon

Reputation: 768
  • View blog
  • Posts: 2,239
  • Joined: 20-March 10

Posted 03 January 2013 - 04:03 AM

Hello,

Lately in the forum we have been getting a lot of requests for charting Libraries
for windows while there are a lot of options for commercial libraries.

Not so much in terms of free ones.

The best I have come across is Dislin by the Max Planck Institute
but the biggest problem is that it draws in its own window or you
can save the chart as a jpg and load it into your app.

It doesnt just draw on a device context and be done with it.

In windows device contexts are very important for displaying on
screen or on a printer and everything else for that matter.


Anyway it struck me that in these cases its time to build your own library
that does things the way you want it to.

After all it's just a little math how hard could it be ?

And that is how I came up with ChartKM.lib and libChartKM.a
which are of course the same Library but for different compilers.

as usual I will post the code for it all and discuss the finer points
in detail later on.

First of all I have made the entire library available but I am going to
split it into 3 parts.

The first Tutorial Part 1 will discuss the Pie Chart making capabilities.

The second Tutorial Part 2 will discuss the Bar Chart making capabilities.

The third Tutorial Part 3 will discuss the Line Graph making capabilities.

Each Tutorial will have an example program to illustrate how to use
the Library. I think this is necessary, while the Library is fairly intuitive
It will be better if there is at least some discussion on how to use it.

As it gets fairly complex pretty quickly.

I may do an extension to the Library to include 3d Pie , Bar and Line charts
If I do this it will be again in 3 parts with the new 3d class being derived from the original class chartObject.

However I will be going back to work soon and time will be again at a premium.

On with the show then....

the library relies on the gdi32 library.

If you are using MSVC and the cl compiler you dont need to do anything
as I have included pragma comments to load the Library.

If you are using MinGW derivatives such as Code::Blocks , Dev C or Eclipse
with the gcc compiler your going to have to link to libgdi32.a

If you are using MSVC select an empty project go to

Project Properties->Configuration Properties->General

In the configuration type select -> Static library(.lib)

Change the character set to -> Use Multi Byte Character Set

as this is not a unicode build however it is not that hard to make it
a unicode build. This has been done so the code will run on as many compiler
suites as possible.

If your using Code::Blocks

Select Empty Project go to
Project Properties -> Build Targets
In Type change it to -> Static library from the drop down list.

For Both add a new .cpp file and call it ChartKM.cpp

For Both add a new .h file and call it ChartKM.h

This is important as we need the names as it appears above so our example
Programs will work correctly.

ChartKM.h


//ChartKM.h
#ifndef CHARTKM_H_INCLUDED
#define CHARTKM_H_INCLUDED
#include <vector>
#include <cstring>
#include <Windows.h>

#ifndef MSVC
#define CLEARTYPE_QUALITY       5
#define CLEARTYPE_NATURAL_QUALITY       6
#endif

using namespace std;


class chartObject
{
private:

    int Total;
	float Units;
	float Degrees;
	vector<float> Percentage;
	float a;
	float x2;
	float y2;
	float ValueItem;
	unsigned FreqBar;
	float LargestBar;
	int szNames;
	int szInfo;
	int szGroup;
	string xName;
	string yName;
	BOOL vLegend;
	string Title;
	vector<string> Names;
	vector <float> Info;
	vector <float> Frequency;
	vector <float> b;
	vector <COLORREF> colors;
	vector <string> GroupName;
	vector <string> ItemName;
	vector <float>  ItemValue;
	vector <float>  LineValueX;
	vector <float>  LineValueY;
	float  LineRangeX;
	float  LineRangeY;
	vector <int> ItemGroup;

public:

	//constructor
	chartObject();

	//destructor
	~chartObject();




	//Draws Pie Chart
	void DrawPie(HDC hdcDst,int x,int y, int szPieX, int szPieY, COLORREF bkcolor);
	//Draws Bar Chart
	void DrawBar(HDC hdcDst,int x, int y, int szBarX, int szBarY, COLORREF bkcolor);
	//Draws Line Chart
	void DrawLine(HDC hdcDst,int x, int y, int szBarX, int szBarY, COLORREF bkcolor);
	//Adds Data for Pie Chart
	void AddData(string Labels, float Data, COLORREF color);
	//Adds Group Data for Bar and Line Charts
	void AddGroupData(string nmGroup, COLORREF color);
	// Adds Item Data for Bar Charts
	void AddItemData(string nmItem, float value, int GroupNo);
	// Adds Item Data for Line Charts
	void AddLineData(string nmItem, float valueX, float valueY);
	// Adds Group and Range Data for Line Charts
	void AddRangeData(float RangeX, float RangeY, int GroupNo);
	// Gets the Totals for Pie Charts
	int GetTotal();
	// Gets the frequency for Pie Charts
	void GetFrequency();
	// Adds a Title to Chart;
	void AddTitle(string szTitle);
	// Adds a Legend to a Pie Chart
	void Legend(BOOL szLegend);
	// Add Titles for x and y axis
	void SetAxisTitles(string szAxisX, string szAxisY);
	// Get Total for Bars
	void GetTotalBar();
    // Get Amount of Groups for Bars
	void GetGroupBar();
	// Get Largest Amount Of Item Data for Bars
	void GetLargestBar();

};





#endif



ChartKM.cpp


#include <Windows.h>
#include <cmath>
#include <iomanip>
#include <sstream>
#ifdef MSVC
#pragma comment(lib, "libGdi32.a")// link to gdi functions
#endif
#define round(a)(int(a+0.5))
//we need this for windows stuff.

#include "ChartKM.h"






chartObject::chartObject()
{

	szNames =0;
	szInfo = 0;
	Total =0;
	Degrees = 0;
	Title = "";
	a = 0;
	x2 = 0;
	y2 = 0;
	szNames = 0;
	szInfo = 0;
	vLegend = TRUE;
	ValueItem = 0;
	Units = 0.0f;
	ItemValue.clear();
	ItemName.clear();
	GroupName.clear();
}

chartObject::~chartObject()
{

}



void chartObject::DrawPie(HDC hdcDst,int x, int y,int szPieX, int szPieY,COLORREF bkcolor)
{


	HPEN pen = CreatePen(PS_SOLID,1,bkcolor);
	HFONT font;
	font = CreateFont(14,0,0,0,FW_DONTCARE,FALSE,FALSE,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS,
		CLIP_DEFAULT_PRECIS,CLEARTYPE_QUALITY, VARIABLE_PITCH,TEXT("Arial Bold"));
	SelectObject(hdcDst, font);
	SelectObject(hdcDst,pen);
	//Draw Circle
	Arc(hdcDst, x,y,szPieX+x,szPieY+y,x,y,x,y);
	//Draw First Line
	MoveToEx(hdcDst,(szPieX/2)+x,(szPieY/2)+y,NULL);
	LineTo(hdcDst,szPieX+x,(szPieY/2)+y);
	//Set Background Color for Text
	SetBkColor(hdcDst,GetPixel(hdcDst,0,0));
	//Gets Totals for Pie Chart
	Total = GetTotal();
	GetFrequency();

	//Draw Lines of arc


	float radius = szPieX/2.0f;
	float yradius = szPieY/2.0f;
	//float y1= 0.0f;
	//Draw segments
	for(int j = 0; j<szInfo; j++)
	{

		HBRUSH hbr = CreateSolidBrush(colors[j]);
		SelectObject(hdcDst,hbr);

		MoveToEx(hdcDst,(szPieX/2)+x,(szPieY/2)+y,NULL);
		Percentage.push_back( Frequency[j]*100 );

		a = Frequency[j]*360;
		if(j==0)
			b.push_back(a);
		else
			b.push_back(b[j-1]+a);
		Degrees= (float)(3.14*b[j])/180;
		x2=(((szPieX/2)+x)+radius*cos(Degrees));
		y2=(((szPieY/2)+y)-yradius*sin(Degrees));
		LineTo(hdcDst,round(x2),round(y2));


		if(b[j]<=30)
		{
			FloodFill(hdcDst,(int)x2-1,(int)y2+5,bkcolor);
		}
		if(b[j]>30&&b[j]<=80)
		{
			FloodFill(hdcDst,(int)x2,(int)y2+4,bkcolor);
		}
		if(b[j]>80&&b[j]<=100)
		{
			FloodFill(hdcDst,(int)x2+2,(int)y2+4,bkcolor);
		}
		if(b[j]>100&&b[j]<=140)
		{
			FloodFill(hdcDst,(int)x2+4,(int)y2+4,bkcolor);
		}

		if(b[j]>140&&b[j]<=160)
		{
			FloodFill(hdcDst,(int)x2+4,(int)y2,bkcolor);
		}

		if(b[j]>160&&b[j]<=220)
		{
			FloodFill(hdcDst,(int)x2+4,(int)y2-4,bkcolor);
		}

		if(b[j]>220&&b[j]<=260)
		{
			FloodFill(hdcDst,(int)x2-2,(int)y2-4,bkcolor);
		}

		if(b[j]>260&&b[j]<=300)
		{
			FloodFill(hdcDst,(int)x2-4,(int)y2-4,bkcolor);
		}

		if(b[j]>300&&b[j]<=340)
		{
			FloodFill(hdcDst,(int)x2-4,(int)y2,bkcolor);
		}
		if(b[j]>340&&b[j]<=361)
		{
			FloodFill(hdcDst,(int)x2-4,(int)y2+4,bkcolor);
		}
	}


	if(Title !="")
	{
		TextOut(hdcDst,(x+szPieX/2)-(Title.length()*2)-Title.size(),y-24,Title.c_str(),Title.size());
	}

	if(vLegend)
	{
		//Draw Legend
		TextOut(hdcDst,x+szPieX+15,y,"Legend",6);
		string buffer;
		stringstream sbuf;
		char buf[20];
		float d = 0.0f;
		for(int i = 0; i<szNames; i++)
		{
			buffer = "";
			sbuf.clear();

			d = Info[i];
			// write strings
			buffer.append(Names[i]);
			buffer.append(" ");
			sbuf << d;
			sbuf >> buf;
			buffer.append(buf);
			TextOut(hdcDst,x+szPieX+40,y+(25*(i+1)),buffer.c_str(),buffer.size());
			//Draw Colored Boxes
			Rectangle(hdcDst,x+szPieX+15,y+(25*(i+1)),x+szPieX+28,y+(25*(i+1)+12));
			RECT rc;
			rc.left= x+szPieX+16;
			rc.top = y+(25*(i+1))+1;
			rc.right = x+szPieX+28-1;
			rc.bottom = y+(25*(i+1)+12)-1;
			HBRUSH hbr = CreateSolidBrush(colors[i]);

			FillRect(hdcDst,&rc,hbr);
		}
	}


	DeleteObject(pen);
	DeleteObject(font);

}

void chartObject::AddData(string Labels, float Data, COLORREF color)
{

	Names.push_back(Labels);
	szNames = Names.size();
	Info.push_back(Data);
	szInfo = Info.size();
	colors.push_back(color);

}

int chartObject::GetTotal()
{
	float Totals=0.0f;

	for(int i = 0; i<szInfo; i++)
	{

		Totals =Totals + Info[i];
	}

	return (int) (Totals+0.5);
}

void chartObject::GetFrequency()
{
	float result;

	for(int i = 0; i<szInfo; i++)
	{
		result = Info[i]/Total;
		Frequency.push_back(result) ;
	}

	return;
}

void chartObject::AddTitle(string szTitle)
{
	Title.assign(szTitle);
}

void chartObject::Legend(BOOL szLegend)
{
	vLegend = szLegend;
}

void chartObject::AddGroupData(string nmGroup, COLORREF color)
{
	GroupName.push_back(nmGroup);
	colors.push_back(color);
}

void chartObject::AddItemData(string nmItem, float value, int GroupNo)
{
	ItemName.push_back(nmItem);
	ItemValue.push_back(value);
	ItemGroup.push_back(GroupNo);
}
void chartObject::SetAxisTitles(string szAxisX, string szAxisY)
{
	xName.assign(szAxisX);
	yName.assign(szAxisY);

}

void chartObject::GetTotalBar()
{
	for(unsigned i = 0; i<ItemValue.size(); i++)
	{
		ValueItem = ValueItem+ItemValue[i];
	}

	ValueItem = ValueItem / (ItemValue.size()+1);
}

void chartObject::GetGroupBar()
{
	FreqBar = GroupName.size();

}

void chartObject::GetLargestBar()
{
	LargestBar  = ItemValue[0];
	for(unsigned i =1; i<ItemValue.size(); i++)
	{
		if(LargestBar<ItemValue[i])
		{
			LargestBar = ItemValue[i];
		}
	}

}


void chartObject::DrawBar(HDC hdcDst,int x, int y, int szBarX, int szBarY, COLORREF bkcolor)
{
	HPEN pen = CreatePen(PS_SOLID,1,bkcolor);
	SelectObject(hdcDst,pen);
	HFONT font;
	//Set Background Color for Text
	SetBkColor(hdcDst,GetPixel(hdcDst,0,0));
	font = CreateFont(14,0,900,900,FW_DONTCARE,FALSE,FALSE,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS,
		CLIP_DEFAULT_PRECIS,CLEARTYPE_QUALITY, VARIABLE_PITCH,TEXT("Arial Bold"));
	SelectObject(hdcDst, font);
	//Draw y axis
	MoveToEx(hdcDst,x+25+14,(szBarY)+y,NULL);
	LineTo(hdcDst,x+25+14,y);
	//Draw x axis
	MoveToEx(hdcDst,x+25+14,(szBarY)+y,NULL);
	LineTo(hdcDst,szBarX+x+25+14,(szBarY)+y);

	TextOut(hdcDst,x,((szBarY+y/2)+(yName.length()/2)),yName.c_str(),yName.length());// Draw y-axis label
	font = CreateFont(14,0,0,0,FW_DONTCARE,FALSE,FALSE,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS,
		CLIP_DEFAULT_PRECIS,CLEARTYPE_QUALITY, VARIABLE_PITCH,TEXT("Arial Bold"));
	SelectObject(hdcDst, font);
	TextOut(hdcDst,((szBarX-x)/2)-(xName.length()/2),y+szBarY+42,xName.c_str(),xName.length());//Draw x-axis label
	TextOut(hdcDst,x+(25),y+szBarY,"0",1);// Draw a zero at origin

	TextOut(hdcDst,((szBarX-x)/2)-(Title.length()/2),y-24,Title.c_str(),Title.length());//Draw Title

	//Get Totals for Bar Chart
	GetTotalBar();
	GetGroupBar();
	GetLargestBar();

	//Draw Graph
	Units = LargestBar/3.0f;
	int graph = szBarY/3;
	for(int i = 0; i<3; i++)
	{
		MoveToEx(hdcDst,x+25+14,(graph*i)+y,NULL);
		LineTo(hdcDst,x+25+14+szBarX+5,(graph*i)+y);
		stringstream  number;
		number << setprecision(3);
		number << (LargestBar-(Units*i));
		string num = "";
		number >> num;
		TextOut(hdcDst,x+25+14+szBarX+7,(graph*i)+y,num.c_str(),num.length());//draw numbers

	}
	for(unsigned i= 0; i<GroupName.size(); i++)
	{
		//draw Legend
		Rectangle(hdcDst,(x+50-12-1),y+szBarY+42+(25*(i+1)-1),(x+50-12-1+12),y+szBarY+42+(25*(i+1)+12));
		RECT rc;
		rc.left= (x+50-12);
		rc.top = y+szBarY+42+(25*(i+1));
		rc.right = (x+50)-2;
		rc.bottom = y+szBarY+42+(25*(i+1)+12)-1;
		HBRUSH hbr = CreateSolidBrush(colors[i]);

		FillRect(hdcDst,&rc,hbr);
		TextOut(hdcDst,(x)+(GroupName[i].length()+50),y+szBarY+42+(25*(i+1)),GroupName[i].c_str(),GroupName[i].length());//Drawlabels


	}
	MoveToEx(hdcDst,x+25+14+szBarX,(y+szBarY),NULL);
	LineTo(hdcDst,x+25+14+szBarX,(y+szBarY+15));
	MoveToEx(hdcDst,x+25+14+szBarX,y,NULL);
	LineTo(hdcDst,x+25+14+szBarX,y+szBarY);
	int amt = (ItemValue.size())/(GroupName.size());
	int sections = (szBarX)/amt;
	int counter = 0;
	int count = 0;
	// Draw Bar Data
	for(unsigned i =0; i<(ItemValue.size()); i=i+GroupName.size())
	{
		counter = (i/GroupName.size())* sections;
		MoveToEx(hdcDst,x+25+14+counter,(y+szBarY),NULL);
		LineTo(hdcDst,x+25+14+counter,(y+szBarY+15));
		TextOut(hdcDst,(x+25+14+counter)+(ItemName[i].length()),(y+szBarY+15),ItemName[i].c_str(),ItemName[i].length());



		for(unsigned j = 0; j<(GroupName.size()); j++)
		{
			RECT rect;
			float result = szBarY-(szBarY*(ItemValue[count]/LargestBar))+y;
			rect.bottom = y+szBarY;
			rect.right =  x+25+14+counter+5+((szBarX/20)*(j+1));
			rect.top =  (LONG)(result);
			rect.left = (x+25+14+counter+5+((szBarX/20)*j));

			HBRUSH hbr = CreateSolidBrush(colors[j]);
			FillRect(hdcDst,&rect,hbr);
			count++;

		}
	}
	DeleteObject(pen);
	DeleteObject(font);
}

void chartObject::AddLineData(string nmItem, float valueX, float valueY)
{
	ItemName.push_back(nmItem);
	LineValueX.push_back(valueX);
	LineValueY.push_back(valueY);
}

void chartObject::AddRangeData(float RangeX, float RangeY, int GroupNo)
{
	LineRangeX = RangeX;
	LineRangeY = RangeY;
	ItemGroup.push_back(GroupNo);

}

void chartObject::DrawLine(HDC hdcDst,int x, int y, int szBarX, int szBarY, COLORREF bkcolor)
{
	HPEN pen = CreatePen(PS_SOLID,1,bkcolor);
	SelectObject(hdcDst,pen);
	HFONT font;
	//Set Background Color for Text
	SetBkColor(hdcDst,GetPixel(hdcDst,0,0));
	font = CreateFont(14,0,900,900,FW_DONTCARE,FALSE,FALSE,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS,
		CLIP_DEFAULT_PRECIS,CLEARTYPE_QUALITY, VARIABLE_PITCH,TEXT("Arial Bold"));
	SelectObject(hdcDst, font);
	//Draw y axis
	MoveToEx(hdcDst,x+50+14,(szBarY)+y,NULL);
	LineTo(hdcDst,x+50+14,y);
	//Draw x axis
	MoveToEx(hdcDst,x+50+14,(szBarY)+y,NULL);
	LineTo(hdcDst,szBarX+x+50+14,(szBarY)+y);
	TextOut(hdcDst,x,((szBarY+y/2)+(yName.length()/2)),yName.c_str(),yName.length());// Draw y-axis label
	// change font
	font = CreateFont(14,0,0,0,FW_DONTCARE,FALSE,FALSE,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS,
		CLIP_DEFAULT_PRECIS,CLEARTYPE_QUALITY, VARIABLE_PITCH,TEXT("Arial Bold"));
	SelectObject(hdcDst, font);
	TextOut(hdcDst,((szBarX+x+50)/2)-(xName.length()/2)+50,y+szBarY+42,xName.c_str(),xName.length());//Draw x-axis label
	TextOut(hdcDst,x+(50),y+szBarY,"0",1);// Draw a zero at origin
	//Draw Range at top of graph y axis
	stringstream ss;
	ss << LineRangeY;
	string number;
	ss >> number;
	TextOut(hdcDst,x+50-(number.length()*4),y-4,number.c_str(),number.length());
	//Draw Range at top of graph x axis
	ss.clear();
	ss << LineRangeX;
	ss >> number;
	TextOut(hdcDst,x+szBarX+50+14-(number.length()*4),y+szBarY+4,number.c_str(),number.length());

	// Add Title
	TextOut(hdcDst,((szBarX+x+50+14)/2)-((Title.length()*4)/2),y-24,Title.c_str(),Title.length());//Draw Title
	// Draw Halfway segment points
	MoveToEx(hdcDst, ((szBarX/2)+x+50+14), y+szBarY,NULL);
	LineTo(hdcDst, ((szBarX/2)+x+50+14), y+szBarY+4);
	ss.clear();
	ss << (LineRangeX/2);
	ss >> number;
	TextOut(hdcDst,(szBarX/2)+50+14+x-((number.length()*4)/2),y+szBarY+6,number.c_str(),number.length());
	MoveToEx(hdcDst,x+50+14,y+(szBarY/2),NULL);
	LineTo(hdcDst, (x+50+14)-4,y+(szBarY/2));
	ss.clear();
	ss << (LineRangeY/2);
	ss >> number;
	TextOut(hdcDst,50+x-((number.length()*4)),y+(szBarY/2),number.c_str(),number.length());
	string container;
	HPEN pen2;
	int x1=x+50+14;
	int y1=y+szBarY;
	//Start Drawing Data inside loops
	for(unsigned k = 0; k < GroupName.size(); k++)
	{
		container.clear();
		container = GroupName[k];
		x1=x+50+14;
		y1=y+szBarY;
		MoveToEx(hdcDst,x1,y1,NULL);
		for(unsigned j=0; j<GroupName.size(); j++)
		{
			if(GroupName[j] == container)
			{
				pen2 = CreatePen(PS_SOLID,1,colors[j]);
				SelectObject(hdcDst, pen2);
			}
		}

		for(unsigned i=0; i<LineValueX.size(); i++)
		{
			if(ItemName[i] == container)
			{
				x1=  (int)(x+50+14+((LineValueX[i]/LineRangeX)*szBarX));
				y1 = (int)(y +szBarY-((LineValueY[i]/LineRangeY)*szBarY));
				LineTo(hdcDst,x1,y1);
			}
			MoveToEx(hdcDst,x1,y1,NULL);
		}//End loop i
	}//End loop k

	HBRUSH hbr = NULL;
	//Draw Legend
	if(vLegend)
	{
		//Draw Legend
		TextOut(hdcDst,x+szBarX+15+50,y,"Legend",6);
		string buffer;


		//float d = 0.0f;
		for(unsigned i = 0; i<GroupName.size(); i++)
		{
			buffer = "";



			// write strings
			buffer.append(GroupName[i]);
			TextOut(hdcDst,x+szBarX+70+15,y+(25*(i+1)),buffer.c_str(),buffer.size());
			//Draw Colored Boxes
			Rectangle(hdcDst,x+szBarX+15+50,y+(25*(i+1)),x+szBarX+28+50,y+(25*(i+1)+12));
			RECT rc;
			rc.left= x+szBarX+16+50;
			rc.top = y+(25*(i+1))+1;
			rc.right = x+szBarX+28-1+50;
			rc.bottom = y+(25*(i+1)+12)-1;
			hbr = CreateSolidBrush(colors[i]);

			FillRect(hdcDst,&rc,hbr);
		}
	}


	SelectObject(hdcDst,pen);
	DeleteObject(pen);
	DeleteObject(pen2);
	DeleteObject(font);
	DeleteObject(hbr);
}





The Explanation Part

I am going to go over the parts which relate to Pie charts.

The Header....

We set up some '#includes'
We define CLEARTYPE_QUALITY to equal 5 if we are not using MSVC..

MinGW does not define this macro for some reason though it is clearly defined in MSVC so you have to do it yourself.

The #ifndef bit just means that if we are not using MSVC define it otherwise
leave it alone. This stops warnings since we are using cross compiler code.

//Draws Pie Chart
	void DrawPie(HDC hdcDst,int x,int y, int szPieX, int szPieY, COLORREF bkcolor);


This is the main bit we are interested in, it draws a Pie Chart at x,y size of szPieX, SzPieY with a color of whatever we like.

//Adds Data for Pie Chart
	void AddData(string Labels, float Data, COLORREF color);


This adds the individual data segments with a specified color.

// Gets the Totals for Pie Charts
	int GetTotal();
	// Gets the frequency for Pie Charts
	void GetFrequency();
	// Adds a Title to Chart;
	void AddTitle(string szTitle);
	// Adds a Legend to a Pie Chart
	void Legend(BOOL szLegend);



GetTotal adds up the totals,
GetFreqeuncy gets the frequency of the data which we will discuss later.
AddTitle and Legend add Titles and draw Legends.

So far so good.

ChartKM.cpp


We set up our constructor to initialise certain values.

void chartObject::DrawPie(HDC hdcDst,int x, int y,int szPieX, int szPieY,COLORREF bkcolor)
{


Draws a Pie Chart onto hdcDst,

HPEN pen = CreatePen(PS_SOLID,1,bkcolor);
	HFONT font;
	font = CreateFont(14,0,0,0,FW_DONTCARE,FALSE,FALSE,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS,
		CLIP_DEFAULT_PRECIS,CLEARTYPE_QUALITY, VARIABLE_PITCH,TEXT("Arial Bold"));
	SelectObject(hdcDst, font);
	SelectObject(hdcDst,pen);
	//Draw Circle
	Arc(hdcDst, x,y,szPieX+x,szPieY+y,x,y,x,y);



We set up a pen with a solid line a line thickness of 1 and a certain color.

Create a font in this case Arial Bold.
Activate the pen and font by selecting them into the hdc.

Draw a circle with SzPieX width and szPieY height
This forms the base of our Pie Chart.

//Draw First Line
	MoveToEx(hdcDst,(szPieX/2)+x,(szPieY/2)+y,NULL);
	LineTo(hdcDst,szPieX+x,(szPieY/2)+y);
	//Set Background Color for Text
	SetBkColor(hdcDst,GetPixel(hdcDst,0,0));
	//Gets Totals for Pie Chart
	Total = GetTotal();
	GetFrequency();


We draw our first line as our starting point, set the background color for text.
We call our functions GetTotal and GetFrequency.

Time to Explain about Pie Charts

The earliest known pie chart is generally credited to William Playfair's Statistical Breviary of 1801.

The pie chart is perhaps the most widely used statistical chart in the business world and the mass media. However, it has been criticized, and some recommend avoiding it, pointing out in particular that it is difficult to compare different sections of a given pie chart, or to compare data across different pie charts. Pie charts can be an effective way of displaying information in some cases, in particular if the intent is to compare the size of a slice with the whole pie, rather than comparing the slices among them. Pie charts work particularly well when the slices represent 25 to 50% of the data, but in general, other charts such as the bar chart or the Line chart, or non-graphical methods such as tables, may be more adapted for representing certain information. (Cite: http://en.wikipedia....wiki/Pie_chart)

So with this in mind we can get on with constructing our pie chart.

//Draw Lines of arc


	float radius = szPieX/2.0f;
	float yradius = szPieY/2.0f;
	//float y1= 0.0f;
	//Draw segments
	for(int j = 0; j<szInfo; j++)
	{

		HBRUSH hbr = CreateSolidBrush(colors[j]);
		SelectObject(hdcDst,hbr);

		MoveToEx(hdcDst,(szPieX/2)+x,(szPieY/2)+y,NULL);
		Percentage.push_back( Frequency[j]*100 );

		a = Frequency[j]*360;
		if(j==0)
			b.push_back(a);
		else
			b.push_back(b[j-1]+a);
		Degrees= (float)(3.14*b[j])/180;
		x2=(((szPieX/2)+x)+radius*cos(Degrees));
		y2=(((szPieY/2)+y)-yradius*sin(Degrees));
		LineTo(hdcDst,round(x2),round(y2));


		if(b[j]<=30)
		{
			FloodFill(hdcDst,(int)x2-1,(int)y2+5,bkcolor);
		}
		if(b[j]>30&&b[j]<=80)
		{
			FloodFill(hdcDst,(int)x2,(int)y2+4,bkcolor);
		}
		if(b[j]>80&&b[j]<=100)
		{
			FloodFill(hdcDst,(int)x2+2,(int)y2+4,bkcolor);
		}
		if(b[j]>100&&b[j]<=140)
		{
			FloodFill(hdcDst,(int)x2+4,(int)y2+4,bkcolor);
		}

		if(b[j]>140&&b[j]<=160)
		{
			FloodFill(hdcDst,(int)x2+4,(int)y2,bkcolor);
		}

		if(b[j]>160&&b[j]<=220)
		{
			FloodFill(hdcDst,(int)x2+4,(int)y2-4,bkcolor);
		}

		if(b[j]>220&&b[j]<=260)
		{
			FloodFill(hdcDst,(int)x2-2,(int)y2-4,bkcolor);
		}

		if(b[j]>260&&b[j]<=300)
		{
			FloodFill(hdcDst,(int)x2-4,(int)y2-4,bkcolor);
		}

		if(b[j]>300&&b[j]<=340)
		{
			FloodFill(hdcDst,(int)x2-4,(int)y2,bkcolor);
		}
		if(b[j]>340&&b[j]<=361)
		{
			FloodFill(hdcDst,(int)x2-4,(int)y2+4,bkcolor);
		}
	}



We get the xradius and yradius of the circle we then do a lot of math pertaining to angles to construct the segments according to data size.
We then have series of if statements to fill the Pie Chart with the FloodFill statement.

if(Title !="")
	{
		TextOut(hdcDst,(x+szPieX/2)-(Title.length()*2)-Title.size(),y-24,Title.c_str(),Title.size());
	}

	if(vLegend)
	{
		//Draw Legend
		TextOut(hdcDst,x+szPieX+15,y,"Legend",6);
		string buffer;
		stringstream sbuf;
		char buf[20];
		float d = 0.0f;
		for(int i = 0; i<szNames; i++)
		{
			buffer = "";
			sbuf.clear();

			d = Info[i];
			// write strings
			buffer.append(Names[i]);
			buffer.append(" ");
			sbuf << d;
			sbuf >> buf;
			buffer.append(buf);
			TextOut(hdcDst,x+szPieX+40,y+(25*(i+1)),buffer.c_str(),buffer.size());
			//Draw Colored Boxes
			Rectangle(hdcDst,x+szPieX+15,y+(25*(i+1)),x+szPieX+28,y+(25*(i+1)+12));
			RECT rc;
			rc.left= x+szPieX+16;
			rc.top = y+(25*(i+1))+1;
			rc.right = x+szPieX+28-1;
			rc.bottom = y+(25*(i+1)+12)-1;
			HBRUSH hbr = CreateSolidBrush(colors[i]);

			FillRect(hdcDst,&rc,hbr);
		}
	}


	DeleteObject(pen);
	DeleteObject(font);

}



We then draw the Title and Legend if required to finish off.

We can now go on to discuss an example program that uses the Library.


#include <Windows.h>
#include "ChartKM.h"

#pragma comment(lib, "ChartKM.lib")


chartObject Chart;


/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
char szClassName[ ] = "PieChart";

int WINAPI WinMain (HINSTANCE hThisInstance,
	HINSTANCE hPrevInstance,
	LPSTR lpszArgument,
	int nCmdShow)
{
	HWND hwnd;               /* This is the handle for our window */
	MSG messages;            /* Here messages to the application are saved */
	WNDCLASSEX wincl;        /* Data structure for the windowclass */

	/* The Window structure */
	wincl.hInstance = hThisInstance;
	wincl.lpszClassName = szClassName;
	wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
	wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
	wincl.cbSize = sizeof (WNDCLASSEX);

	/* Use default icon and mouse-pointer */
	wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
	wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
	wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
	wincl.lpszMenuName = NULL;                 /* No menu */
	wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
	wincl.cbWndExtra = 0;                      /* structure or the window instance */
	/* Use Windows's default colour as the background of the window */
	wincl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);

	/* Register the window class, and if it fails quit the program */
	if (!RegisterClassEx (&wincl))
		return 0;

	/* The class is registered, let's create the program*/
	hwnd = CreateWindowEx (
		0,                   /* Extended possibilites for variation */
		szClassName,         /* Classname */
		"Charting Demo Application.",       /* Title Text */
		WS_OVERLAPPEDWINDOW, /* default window */
		CW_USEDEFAULT,       /* Windows decides the position */
		CW_USEDEFAULT,       /* where the window ends up on the screen */
		1100,                 /* The programs width */
		550,                 /* and height in pixels */
		HWND_DESKTOP,        /* The window is a child-window to desktop */
		NULL,                /* No menu */
		hThisInstance,       /* Program Instance handler */
		NULL                 /* No Window Creation data */
		);

	/* Make the window visible on the screen */
	ShowWindow (hwnd, nCmdShow);

	/* Run the message loop. It will run until GetMessage() returns 0 */
	while (GetMessage (&messages, NULL, 0, 0))
	{
		/* Translate virtual-key messages into character messages */
		TranslateMessage(&messages);
		/* Send message to WindowProcedure */
		DispatchMessage(&messages);
	}

	/* The program return-value is 0 - The value that PostQuitMessage() gave */
	return messages.wParam;
}


/*  This function is called by the Windows function DispatchMessage()  */

LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{


	switch (message)                  /* handle the messages */
	{
	case WM_CREATE:
		{


			Chart.AddData("Cars",140.0f, RGB(255,0,0));
			Chart.AddData("Vans",40.0f,RGB(0,0,255));
			Chart.AddData("MotorBikes",70.0f,RGB(0,255,0));
			Chart.AddData("Bicycles",25.0f,RGB(0,0,128));
			Chart.AddData("Buses",5.0f,RGB(255,242,0));
			Chart.AddData("Trucks",25.0f,RGB(163,73,164));
			Chart.AddTitle("Traffic Survey High Street, Kirby 2003");
			
			InvalidateRect(hwnd,NULL,FALSE);
			return 0;
		}

	case WM_SIZE:
		{
			InvalidateRect(hwnd,NULL,FALSE);
			return 0;
		}

	case WM_PAINT:
		{
			PAINTSTRUCT ps;
			HDC hdc = BeginPaint(hwnd,&ps);

			// Draw the Chart onto the screen dc
			Chart.DrawPie(hdc,30,30,200,200,RGB(0,0,0));


			EndPaint(hwnd,&ps);

			return 0;
		}


	case WM_DESTROY:
		PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
		break;
	default:                      /* for messages that we don't deal with */
		return DefWindowProc (hwnd, message, wParam, lParam);
	}

	return 0;
}





Remember to link to the library ChartKM.lib.

and copy ChartKM.h to your include folder.

We create a new chartObject called Chart.

In WM_CREATE we add the data and set the title

In WM_PAINT we draw the chart.

Well that is enough for now, till next time.

Take Care

Snoopy.

Is This A Good Question/Topic? 1
  • +

Page 1 of 1