Introduction

The initial idea was to create a utility class / class library that could be used for drawing 3-D pie charts. At first, this seemed quite simple, since there is a DrawPie method already available in the Graphics class. This method accepts start angle and sweep angle as arguments, so it should not be a problem to use it: just sum up all values and then calculate the portion for each one, converting it to sweep angle for the corresponding pie slice. And this works for a circular chart. However, if you want to add 3-D perspective (i.e., if a chart is drawn with an ellipse shape) this approach will result in an impression of varying values, as demonstrated in the figure below: pie slices at the left and right side appear larger than those up and down, although all of them have the same sweep angle.

Instead of a direct insertion of the sweep angle, the parametric equation for ellipse has to be used.

The above problem thus solved, adding a real 3-D look to the chart requires only one additional step: drawing a cylinder brink. However, if you want to draw pie slices displaced from the common center, then the slice cut sides become visible and have to be drawn too. Since these sides may partially overlap, the order of drawing is of utmost importance to obtain the correct 3-D appearance.

Background

Drawing

First, note that the coordinate system as shown in the figure below is used:

The parametric equation of ellipse has a form of:

x = a * cos(t)

y = b * sin(t)

where a and b are major and minor semi-axis, respectively, and t is a variable parameter. Note that t does not have direct interpretation in terms of an angle but (as anyone familiar with trigonometry will conclude from the figure below) can be related to the polar angle from the ellipse center as:

angle = tan-1(y/x) = tan-1((b * sin(t)) / (a * cos(t)))

Consequently, when initializing individual shapes for rendering, the corresponding start and sweep angles have to be transformed by the following method:

In the above method, m_boundingRectangle is the boundary rectangle of the ellipse from which the pie shape is cut out. The width and height of this rectangle are equal to the major and minor axes of the ellipse, respectively.

When drawing a 3-D pie slice (with some finite height), it is necessary to draw the slice cut sides as well as the outer periphery of the cylinder from which the slice is cut out. For this, the center point and the points on the pie slice periphery (m_center, m_pointStart and m_pointEnd private members of the PieSlice class) and their corresponding siblings on the slice bottom side have to be calculated first. These points are used to constitute GraphicsPaths: paths for cut sides consist of four lines, while the path for the cylinder periphery consists of two vertical lines and two arcs.

It is worthy to note that the slice side corresponding to the start angle is visible only when the start angle is larger than 90 and less than 270 degrees, while the side corresponding to the end angle is visible only when the angle is between 270 and 90 degrees. Also, the cylinder brink is visible only for angles between 0 and 180 degrees.

As already mentioned, the drawing order is important when the chart contains several slices displaced from the center. The pie shape that is crossing the 270 degrees boundary must be drawn first because it may be (partially) covered by another pie slice. The slice closest to the 270 degrees axis (regardless if it is from the left or the right side) is drawn next, the procedure being repeated for the slices to follow.

To achieve this order, the pie slices are stored into an array starting with the shape that crosses the 270 degrees axis. Consequently, neighboring shapes will be placed in the second and in the last position of the array. Therefore, the search for the next shape to be drawn goes from the start and from the end of the list simultaneously, selecting the shape which is closer to the 270 degrees axis to be drawn first.

Pie slices crossing the 270 degrees axis have a unique feature: both cut sides (corresponding to the start and the end angle) are visible - c.f. figure below left. Moreover, if both the start and the end angles are within 0 and 180 degrees range, the slice will have its cylinder brink consisting of two parts (figure below right). To handle this, the slice is split into two sub-slices in the course of drawing, with the common top side.

This splitting comes into play with drawing charts like the one shown below: if the blue slice was drawn first and completely, the green slice would completely overlap it, resulting in an irregular illusion. The numbers on each shape indicate the correct order of drawing.

Hit Testing

When the first version of the article was published, several readers suggested to add tool tips and pie slice highlighting when the mouse is over it. This feature has been implemented in version 1.1.

The main problem was to find and implement the algorithm that searches for the pie slice currently under the mouse cursor. The search order for the entire chart is the reverse of the drawing order, starting from the foremost slice. However, processing of individual slices is cumbersome because of their irregular shapes.

To test if a pie is hit, the pie slice shape has to be decomposed into several surfaces as shown on the figure below, and each of these surfaces is tested if it contains the hit point.

Note that the cylinder outer periphery hitting is not tested directly (in fact, I have no idea how it could be done simply), but is covered by testing the top (1) and the bottom (2) pie surfaces and the quadrilateral defined by the periphery points (3).

Hit testing for the top and bottom slice surfaces is straightforward - the distance of the point from the center of the ellipse is compared to the ellipse radius for the corresponding angle:

For quadrilaterals, a well know algorithm for testing if a point is inside a polygon is used: a ray is traced from the point to test and the number of intersections of this ray with the polygon is counted. If the number is odd, the point is inside the polygon, if it is even, the point is outside (c.f. figure below).

Consequently, all polygon sections are passed, counting intersections with the ray:

Using the code

The PieChart solution contains three classes: PieSlice, PieChart3D and PieChartControl (derived from the System.Windows.Forms.Panel control). The PieSlice class provides all the functionality required to draw a 3-D pie slice with given a start and sweep angle, color, height and shadow style.

The PieChart3D represents the entire chart. There are several constructors available, all of them taking a bounding rectangle and an array of values. Some constructors also accept:

array of colors used to represent values,

array of slice displacements,

slice thickness.

Slice displacement is expressed as a ratio of the slice "depth" and ellipse radius; minimum value of 0 means that there is no displacement, while 1 (largest allowed value) means that the shape is completely taken out of the ellipse.

Slice thickness represents the ratio of pie slice thickness and the ellipse's vertical, minor axis; largest allowed value being 0.5.

It is also possible to set any of the above parameters using public properties. Note that if the number of colors provided is less than the number of values, colors will be re-used. Similarly, if the number of displacements is exhausted, the last displacement will be used for all the remaining pie slices.

There are also additional public properties that can be set:

Texts,

Font,

ForeColor,

ShadowStyle,

EdgeColorType,

EdgeLineWidth,

InitialAngle,

FitToBoundingRectangle.

The meaning of all these properties and their possible values can be seen from the demo sample. The Texts property is an array of strings that are displayed on corresponding slices. Default implementation places text near the center of the slice's top, but the user may override the PlaceTexts method of the PieChart3D class to implement her/his own placement logic. Font and ForeColor properties define the font and the color that is used to display these texts.

The PieChart3D class can be used for printing: it is only necessary to initialize the chart object and then call its Draw method, providing the corresponding Graphics object:

publicvoid Draw(Graphics graphics) { ... }

To display the chart on the screen, PieChartControl is more appropriate: it encapsulates the chart into a panel that is responsible for chart (re)painting. The user only has to place it on the form and set the required values. For example:

Note that PieChartControl has an additional ToolTips property accepting an array of strings that are displayed when the corresponding pie slice is hit. If any string in this array is empty, the corresponding value will be displayed instead.

Points of Interest

To achieve a better 3-D perspective, I have introduced a "gradual" shadow, changing the brightness of the slice cut sides depending on their angles. To achieve this effect on the cylinder brink, a gradient fill is used for painting the periphery. I used an empirical formula for this. However, the user may change this by deriving a class from PieSlice and overriding the CreateBrushForSide and the CreateBrushForPeriphery methods in the PieSlice class, implementing her/his own logic.

Similarly, a user can override the CreatePieSliceHighlighted method in the PieChart class; the default implementation draws the highlighted pie slice in a slightly lighter color.

From version 1.4, a simple pie chart printing is included in the demo program; the user just has to click the Print button on the demo form. The printing code is provided in the PrintChart class of the Test project.

Copyright notice

You are free to use this code and the accompanying DLL. Please include a reference to this web page in the list of credits.

History

June 1, 2004 - Initial submission of the article.

June 22, 2004 - ver. 1.1: Tool tip and pie slice highlighting added. Also (credits for these go to Andreas Krohn), flickering on resize has been removed and assertion failure bug when control is made very small has been fixed.

November 11, 2004 - ver. 1.2: bug fixes.

March 21, 2005 - ver. 1.3: color transparency support added (thanks to Bogdan Pietroiu for this suggestion), and description text for each slice (as suggested by ccarlinx) added.

November 10, 2005 - ver. 1.4: Pie chart control crashing for angle of 270 degrees bug (found by gabbyr and rafabgood) and "Crash when all slices have 0 value" has been fixed. A simple pie chart printing sample has been included into the demo project (please note that the quality of the printout depends on the capabilities of the printer and is usually far behind the screen display quality).

March 12, 2006 - ver. 1.5: Control crashing bug (as noticed by jianingy) has been fixed.

Share

About the Author

Graduated at the Faculty of Electrical Engineering and Computing, University of Zagreb (Croatia) and received M.Sc. degree in electronics. For several years he was research and lecturing assistant in the fields of solid state electronics and electronic circuits, published several scientific and professional papers, as well as a book "Physics of Semiconductor Devices - Solved Problems with Theory" (in Croatian).During that work he gained interest in C++ programming language and have co-written "C++ Demystified" (in Croatian), 1st edition published in 1997, 2nd in 2001, 3rd in 2010, 4th in 2014.After book publication, completely switched to software development, programming mostly in C++ and in C#.In 2016 coauthored the book "Python for Curious" (in Croatian).

I couldnt find out, how to use the class used in this tutorial. I didnt know, what refference should I add or what to do. Now I "included" dll from the demo and the chart is availible. Is that the right way of using it, I mean how author meaned it? Or its just "my way that works".

I am new to Windows GUI programming, and it's been some time since I last programmed on C++. I am getting re-started with a pie chart project, and I downloaded your sources to take a look. However, at compilation I get the errors here below.

I downloaded and copied all the .h files to the include directory. When I saw that all problems were related to CCmdTarget.h, I googled the problem and found that several people had similar issues, but I couldn't find a fixed file. And trying to fix it "by hand" is practically impossible! (I tried setting msgEntries to [50] to see if that made the miracle, but there are too many errors...).

Help welcome! (I assume it's a good file, I just can't find where to get it).Regards,

Great Control!I was wondering if you could explain how I would go about using this dynamically if you do not know how many pie slices you have before you begin? I would like to use it to show the break down of a folder structure by size.Thanks

Okay, the SVN repository now has a README.txt which tells a little about the project and there's a TestPieChart project that shows some basic usage. Hopefully it's enough to get people started. Furthermore, there's plenty of functionality that doesn't work correctly. I'm perfectly willing to incorporate bug fixes, etc.

I've just moved the piechart3d stuff from http://ender.dreamhosters.com/piechart3d to http://ender.dreamhosters.com/src/piechart3d. The old address will now reference a Trac project, which I'm putting together, that should eventually hold some information about the project and make it a little easier for people to talk about directly.

I totally agree with your comment, once I started using this project and realized some of the oddities about it. I looked in to the code and saw the strange things you mentioned. So, I went through and quickly cleaned up the things that bothered me most. It's far from great now, but I think it's a little more usable.

The author of the original article and feel free to contact me for an updated version of the project to see the changes. Readers can contact me also and I'll send them the code. Too bad there's not an easy way to submit code updates to this site...

Quick list of fixes off the top of my head: - Properties that were originally stored in arrays at a higher level are now in the PieSlice struct such as Value, SurfaceColor, Offset, etc. - The PieControl now exposes the PieChart and the PieChart now exposes the PieSlices - The "GetSelectedPieSliceIndex" now returns a valuable index into the PieSlices array, so you can now correctly query, say, which slice you've clicked on - A zero-sized slice will not draw text - A pie chart with only zero-sized slices will now draw - no more memory allocations when pie slices are changed at all - New pie slices are only created

I think that's most of it. Other things are likely broken, but it works enough for what I want it for

I cannot disagree with both posters . The code would need a major refactoring, adding some features that would make it easier to use and extend. I suppose adding Click, MouseEnter and MouseLeave events would make the control more user friendly.

Actually, when I was initially writing the code, I was mainly focused on drawing algorithm, neglecting extensibility and readability.

Well, you earn my respect for not getting defensive about your code. We all write quick code focused on one thing and leaving out another. Cheap, Fast, Good - pick 2, and yours was free .

Anyways, you might want to consider making a Googlecode project, or some other similar source hosting solution - then we could all post improvements to the code and get it all the way up to snuff. It's a cool little widget with lots of promise. Just needs some cleanup is all.

At this point, I've tried to send the code to a couple of people and not received replies. I'm not sure what's happening, but I would guess that the emails aren't getting to people. Because of this, I've decided to put the code somewhere more publicly accessible. You can check it out with subversion from

http://ender.dreamhosters.com/piechart3d

I'm happy to patch it, or even give write access to anyone who shows a lot of aptitude in updating the code. Feel free to contact me about it at

I'm new to C# and have been trying unsuccessfully for the past couple hours to get your updated code to work for me. Would you mind posting a quick block of code that would show me how to generate a chart with a couple segments. I dont need something with all the properties available. I'm sure if I can just get it to appear I can take it from there.

I was originally looking for a simple Pie Chart Control that I might have to adapt to do transparancy, segment offset, angle alignment etc. I was just being lazy and didn't want to have to put in the guts of the drawing fundamentals.

I was extremely surprised to find this gem of an app, it does exactly what I need, a big thank you, and all credit you get here is greatly deserved.

I did not see a property/method for altering the 'perspective' angle. I sifted through the PieChart class and could not put my finger on a constant or something similar. Could you point me in the right direction if this is possible?

However there is one little thing I would like to ask you about: Tooltips. When I included your 3D chart into my application, everything works fine, except for tooltips, which appear to be flickering all the time. That isn't happening in the demo project available here, so I am quite confused since I used the tooltips in exactly the same way - [3dchart].Tooltips = ...

Is there a separate setting available for tooltips? I must admit that I cannot find the reason for the flickering on my own, so do you have any idea why this may be occurring?