Level Extreme platform
Subscription
Corporate profile
Products & Services
Support
Legal
Français
Articles
Search: 

Advanced pie charts with GDI+
Cesar Chalom, June 1, 2006
Almost any application makes use of charts to display information to their users. Graphs are used for many different reasons, and can be found all over. We see them in the newspapers, magazines, and on television because they help us to communicate information. One of the commonest types of charts i...
Summary
Almost any application makes use of charts to display information to their users. Graphs are used for many different reasons, and can be found all over. We see them in the newspapers, magazines, and on television because they help us to communicate information. One of the commonest types of charts is the PIE style. A pie chart is a circle graph divided into pieces, each displaying the size of some related piece of information. Pie charts are used to display the sizes of parts that make up some whole.
Description
Almost any application makes use of charts to display information to their users. Graphs are used for many different reasons, and can be found all over. We see them in the newspapers, magazines, and on television because they help us to communicate information. One of the commonest types of charts is the PIE style. A pie chart is a circle graph divided into pieces, each displaying the size of some related piece of information. Pie charts are used to display the sizes of parts that make up some whole.

My target in this article is to show step by step how to create more interesting pie charts, allowing detaching, or separating a slice from the pie, writing legends inside the slices and creating cool 3D effects. For this task, we will make use of most of the drawing functions from the graphics class of GDI+. No Active-X is required, just the class _gdiplus.vcx that ships with VFP9.

The circle of a pie graph represents 100%. Each portion that takes up space within the circle stands for a part of that 100%. In this way, it is possible to see how something is divided among different groups.

The main functions used to create pies are: DrawPie, FillPie and DrawArc. The most important parameters for these functions are :

Parameter Description
startAngle Real number that specifies the angle, in degrees, between the x-axis and the starting point of the arc that defines the pie. A positive value specifies clockwise rotation.
sweepAngle Real number that specifies the angle, in degrees, between the starting and ending points of the arc that defines the pie. A positive value specifies clockwise rotation.

The illustration below shows the starting point for drawing a slice, the angle zero. To draw the slice from the “2nd quadrant”, we need to create a solid brush, assign the yellow color to it, then call the fillpie function, using the StartAngle of zero and SweepAngle of 90.

To create any chart, the main task is to select the correct information. For all the examples that I’ll show, I’ll use the same data, provided by the cursor “sales” below:

CREATE CURSOR sales (nValue n(8,2), cLegend c(6), lDetach l, nColor i, cSliceText c(10))
INSERT INTO sales VALUES (250, "JAN", .F., RGB(0,0,255)   ,"")
INSERT INTO sales VALUES (128, "FEB", .T., RGB(0,255,255) ,"")
INSERT INTO sales VALUES ( 90, "MAR", .F., RGB(255,0,255) ,"MAR")
INSERT INTO sales VALUES (330, "APR", .F., RGB(255,160,60),"")
INSERT INTO sales VALUES (250, "MAY", .T., RGB(255,255,0) ,"")
INSERT INTO sales VALUES (150, "JUN", .F., RGB(0,255,64)  ,"JUN")
INSERT INTO sales VALUES (180, "JUL", .F., RGB(255,0,0)   ,"US$ 180")
INSERT INTO sales VALUES (100, "AUG", .T., RGB(128,128,128),"")
SELECT sales
The fields above will contain some important information for the creation of the pie:

Field Type Description
nValue numeric value or percentage
cLegend character the legend corresponding to this slice of pie
lDetach logical .T. if the slice must be shown detached
nColor integer RGB color of the slice
cSliceText character the text to be drawn inside the slice

Basic Pie Charts

The example below creates the simplest pie chart using GDI+.

*!* PROGRAM : UT_SIMPLEPIE.PRG
*!* AUTHOR  : CESAR CHALOM
*!* Creates simple pie chart based in the contents of a cursor
*!* Switch llBW from .F. to .T to get a B&W output

CREATE CURSOR sales (nValue N(8,2), cLegend c(6), lDetach l, nColor i, cSliceText c(10))
INSERT INTO sales VALUES (250, "JAN", .F., RGB(0,0,255)   ,"")
INSERT INTO sales VALUES (128, "FEB", .T., RGB(0,255,255) ,"")
INSERT INTO sales VALUES ( 90, "MAR", .F., RGB(255,0,255) ,"MAR")
INSERT INTO sales VALUES (330, "APR", .F., RGB(255,160,60),"")
INSERT INTO sales VALUES (250, "MAY", .T., RGB(255,255,0) ,"")
INSERT INTO sales VALUES (150, "JUN", .F., RGB(0,255,64)  ,"JUN")
INSERT INTO sales VALUES (180, "JUL", .F., RGB(255,0,0)   ,"US$ 180")
INSERT INTO sales VALUES (100, "AUG", .T., RGB(128,128,128),"")

SELECT sales

LOCAL lnTotal, lnStart, lnSweep, llBW

llBW = .F.
*!* llBW = .T. && Uncomment this line to create Black and White charts 

CALCULATE SUM(sales.nValue) TO lnTotal

wImg = 200 && Width of image
hImg = 200 && Height of image

*!* Create empty Bitmap
LOCAL loBitmap AS GpBitmap OF ffc/_gdiplus.vcx
loBitmap = NEWOBJECT("GpBitmap", HOME() + "ffc/_gdiplus.vcx")
loBitmap.CREATE(wImg, hImg)

LOCAL loGraph AS GpGraphics OF HOME() + ffc/_gdiplus.vcx
loGraph = NEWOBJECT('GpGraphics', HOME() + "ffc/_gdiplus.vcx")
loGraph.CreatefromImage(loBitmap)
loGraph.Clear(0xFFFFFFFF) && White BackGround

*!* Create the drawing objects
LOCAL loLineColor AS GpColor OF HOME() + ffc/_gdiplus.vcx ;
   , loBrushColor AS GpColor OF HOME() + ffc/_gdiplus.vcx ;
   , loPen AS GpPen OF HOME() + ffc/_gdiplus.vcx ;
   , loBrush AS GpSolidBrush OF HOME() + ffc/_gdiplus.vcx ;
   , loBWBrush as GpHatchBrush of HOME() + ffc/_gdiplus.vcx ;
   , loBounds AS GpRectangle OF HOME() + ffc/_gdiplus.vcx

loBounds = NEWOBJECT('GpRectangle', HOME() + 'ffc/_gdiplus.vcx','', 0, 0, wImg-1, hImg-1)
loLineColor = NEWOBJECT('GpColor', HOME() + 'ffc/_gdiplus.vcx','', 0,0,0 ) && black
loBrushColor = NEWOBJECT('GpColor', HOME() + 'ffc/_gdiplus.vcx')
loBWBrush = newobject('GpHatchBrush', HOME() + 'ffc/_gdiplus.vcx','' )
loPen = NEWOBJECT('GpPen', HOME() + 'ffc/_gdiplus.vcx','', loLineColor ) && 1-pixel-wide pen
loBrush = NEWOBJECT('GpSolidBrush', HOME() + 'ffc/_gdiplus.vcx','' )
loBrush.CREATE()

* Draw the pie
lnStart = 0

SCAN
   lnSweep = sales.nValue / lnTotal * 360
   IF llBW

      * tnStyle, tvForeColor, tvBackColor
      loBWBrush.Create(RECNO()*2, loLineColor, 0xFFFFFFFF)
      loGraph.FillPie(loBWBrush, loBounds, lnStart, lnSweep) && draw the slice
      loGraph.DrawPie(loPen, loBounds, lnStart, lnSweep) && draw the black contour
   ELSE
      loBrushColor.FoxRGB = sales.nColor
      loBrush.BrushColor = loBrushColor
      loGraph.FillPie(loBrush, loBounds, lnStart, lnSweep) && draw the slice
      loGraph.DrawPie(loPen, loBounds, lnStart, lnSweep) && draw the black contour
   ENDIF
   lnStart = lnStart + lnSweep
ENDSCAN

*!* Save Image
loBitmap.SavetoFile("PieBasic.png","image/png")
*!* Show image in MS Internet Explorer
RUN /N explorer.EXE PieBasic.png
And the output will be:

The methods DrawPie and FillPie draw the slices inside a rectangle. In the original example, I used a 200 x 200 pixel image. The width was defined in the variable wImg. If you change it, for instance, to 350, the result will be:

Using HatchBrushes

Sometimes the charts created need to be printed in monochrome. This is really easy to do, once GDI+ brings some different kind of brushes. In the above example, I used the SolidBrush, which fills the object with only one color.

Please uncomment line 22 at the previous code,

*!* llBW = .T. && Uncomment this line to create Black and White charts 
and run it again, to create this output:

Visit this link from MSDN to better understand what effects can be obtained with the use of HatchBrushes: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdicpp/GDIPlus/GDIPlusReference/Enumerations/HatchStyle.asp

There are more than 50 different HatchBrushes that we can use. In the prior example, note that I used RECNO() * 2 to select the brush style. You can change it and choose the better results that better fit your needs.

The FillPie method draws the slice, and the DrawPie method is used to draw the contour of the slice in Black.

Know the right coordinates

To create more interesting charts, we need to know exactly the coordinates of every slice in the graphic. This is really not complicated, and these coordinates can be obtained with some basic trigonometry calculations.

To draw any circle, there are two main parameters: 1 point with the coordinates of the center of the circle (x,y) and the Radius. Having this, it is very simple to calculate the coordinates of any point in the circumference border.

For this purpose, I'll need to make you remember the concepts of sine and cosine :

In any right angled triangle, for any angle:

Sine of the angle = length of the opposite side / length of the hypotenuse

Cosine of the angle = length of the adjacent side / length of the hypotenuse

Hypotenuse of a right angled triangle is the longest side, which is the one opposite the right angle. The adjacent side is the side which is between the angle in question and the right angle. The opposite side is opposite the angle in question.

So, imagine a right angled triangle inside a circumference, like in the picture below.

Sine of angle = opposite side (Height or Y) / hypotenuse (Radius) !!!

Height = Sine of angle * Radius

CoSine of angle = adjacent side (Width or X) / hypotenuse (radius) !!!

Width = CoSine of angle * Radius

Now we can create a loop starting from angle 0 (zero) and finishing at angle 360 degrees. At each step we can calculate the position of every point of the circle!

Detaching slices

In many cases we need to draw one or more slice separated from the rest of the pie in order to emphasize an important piece of information. Using the trigonometric concepts shown in the previous topic, we can calculate the right position to draw a specific slice in order to make it appear detached or separated from the pie.

All we have to do is to dislocate the center of the pie away, draw the slice and then move the center of the pie to its original position.

The next example creates a pie with detached slices. It has some few modifications:

   lnDetachPixels = 30 && the quantity of pixels to dislocate away from the pie
The whole image is dislocated in lnDetachPixels to make space for the detached slices to fit in the image
   lnX = 0 + lnDetachPixels 
   lnY = 0 + lnDetachPixels 
   lnW = wImg - 1 - (2*lnDetachPixels)
   lnH = hImg - 1 - (2*lnDetachPixels)
The new position of the slice to be detached is calculated here, before drawing:
   lnDetachAngle = 360 - (lnStart + (lnSweep / 2))
   x1 = lnX + (COS(DTOR(lnDetachAngle)) * lnDetachPixels)
   y1 = lnY - (SIN(DTOR(lnDetachAngle)) * lnDetachPixels)
Run the code below to see the desired results:
*!* PROGRAM : UT_DETACHPIE.PRG
*!* AUTHOR  : CESAR CHALOM
*!* Creates pie chart based in the contents of a cursor
*!* Switch llBW from .F. to .T to get a B&W output
*!* Detaches slice if field lDetach is .T.

CREATE CURSOR sales (nValue N(8,2), cLegend c(6), lDetach l, nColor i, cSliceText c(10))
INSERT INTO sales VALUES (250, "JAN", .F., RGB(0,0,255)   ,"")
INSERT INTO sales VALUES (128, "FEB", .T., RGB(0,255,255) ,"")
INSERT INTO sales VALUES ( 90, "MAR", .F., RGB(255,0,255) ,"MAR")
INSERT INTO sales VALUES (330, "APR", .F., RGB(255,160,60),"")
INSERT INTO sales VALUES (250, "MAY", .T., RGB(255,255,0) ,"")
INSERT INTO sales VALUES (150, "JUN", .F., RGB(0,255,64)  ,"JUN")
INSERT INTO sales VALUES (180, "JUL", .F., RGB(255,0,0)   ,"US$ 180")
INSERT INTO sales VALUES (100, "AUG", .T., RGB(128,128,128),"")

SELECT sales

LOCAL lnTotal, lnStart, lnSweep, llBW, lnDetachPixels

lnDetachPixels = 30

llBW = .F.
*!* llBW = .T. && Uncomment this line to create Black and White charts 

CALCULATE SUM(sales.nValue) TO lnTotal

wImg = 300 && Width of image
hImg = 300 && Height of image

*!* Create empty Bitmap
LOCAL loBitmap AS GpBitmap OF ffc/_gdiplus.vcx
loBitmap = NEWOBJECT("GpBitmap", HOME() + "ffc/_gdiplus.vcx")
loBitmap.CREATE(wImg, hImg)

LOCAL loGraph AS GpGraphics OF HOME() + ffc/_gdiplus.vcx
loGraph = NEWOBJECT('GpGraphics', HOME() + "ffc/_gdiplus.vcx")
loGraph.CreatefromImage(loBitmap)
loGraph.Clear(0xFFFFFFFF) && White BackGround

*!* Create the drawing objects
LOCAL loLineColor AS GpColor OF HOME() + ffc/_gdiplus.vcx ;
   , loBrushColor AS GpColor OF HOME() + ffc/_gdiplus.vcx ;
   , loPen AS GpPen OF HOME() + ffc/_gdiplus.vcx ;
   , loBrush AS GpSolidBrush OF HOME() + ffc/_gdiplus.vcx ;
   , loBWBrush as GpHatchBrush of HOME() + ffc/_gdiplus.vcx

loLineColor = NEWOBJECT('GpColor', HOME() + 'ffc/_gdiplus.vcx','', 0,0,0 ) && black
loBrushColor = NEWOBJECT('GpColor', HOME() + 'ffc/_gdiplus.vcx')
loBWBrush = newobject('GpHatchBrush', HOME() + 'ffc/_gdiplus.vcx','' )
loPen = NEWOBJECT('GpPen', HOME() + 'ffc/_gdiplus.vcx','', loLineColor ) && 1-pixel-wide pen
loBrush = NEWOBJECT('GpSolidBrush', HOME() + 'ffc/_gdiplus.vcx','' )
loBrush.CREATE()

* Draw the pie

lnX = 0 + lnDetachPixels 
lnY = 0 + lnDetachPixels 
lnW = wImg - 1 - (2*lnDetachPixels)
lnH = hImg - 1 - (2*lnDetachPixels)

lnStart = 0

SCAN
   x1 = lnX
   y1 = lnY
   lnSweep = sales.nValue / lnTotal * 360

   IF Sales.lDetach = .T.
      lnDetachAngle = 360 - (lnStart + (lnSweep / 2))
      x1 = lnX + (COS(DTOR(lnDetachAngle)) * lnDetachPixels)
      y1 = lnY - (SIN(DTOR(lnDetachAngle)) * lnDetachPixels)
   ENDIF
   IF llBW

      * tnStyle, tvForeColor, tvBackColor
      loBWBrush.Create(RECNO()*2, loLineColor, 0xFFFFFFFF)
      loGraph.FillPie(loBWBrush, x1, y1, lnW, lnH, lnStart, lnSweep) && draw the slice
      loGraph.DrawPie(loPen, x1, y1, lnW, lnH, lnStart, lnSweep) && draw the black contour
   ELSE
      loBrushColor.FoxRGB = sales.nColor
      loBrush.BrushColor = loBrushColor
      loGraph.FillPie(loBrush, x1, y1, lnW, lnH, lnStart, lnSweep) && slice
      loGraph.DrawPie(loPen, x1, y1, lnW, lnH, lnStart, lnSweep) && black contour
   ENDIF
   lnStart = lnStart + lnSweep
ENDSCAN

*!* Save Image
loBitmap.SavetoFile("PieDetached.png","image/png")
*!* Show image in MS Internet Explorer
RUN /N explorer.EXE PieDetached.png

Drawing texts inside the slices

In this case I will use the same technique of the previous example. As I want to draw the legend inside the slice, this time I need to calculate where to Draw the String.

These are the steps:

  • check if there is a string to draw
  • calculate the coordinates to draw the text
  • compensate if the slice is detached
  • create a Font object
  • create StringFormat object, setting alignment to center in vertical and horizontal
  • measure the size of the string to draw to permit centering correctly
  • create Rectangle in which text will be drawn
  • draw an empty white rectangle under the text, to make it more visible
  • draw the text
The coordinates of the text are calculated below
 * Calculate positions
  lncenterx = (lnw / 2) + lndetachpixels
  lncentery = (lnh / 2) + lndetachpixels

  lndistance = 0.75 && distance from the center of pie to draw text
      && if lnDistance < 1 text will be inside the slice
      && if lnDistance = 1 text will be in the border of the slice
      && if lnDistance > 1 text will be outside the border of the slice
  lnradius = (lnw / 2) * lndistance

  lnangle = 360 - (lnstart + lnsweep / 2)
  x1 = lncenterx + (COS(DTOR(lnangle)) * lnradius)
  y1 = lncentery - (SIN(DTOR(lnangle)) * lnradius)
The full code is below:
*!* PROGRAM : UT_TEXTDETACHPIE.PRG
*!* AUTHOR  : CESAR CHALOM
*!* Creates pie chart based in the contents of a cursor
*!* Switch llBW from .F. to .T to get a B&W output
*!* Detaches slice if field lDetach is .T.
*!* Draws Text inside Slice if field cSliceText is not empty

#DEFINE fontstyleregular 0
#DEFINE fontstylebold  1
#DEFINE fontstyleitalic  2
#DEFINE fontstylebolditalic 3
#DEFINE fontstyleunderline 4
#DEFINE fontstylestrikeout 8

#DEFINE unitworld  0
#DEFINE unitdisplay  1
#DEFINE unitpixel  2
#DEFINE unitpoint  3
#DEFINE unitinch  4
#DEFINE unitdocument 5
#DEFINE unitmillimeter 6

#DEFINE stringalignmentnear 0
#DEFINE stringalignmentcenter 1
#DEFINE stringalignmentfar  2

CREATE CURSOR sales (nvalue N(8,2), clegend c(6), ldetach l, ncolor i, cslicetext c(10))
INSERT INTO sales VALUES (250, "JAN", .F., RGB(0,0,255)   ,"")
INSERT INTO sales VALUES (128, "FEB", .T., RGB(0,255,255) ,"FEB")
INSERT INTO sales VALUES ( 90, "MAR", .F., RGB(255,0,255) ,"")
INSERT INTO sales VALUES (330, "APR", .F., RGB(255,160,60),"")
INSERT INTO sales VALUES (250, "MAY", .T., RGB(255,255,0) ,"MAY")
INSERT INTO sales VALUES (150, "JUN", .F., RGB(0,255,64)  ,"JUN")
INSERT INTO sales VALUES (180, "JUL", .F., RGB(255,0,0)   ,"US$ 180")
INSERT INTO sales VALUES (100, "AUG", .T., RGB(128,128,128),"")

SELECT sales

LOCAL lntotal, lnstart, lnsweep, llbw, lndetachpixels

lndetachpixels = 30

llbw = .F.
*!* llBW = .T. && Uncomment this line to create Black and White charts

CALCULATE SUM(sales.nvalue) TO lntotal

wimg = 300 && Width of image
himg = 300 && Height of image

*!* Create empty Bitmap
LOCAL lobitmap AS gpbitmap OF HOME() + ffc/_gdiplus.vcx
lobitmap = NEWOBJECT("GpBitmap", HOME() + "ffc/_gdiplus.vcx")
lobitmap.CREATE(wimg, himg)

LOCAL lograph AS gpgraphics OF HOME() + ffc/_gdiplus.vcx
lograph = NEWOBJECT('GpGraphics', HOME() + "ffc/_gdiplus.vcx")
lograph.createfromimage(lobitmap)
lograph.CLEAR(0xffffffff) && White BackGround

*!* Create the drawing objects
LOCAL lolinecolor AS gpcolor OF HOME() + ffc/_gdiplus.vcx ;
   , lobrushcolor AS gpcolor OF HOME() + ffc/_gdiplus.vcx ;
   , lopen AS gppen OF HOME() + ffc/_gdiplus.vcx ;
   , lobrush AS gpsolidbrush OF HOME() + ffc/_gdiplus.vcx ;
   , lotextbrush AS gpsolidbrush OF HOME() + ffc/_gdiplus.vcx ;
   , lobwbrush AS gphatchbrush OF HOME() + ffc/_gdiplus.vcx ;
   , lotextrect AS gprectangle OF HOME() + 'ffc/_gdiplus.vcx' ;
   , lotextsize AS gpsize  OF HOME() + 'ffc/_gdiplus.vcx'

lolinecolor = NEWOBJECT('GpColor', HOME() + 'ffc/_gdiplus.vcx','', 0,0,0 ) && black
lobrushcolor = NEWOBJECT('GpColor', HOME() + 'ffc/_gdiplus.vcx')
lobwbrush = NEWOBJECT('GpHatchBrush', HOME() + 'ffc/_gdiplus.vcx','' )
lopen = NEWOBJECT('GpPen', HOME() + 'ffc/_gdiplus.vcx','', lolinecolor ) && 1-pixel-wide pen
lobrush = NEWOBJECT('GpSolidBrush', HOME() + 'ffc/_gdiplus.vcx','' )
lobrush.CREATE()

lotextbrush = NEWOBJECT('GpSolidBrush', HOME() + 'ffc/_gdiplus.vcx','' )
lotextbrush.CREATE()
lotextbrush.brushcolor = lolinecolor

* Draw the pie
LOCAL lnx, lny, lnw, lnh, x1, y1, lncenterx, lncentery, lnradius, lndistance

* Calculate coordinates
lnx = 0 + lndetachpixels
lny = 0 + lndetachpixels
lnw = wimg - 1 - (2*lndetachpixels)
lnh = himg - 1 - (2*lndetachpixels)

lnstart = 0

SCAN
   x1 = lnx
   y1 = lny
   lnsweep = sales.nvalue / lntotal * 360

   IF sales.ldetach = .T.
      lndetachangle = 360 - (lnstart + (lnsweep / 2))
      x1 = lnx + (COS(DTOR(lndetachangle)) * lndetachpixels)
      y1 = lny - (SIN(DTOR(lndetachangle)) * lndetachpixels)
   ENDIF

   IF llbw

      * tnStyle, tvForeColor, tvBackColor
      lobwbrush.CREATE(RECNO()*2, lolinecolor, 0xffffffff)
      lograph.fillpie(lobwbrush, x1, y1, lnw, lnh, lnstart, lnsweep) && draw the slice
      lograph.drawpie(lopen, x1, y1, lnw, lnh, lnstart, lnsweep) && draw the black contour
   ELSE
      lobrushcolor.foxrgb = sales.ncolor
      lobrush.brushcolor = lobrushcolor
      lograph.fillpie(lobrush, x1, y1, lnw, lnh, lnstart, lnsweep) && draw the slice
      lograph.drawpie(lopen, x1, y1, lnw, lnh, lnstart, lnsweep) && draw the black contour
   ENDIF


   IF NOT EMPTY(sales.cslicetext)

      * Calculate positions
      lncenterx = (lnw / 2) + lndetachpixels
      lncentery = (lnh / 2) + lndetachpixels

      lndistance = 0.75 && distance from the center of pie to draw text
         && if lnDistance < 1 text will be inside the slice
         && if lnDistance = 1 text will be in the border of the slice
         && if lnDistance > 1 text will be outside the border of the slice
      lnradius = (lnw / 2) * lndistance

      lnangle = 360 - (lnstart + lnsweep / 2)
      x1 = lncenterx + (COS(DTOR(lnangle)) * lnradius)
      y1 = lncentery - (SIN(DTOR(lnangle)) * lnradius)

      * If detached, need to compensate
      IF sales.ldetach = .T.
         x1 = x1 + (COS(DTOR(lnangle)) * lndetachpixels)
         y1 = y1 - (SIN(DTOR(lnangle)) * lndetachpixels)
      ENDIF

      * Create Font Object
      lofont = NEWOBJECT('GpFont', HOME() + 'ffc/_gdiplus.vcx')
      lofont.CREATE( "Arial" ;  && font name
         , 12 ;                 && size in units below
         , fontstylebold;   && attributes
         , unitpixel )      && units

      * Get a basic string format object, then set properties
      lostringformat = NEWOBJECT('GpStringFormat', HOME() + 'ffc/_gdiplus.vcx')
      lostringformat.CREATE()
      lostringformat.ALIGNMENT = stringalignmentcenter
      lostringformat.linealignment = stringalignmentcenter

      * Get measures from the string so that we can center correctly
      lnchars = 0
      lnlines = 0
      lostringsize = lograph.measurestringa(sales.cslicetext, lofont, , , @lnchars, @lnlines)
      lnwidth = lostringsize.w
      lnheight = lostringsize.h

      * Create Rectangle in which text will be drawn
      lotextrect = NEWOBJECT('GpRectangle', HOME() + 'ffc/_gdiplus.vcx','',;
       x1 - (lnwidth/2) , y1 - (lnheight/2), lostringsize.w, lostringsize.h)

      * Draw an empty White rectangle under the text
      lobrush.brushcolor = 0xffffffff && White Background
      lograph.fillrectangle(lobrush, lotextrect)

      * Draw the Text
      lograph.drawstringa( sales.cslicetext, lofont, lotextrect, lostringformat, lotextbrush )

   ENDIF

   lnstart = lnstart + lnsweep
ENDSCAN

*!* Save Image
lobitmap.savetofile("PieTextDetach.png","image/png")
*!* Show image in MS Internet Explorer
RUN /N explorer.EXE pietextdetach.png

In this case I used

lndistance = 0.75 && distance from the center of pie to draw text
If we want the text to be drawn outside the pie, we can increase the value of lnDistance to a value bigger than 1. Change the value of lnDistance
lndistance = 1.10 && distance from the center of pie to draw text
and check the output:

3D Pie Charts

We can obtain a three-dimensional effect by drawing the slices one above the other at one pixel up for N times. It's also interesting to make the width significantly bigger than the height, to create a more interesting 3D result.

The example below creates two slices with the effect:

#DEFINE HatchStyle05Percent     6 
#DEFINE HatchStyle10Percent     7 
#DEFINE HatchStyle20Percent     8 
#DEFINE HatchStyle25Percent     9 
#DEFINE HatchStyle30Percent     10 
#DEFINE HatchStyle40Percent     11 
#DEFINE HatchStyle50Percent     12 
#DEFINE HatchStyle60Percent     13 
#DEFINE HatchStyle70Percent     14 
#DEFINE HatchStyle75Percent     15 
#DEFINE HatchStyle80Percent     16 
#DEFINE HatchStyle90Percent     17 

ln3dheight = 30
wimg = 300
himg = 200

LOCAL lobitmap AS gpbitmap OF ffc/_gdiplus.vcx
lobitmap = NEWOBJECT("GpBitmap", HOME() + "ffc/_gdiplus.vcx")
lobitmap.CREATE(wimg, himg)

LOCAL lograph AS gpgraphics OF HOME() + ffc/_gdiplus.vcx
lograph = NEWOBJECT('GpGraphics', HOME() + "ffc/_gdiplus.vcx")
lograph.createfromimage(lobitmap)
lograph.CLEAR(0xffffffff) && white Background

* Create the drawing objects
LOCAL lobrushcolor AS gpcolor OF HOME() + ffc/_gdiplus.vcx ;
   , loblackcolor AS gpcolor OF HOME() + ffc/_gdiplus.vcx ;
   , lopen AS gppen OF HOME() + ffc/_gdiplus.vcx ;
   , lobrush AS gpsolidbrush OF HOME() + ffc/_gdiplus.vcx ;
   , lobrush AS gphatchbrush OF HOME() + ffc/_gdiplus.vcx

loblackcolor = NEWOBJECT('GpColor', HOME() + 'ffc/_gdiplus.vcx','',0,0,0)
lopen = NEWOBJECT('GpPen', HOME() + 'ffc/_gdiplus.vcx','', loblackcolor )  && 1-pixel-wide pen
lobrush = NEWOBJECT('GpSolidBrush', HOME() + 'ffc/_gdiplus.vcx','' )
lobrush.CREATE()
lobrushcolor = NEWOBJECT('GpColor', HOME() + 'ffc/_gdiplus.vcx')
lo3dbrush = NEWOBJECT('GpHatchBrush', HOME() + 'ffc/_gdiplus.vcx','' )

LOCAL X, Y, w, h, lnstart, lnsweep

x = 0
Y = 0
w = wimg - 1
h = himg - 1 - ln3dheight

* Draw the first slice without contour
lnstart = 0
lnsweep = 60

lobrushcolor.foxrgb = RGB(255,0,0) && Red
lobrush.brushcolor = lobrushcolor

* tnStyle, tvForeColor, tvBackColor
lo3dbrush.CREATE(hatchstyle50percent, lobrushcolor, loblackcolor)

* Draw the Black contour of the 3D Slice
lograph.fillpie(lo3dbrush, x, Y + ln3DHeight, w, h, lnstart, lnsweep)
* Draw the 3D part of Slice with HatchBrush
FOR N = ln3dheight - 1 TO 1 STEP -1
   lograph.fillpie(lo3dbrush, x, Y + N, w, h, lnstart, lnsweep)
ENDFOR
* Draw the face of the slice with SolidBrush
lograph.fillpie(m.lobrush, x, Y, w, h, lnstart, lnsweep)


* Draw the second slice with the Black contour
lnstart = 180
lnsweep = 60

lobrushcolor.foxrgb = RGB(255,255,0) && Yellow
lobrush.brushcolor = lobrushcolor

* tnStyle, tvForeColor, tvBackColor
lo3dbrush.CREATE(hatchstyle50percent, lobrushcolor, loblackcolor)

* Draw the Black contour of the 3D Slice
lograph.fillpie(lo3dbrush, x, Y + ln3DHeight, w, h, lnstart, lnsweep)
lograph.drawpie(m.lopen, X, Y + ln3DHeight, w, h, lnstart, lnsweep)
* Draw the 3D part of Slice with HatchBrush
FOR N = ln3dheight - 1 TO 1 STEP -1
   lograph.fillpie(lo3dbrush, x, Y + N, w, h, lnstart, lnsweep)
   lograph.drawarc(m.lopen, X, Y + N, w, h, lnstart, 0.25)
   lograph.drawarc(m.lopen, X, Y + N, w, h, lnstart + lnSweep, 0.25)
ENDFOR
* Draw the face of the slice with SolidBrush
lograph.fillpie(lobrush, x, Y, w, h, lnstart, lnsweep)
lograph.drawpie(lopen, x, Y, w, h, lnstart, lnsweep)

lobitmap.savetofile("Pie3DSlices.png","image/png")
RUN /N explorer.EXE pie3dslices.png

Note that in this example a HatchBrush was used to create the 3d Effect, and a SolidBrush for the face of the slice.

The variable ln3DHeight stores the quantity of pixels of the height of the pie

The red Slice was drawn with no contour, while the yellow slice has a black contour, that was created in the second part of the code, in which I needed to draw a pie, and some points using the DrawArc function:

* Draw the Black contour of the 3D Slice
lograph.fillpie(lo3dbrush, x, Y + ln3DHeight, w, h, lnstart, lnsweep)
lograph.drawpie(m.lopen, X, Y + ln3DHeight, w, h, lnstart, lnsweep)
* Draw the 3D part of Slice with HatchBrush
FOR N = ln3dheight - 1 TO 1 STEP -1
   lograph.fillpie(lo3dbrush, x, Y + N, w, h, lnstart, lnsweep)
   lograph.drawarc(m.lopen, X, Y + N, w, h, lnstart, 0.25)
   lograph.drawarc(m.lopen, X, Y + N, w, h, lnstart + lnSweep, 0.25)
ENDFOR
* Draw the face of the slice with SolidBrush
lograph.fillpie(lobrush, x, Y, w, h, lnstart, lnsweep)
lograph.drawpie(lopen, x, Y, w, h, lnstart, lnsweep)
Now that we know how to create a 3D pie chart, we can use our sales cursor, right ? Wrong !!! Run the example below, and see the output created using the techniques shown in the previous topics:
#DEFINE HatchStyle50Percent     12 
LOCAL ln3DHeight, wImg, hImg, lnStart, lnSweep
lnStart = 0 && Start angle
*!* Run this example another time, using lnStart = 270

ln3dHeight = 25

CREATE CURSOR sales (nValue N(8,2), cLegend c(6), lDetach l, nColor i, cSliceText c(10))
INSERT INTO sales VALUES (250, "JAN", .F., RGB(0,0,255)   ,"")
INSERT INTO sales VALUES (128, "FEB", .T., RGB(0,255,255) ,"FEB")
INSERT INTO sales VALUES ( 90, "MAR", .F., RGB(255,0,255) ,"")
INSERT INTO sales VALUES (330, "APR", .F., RGB(255,160,60),"")
INSERT INTO sales VALUES (250, "MAY", .T., RGB(255,255,0) ,"MAY")
INSERT INTO sales VALUES (150, "JUN", .F., RGB(0,255,64)  ,"JUN")
INSERT INTO sales VALUES (180, "JUL", .F., RGB(255,0,0)   ,"US$ 180")
INSERT INTO sales VALUES (100, "AUG", .T., RGB(128,128,128),"")
SELECT sales

CALCULATE SUM(sales.nValue) TO lnTotal

wImg = 350
hImg = 200

LOCAL loBitmap AS GpBitmap OF ffc/_gdiplus.vcx
loBitmap = NEWOBJECT("GpBitmap", HOME() + "ffc/_gdiplus.vcx")
loBitmap.Create(wImg, hImg + ln3dHeight) 

LOCAL loGraph AS GpGraphics OF HOME() + ffc/_gdiplus.vcx
loGraph = NEWOBJECT('GpGraphics', HOME() + "ffc/_gdiplus.vcx")
loGraph.CreatefromImage(loBitmap)
loGraph.Clear(0xFFFFFFFF)

* Create the drawing objects
local loBlackColor as GpColor of HOME() + ffc/_gdiplus.vcx ;
  , loBrushColor as GpColor of HOME() + ffc/_gdiplus.vcx ;
  , loPen as GpPen of HOME() + ffc/_gdiplus.vcx ;
  , loBrush as GpSolidBrush of HOME() + ffc/_gdiplus.vcx ;
  , lo3DBrush as GpHatchBrush of HOME() + ffc/_gdiplus.vcx ;
  , loBounds as GpRectangle of HOME() + ffc/_gdiplus.vcx

loBounds = NEWOBJECT('GpRectangle', HOME() + 'ffc/_gdiplus.vcx','', 0, 0, wImg - 1, hImg - 1)
loBlackColor = newobject('GpColor', HOME() + 'ffc/_gdiplus.vcx','', 0,0,0 ) && black
loPen = newobject('GpPen', HOME() + 'ffc/_gdiplus.vcx','', loBlackColor )  && 1-pixel-wide pen
loBrush = newobject('GpSolidBrush', HOME() + 'ffc/_gdiplus.vcx','' )
loBrush.Create()
loBrushColor = newobject('GpColor', HOME() + 'ffc/_gdiplus.vcx')
lo3DBrush = newobject('GpHatchBrush', HOME() + 'ffc/_gdiplus.vcx','' )

* Draw the pie
SCAN
   * Calculate Sweep
   lnSweep = sales.nValue / lnTotal * 360
   loBrushColor.FoxRGB = Sales.nColor
   loBrush.BrushColor = loBrushColor

   * tnStyle, tvForeColor, tvBackColor
   lo3DBrush.Create(HatchStyle50Percent, loBrushColor, loBlackColor)

   loBounds.Y = loBounds.Y + ln3DHeight
   FOR n = 1 TO ln3dHeight
      loGraph.FillPie(lo3DBrush, loBounds, lnStart, lnSweep)
      loBounds.Y = loBounds.Y - 1
   ENDFOR
	
   loGraph.FillPie(loBrush, loBounds, lnStart, lnSweep)
   loGraph.DrawPie(loPen, loBounds, lnStart, lnSweep)
   lnStart = lnStart + lnSweep
ENDSCAN
loGraph.DrawArc(loPen, 0, ln3DHeight - 1, wImg, hImg, 0, 180)

loBitmap.SavetoFile("Pie3DWrong.png","image/png")
RUN /N explorer.exe Pie3DWrong.png 

Can you guess why we got such an ugly graphic ?

Run the same code above, but make just one modification. In the beginning of the code, in Line 3 change the value of lnStart from 0 to 270:

lnStart = 270 && Start angle

This change made the chart to start drawing starting from angle 270, the top most position in the graphic. The main difference in the result is that the right side of the graphic now looks fine, while the left is a complete mess. This happens because when every slice is drawn, its 3D part overlaps the previous slice.

The solution is to draw each side of the pie chart separately, starting from the highest point. At the right side, starting from angle 270 to 450 (270 + 180); In the left side, we need to start from angle 270, going backwards to angle 90 (270 – 180).

Now try this fixed code:

*!* PROGRAM : UT_PIE3DFIXED.PRG
*!* AUTHOR  : CESAR CHALOM
*!* Creates simple pie chart with 3D effect
*!* allows detachment of slices

#DEFINE HatchStyle50Percent     12 

LOCAL ln3DHeight, wImg, hImg, lnStart, lnSweep, lnDetachPixels

lnStart = 270 && Start angle
ln3dHeight = 25
lnDetachPixels = 30

CREATE CURSOR sales (nValue N(8,2), cLegend c(6), lDetach l, nColor i, cSliceText c(10))
INSERT INTO sales VALUES (250, "JAN", .F., RGB(0,0,255)   ,"")
INSERT INTO sales VALUES (128, "FEB", .T., RGB(0,255,255) ,"FEB")
INSERT INTO sales VALUES ( 90, "MAR", .F., RGB(255,0,255) ,"")
INSERT INTO sales VALUES (330, "APR", .F., RGB(255,160,60),"")
INSERT INTO sales VALUES (250, "MAY", .T., RGB(255,255,0) ,"MAY")
INSERT INTO sales VALUES (150, "JUN", .F., RGB(0,255,64)  ,"JUN")
INSERT INTO sales VALUES (180, "JUL", .T., RGB(255,0,0)   ,"US$ 180")
INSERT INTO sales VALUES (100, "AUG", .F., RGB(128,128,128),"")
SELECT sales

CALCULATE SUM(sales.nValue) TO lnTotal

wImg = 350
hImg = 200

LOCAL loBitmap AS GpBitmap OF ffc/_gdiplus.vcx
loBitmap = NEWOBJECT("GpBitmap", HOME() + "ffc/_gdiplus.vcx")
loBitmap.Create(wImg, hImg + ln3dHeight) 

LOCAL loGraph AS GpGraphics OF HOME() + ffc/_gdiplus.vcx
loGraph = NEWOBJECT('GpGraphics', HOME() + "ffc/_gdiplus.vcx")
loGraph.CreatefromImage(loBitmap)
loGraph.Clear(0xFFFFFFFF)

* Create the drawing objects
local loBlackColor as GpColor of HOME() + ffc/_gdiplus.vcx ;
  , loBrushColor as GpColor of HOME() + ffc/_gdiplus.vcx ;
  , loPen as GpPen of HOME() + ffc/_gdiplus.vcx ;
  , loBrush as GpSolidBrush of HOME() + ffc/_gdiplus.vcx ;
  , lo3DBrush as GpHatchBrush of HOME() + ffc/_gdiplus.vcx ;
  , loBounds as GpRectangle of HOME() + ffc/_gdiplus.vcx

loBounds = NEWOBJECT('GpRectangle', HOME() + 'ffc/_gdiplus.vcx','', 0, 0, wImg - 1, hImg - 1)
loBlackColor = newobject('GpColor', HOME() + 'ffc/_gdiplus.vcx','', 0,0,0 ) && black
loPen = newobject('GpPen', HOME() + 'ffc/_gdiplus.vcx','', loBlackColor )  && 1-pixel-wide pen
loBrush = newobject('GpSolidBrush', HOME() + 'ffc/_gdiplus.vcx','' )
loBrush.Create()
loBrushColor = newobject('GpColor', HOME() + 'ffc/_gdiplus.vcx')
lo3DBrush = newobject('GpHatchBrush', HOME() + 'ffc/_gdiplus.vcx','' )

lnX = 0 + lnDetachPixels 
lnY = 0 + lnDetachPixels 
lnW = wImg - 1 - (2*lnDetachPixels)
lnH = hImg - 1 - (2*lnDetachPixels)

* Draw the pie
SCAN FOR lnStart < 450
   x1 = lnX
   y1 = lnY

   * Calculate Start point and Sweep
   lnSweep = sales.nValue / lnTotal * 360

   IF Sales.lDetach = .T.
      lnDetachAngle = 360 - (lnStart + (lnSweep / 2))
      x1 = lnX + (COS(DTOR(lnDetachAngle)) * lnDetachPixels)
      y1 = lnY - (SIN(DTOR(lnDetachAngle)) * lnDetachPixels)
   ENDIF

   * Prepare Brushes
   loBrushColor.FoxRGB = Sales.nColor
   loBrush.BrushColor = loBrushColor

   * tnStyle, tvForeColor, tvBackColor
   lo3DBrush.Create(HatchStyle50Percent, loBrushColor, loBlackColor)

   * Draw the 3D Slice using the Hatch Brush
   Y1 = Y1 + ln3DHeight
   FOR n = 1 TO ln3dHeight
   	loGraph.FillPie(lo3DBrush, x1, y1, lnW, lnH, lnStart, lnSweep) && draw slice
      Y1 = Y1 - 1
   ENDFOR

   * Draw the Normal Slice using the Solid Brush
   loGraph.FillPie(loBrush, x1, y1, lnW, lnH, lnStart, lnSweep)
 
   * Draw the Black contour of the slice using the Pen object
   loGraph.DrawPie(loPen, x1, y1, lnW, lnH, lnStart, lnSweep)

   lnStart = lnStart + lnSweep
ENDSCAN

GO BOTTOM 
lnStart = 270
DO WHILE lnStart > 90
   x1 = lnX
   y1 = lnY
   * Calculate Start point and Sweep
   lnSweep = (sales.nValue / lnTotal) * 360
   lnStart = lnStart - lnSweep
   IF lnstart <= 90
      lnSweep = lnSweep - (90 - lnStart)
      lnStart = 90
   ENDIF

   IF Sales.lDetach = .T.
      lnDetachAngle = 360 - (lnStart + (lnSweep / 2))
      x1 = lnX + (COS(DTOR(lnDetachAngle)) * lnDetachPixels)
      y1 = lnY - (SIN(DTOR(lnDetachAngle)) * lnDetachPixels)
   ENDIF

   * Prepare Brushes
   loBrushColor.FoxRGB = Sales.nColor
   loBrush.BrushColor = loBrushColor

   * tnStyle, tvForeColor, tvBackColor
   lo3DBrush.Create(HatchStyle50Percent, loBrushColor, loBlackColor)

   * Draw the 3D Slice using the Hatch Brush
   Y1 = Y1 + ln3DHeight
   FOR n = 1 TO ln3dHeight
      loGraph.FillPie(lo3DBrush, x1, y1, lnW, lnH, lnStart, lnSweep) && draw the slice
      Y1 = Y1 - 1
   ENDFOR

   * Draw the Normal Slice using the Solid Brush
   loGraph.FillPie(loBrush, x1, y1, lnW, lnH, lnStart, lnSweep)

   IF lnStart > 90
      * Draw the Black contour of the slice using the Pen object
      loGraph.DrawPie(loPen, x1, y1, lnW, lnH, lnStart, lnSweep)
   ENDIF
   SKIP -1
ENDDO

loBitmap.SavetoFile("Pie3DFixed.png","image/png")
RUN /N explorer.exe Pie3DFixed.png

Now we can transform all these things discussed here in a class that will permit:

  • create plain and 3D charts, controlling height
  • control pie size
  • colored and monochrome
  • allow detachment of slices and distance from center
  • allow writing over slices
  • add chart title
  • create legends
  • control fonts
  • control slice and border colors
  • hide slices
For this task I’ve created a simple class, GpPie, that can be called this way:

EXAMPLE 1

IF Not "gpPie" $ SET("Classlib") 
   SET CLASSLIB TO gpPie.vcx ADDITIVE 
ENDIF 
  
CREATE CURSOR sales (Value N(8,2), Legend c(12), Detach l, SliceText c(10), lHidden L) 
INSERT INTO sales VALUES (200, "Health" , .F., "$200", .T.) 
INSERT INTO sales VALUES (100, "Rent" , .T., "$100", .F.) 
INSERT INTO sales VALUES ( 90, "Fuel" , .F., "$90" , .F.) 
INSERT INTO sales VALUES (180, "Food" , .T., "$180", .T.) 
INSERT INTO sales VALUES (200, "Trips" , .T., "$200", .F.) 
INSERT INTO sales VALUES ( 65, "Gifts" , .F., "$65" , .F.) 
INSERT INTO sales VALUES (100, "Cleaning" , .F., "$110", .F.) 
INSERT INTO sales VALUES ( 30, "Magazines", .T., "$30" , .F.) 
INSERT INTO sales VALUES (200, "Cell Phone", .T.,"$200", .T.) 
INSERT INTO sales VALUES (100, "Other" , .T., "$100", .F.) 
*** Parameters for direct call : 
*** (tcTable, tcValueField, tcLegendField, tcColorField, tcSliceTextField, tcCaption) 
*!* ImgPie = CREATEOBJECT("gpPie","sales","sales.Value","sales.Legend", ; 
*!* "sales.detach", "sales.color", "sales.slicetext", "Sales Year 2004") 
LOCAL ImgPie AS GpPie OF GpPie.vcx 
ImgPie = CREATEOBJECT("gpPie") 
WITH ImgPie 
.PieHeight = 300 
.PieWidth = 450 
.Style = 2 && 1 = Plain ; 2 = 3D 
*!* .Monochrome = .T. 
.Gradient = .T. 
.GradientLevel = 8 && From 0 to 10 
.FontName = "Tahoma" 
.Title = "MY EXPENDITURES - MAY 2006" 
.TitleFontColor = RGB(0,0,64) && DarkBlue 
.TitleFontSize = 22 
.TitleFontStyle = "" 
.SourceAlias = "sales" 
.PieDetachField = "sales.detach" 
.PieValueField = "sales.Value" 
.PieLegendField = "sales.Legend" 
.PieSliceTextField = "sales.SliceText" 
.PieHiddenField = "sales.lHidden" 
.PieBorderColor = RGB(255,255,255) && Black 
.PieBorderSize = 0 && 1 
.PieLegendFontColor = RGB(255,255,255) && White 
.PieLegendFontSize = 12 
.PieLegendFontStyle = "B" 
.PieLegendStyle = 0 && 0 = transparent 1 = opaque 
.PieLegendBackColor = RGB(255,255,255) 
.PieLegendDistance = 0.7 && distance in percentage from the center of pie to draw text 
&& if lnDistance < 1 text will be inside the slice 
&& if lnDistance = 1 text will be in the border of the slice 
&& if lnDistance > 1 text will be outside the border of the slice 
.LegendShow = .T. && Show Legends 
.LegendPosition = 3 && 1 = TopLeft 2 = bottLeft 3 = TopRight 4 = BottRight 
.LegendBorder = 0 &&1 
.LegendBorderColor = RGB(0,64,64) 
.LegendFontColor = RGB(0,0,0) && Black 
.LegendFontSize = 12 
.LegendFontStyle = "" 
.LegendBackColor = RGB(255,255,255) 
.LegendShapeBorder = 0 && 1 
  
.BackColor = RGB(255,255,255) && White 
.Height3D = 30 
.DetachDistance = 50 
.Create() 
.PrintPreview() && Opens Report in PREVIEW mode or could be .Print(.T.) 
*!* .Print() && Prints Image in Default printer 
.Show 
ENDWITH 
imgPie = NULL 
RETURN

EXAMPLE 2

IF Not "gpPie" $ SET("Classlib") 
SET CLASSLIB TO gpPie.vcx ADDITIVE 
ENDIF 
CREATE CURSOR sales (Value N(8,2), Legend c(12), Detach l, Color i, SliceText c(10), lHidden L) 
INSERT INTO sales VALUES (200, "Health" , .F., RGB(0,0,0) ,"$200", .T.) 
INSERT INTO sales VALUES (100, "Rent" , .T., RGB(48,48,48) ,"$100", .F.) 
INSERT INTO sales VALUES (180, "Food" , .F., RGB(96,96,96) ,"$180", .T.) 
INSERT INTO sales VALUES (200, "Trips" , .T., RGB(144,144,144) ,"$200", .F.) 
INSERT INTO sales VALUES (100, "Cleaning" , .F., RGB(192,192,192),"$100", .F.) 
ImgPie = CREATEOBJECT("gpPie") 
WITH ImgPie 
.PieHeight = 200 
.PieWidth = 350 
.Style = 2 && 1 = Plain ; 2 = 3D 
.Gradient = .T. 
.GradientLevel = 3 
.FontName = "Tahoma" 
.Title = "MY EXPENDITURES - MAY 2006" 
.TitleFontColor = RGB(0,0,64) && Blue 
.TitleFontSize = 20 
.TitleFontStyle = "I" 
.TitleBackColor = RGB(220,220,220) 
.SourceAlias = "sales" 
.PieDetachField = "sales.detach" 
.PieValueField = "sales.Value" 
.PieColorField = "sales.Color" 
.PieLegendField = "sales.Legend" 
.PieSliceTextField = "sales.SliceText" 
.PieHiddenField = "sales.lHidden" 
.PieBorderColor = RGB(255,255,255) && White 
.PieLegendFontColor = RGB(0,0,0) && Black 
.PieLegendFontSize = 12 
.PieLegendFontStyle = "" 
.PieLegendStyle = 1 && 0 = transparent 1 = opaque 
.PieLegendBackColor = RGB(255,255,255) 
.PieLegendDistance = 0.8 && distance in percentage from the center of pie to draw text 
&& if lnDistance < 1 text will be inside the slice 
&& if lnDistance = 1 text will be in the border of the slice 
&& if lnDistance > 1 text will be outside the border of the slice 
.LegendShow = .T. && Show Legends 
.LegendPosition = 4 && 1 = TopLeft 2 = bottLeft 3 = TopRight 4 = BottRight 
.LegendBorder = 0 &&1 
.LegendFontColor = RGB(0,0,0) && Black 
.LegendFontSize = 12 
.LegendFontStyle = "" 
.LegendBackColor = RGB(255,255,255) 
.LegendShapeBorder = 0 && 1 
  
.BackColor = RGB(255,255,255) && White 
.Height3D = 30 
.DetachDistance = 50 
.Create() 
.PrintPreview() && Opens Report in PREVIEW mode or could be .Print(.T.) 
*!* .Print() && Prints Image in Default printer 
.Show 
ENDWITH 
imgPie = NULL 
RETURN

EXAMPLE 3

IF Not "gpPie" $ SET("Classlib") 
SET CLASSLIB TO gpPie.vcx ADDITIVE 
ENDIF 
CREATE CURSOR sales (Value N(8,2), Legend c(12), Detach l) 
INSERT INTO sales VALUES (200, "Health" , .F.) 
INSERT INTO sales VALUES (100, "Rent" , .T.) 
INSERT INTO sales VALUES ( 90, "Fuel" , .F.) 
INSERT INTO sales VALUES (180, "Food" , .T.) 
INSERT INTO sales VALUES (200, "Trips" , .T.) 
INSERT INTO sales VALUES (100, "Cleaning" , .F.) 
INSERT INTO sales VALUES ( 80, "Magazines", .T.) 
*** Parameters for direct call : 
*** tcTable, tcValueField, tcLegendField, tcColorField, tcDetachField,tcSliceTextField,;
 tcCaption 
LOCAL loPieChart AS GpPie OF GpPie.vcx 
loPieChart = CREATEOBJECT("gpPie","sales","sales.Value","sales.Legend", ,"sales.Detach", ;
 "", "My Expenses May 2006") 
WITH loPieChart 
.PieHeight = 250 
.PieWidth = 250 
.Gradient = .T. 
.Style = 2 && 1 = Plain ; 2 = 3D 
.Height3D = 30 
.BackColor = RGB(150,150,255) 
.BackColor = RGB(150,150,255) 
.TitleBackColor = RGB(175,175,255) 
.LegendBackColor = RGB(175,175,255) 
.Create() 
.Show 
*!* MESSAGEBOX("ImageFile : " + loPieChart.ImageFile) 
MESSAGEBOX("Image File created containing Pie Chart " +CHR(13) + ; 
loPieChart.ImageFile + CHR(13) + CHR(13) + ; 
"This file will be automatically erased after the object 'loPieChart' is released",; 
64, "GpPie - Class to create Pie Charts") 
ENDWITH 
RETURN

EXAMPLE 4

IF Not "gpPie" $ SET("Classlib") 
SET CLASSLIB TO gpPie.vcx ADDITIVE 
ENDIF 
CREATE CURSOR sales (Value N(8,2), Legend c(12), Detach l) 
INSERT INTO sales VALUES (200, "Health" , .F.) 
INSERT INTO sales VALUES (100, "Rent" , .T.) 
INSERT INTO sales VALUES ( 90, "Fuel" , .F.) 
INSERT INTO sales VALUES (180, "Food" , .T.) 
INSERT INTO sales VALUES (200, "Trips" , .T.) 
INSERT INTO sales VALUES (100, "Cleaning" , .F.) 
INSERT INTO sales VALUES ( 80, "Magazines", .T.) 
*** Parameters for direct call : 
*** tcTable, tcValueField, tcLegendField, tcColorField, tcDetachField, tcSliceTextField,;
 tcCaption 
LOCAL loPieChart AS GpPie OF GpPie.vcx 
loPieChart = CREATEOBJECT("gpPie","sales","sales.Value","sales.Legend", ,"sales.Detach", ;
 "", "My Expenses May 2006") 
WITH loPieChart 
.PieHeight = 250 
.PieWidth = 250 
.Monochrome = .T. 
.Create() 
.Show 
*!* MESSAGEBOX("ImageFile : " + loPieChart.ImageFile) 
MESSAGEBOX("Image File created containing Pie Chart " +CHR(13) + ; 
loPieChart.ImageFile + CHR(13) + CHR(13) + ; 
"This file will be automatically erased after the object 'loPieChart' is released",; 
64, "GpPie - Class to create Pie Charts") 
ENDWITH 
RETURN

EXAMPLE 5

IF Not "gpPie" $ SET("Classlib") 
SET CLASSLIB TO gpPie.vcx ADDITIVE 
ENDIF 
CREATE CURSOR sales (Value N(8,2), Legend c(12), Detach l, Color i, SliceText c(10), Hidden L) 
INSERT INTO sales VALUES (200, "Health" , .F., RGB(0,0,255) ,"$200", .F.) 
INSERT INTO sales VALUES ( 90, "Fuel" , .F., RGB(255,0,255) ,"$90" , .F.) 
INSERT INTO sales VALUES (100, "Rent" , .T., RGB(0,255,255) ,"$600", .F.) 
INSERT INTO sales VALUES (180, "Food" , .T., RGB(255,160,60) ,"" , .T.) 
INSERT INTO sales VALUES (100, "Cleaning" , .F., RGB(128,128,128),"$110", .F.) 
INSERT INTO sales VALUES (200, "Trips" , .T., RGB(255,255,0) ,"$200", .F.) 
INSERT INTO sales VALUES (200, "Other" , .F., RGB(0,0,164) ,"" , .T.) 
INSERT INTO sales VALUES ( 65, "Gifts" , .F., RGB(0,255,64) ,"$65" , .F.) 
INSERT INTO sales VALUES ( 95, "Taxes" , .T., RGB(255,0,0) ,"$95" , .F.) 
*!* Parameters for direct call : 
*!* tcTable, tcValueField, tcLegendField, tcColorField, tcDetachField, tcSliceTextField,;
 tcCaption 
LOCAL loPieChart AS GpPie OF GpPie.vcx 
loPieChart = CREATEOBJECT("gpPie") 
WITH loPieChart 
.PieHeight = 300 
.PieWidth = 450 
.Style = 2 && 1 = Plain ; 2 = 3D 
*!* .Monochrome = .T. 
.Gradient = .F. 
.FontName = "Tahoma" 
.Title = "Look at the Hidden Slices !!!" 
.TitleFontColor = RGB(0,0,128) && Blue 
.TitleFontSize = 22 
.TitleFontStyle = "" 
.TitleBackColor = RGB(250,250,250) 
.SourceAlias = "sales" 
.PieDetachField = "sales.detach" 
.PieValueField = "sales.Value" 
* .PieColorField = "sales.Color" 
.PieLegendField = "sales.Legend" 
.PieSliceTextField = "sales.SliceText" 
.PieHiddenField = "sales.Hidden" 
.PieBorderColor = RGB(0,0,0) && Black 
.PieBorderSize = 1 
.PieLegendFontColor = RGB(255,32,32) && Black 
.PieLegendFontSize = 12 
.PieLegendFontStyle = "" 
.PieLegendStyle = 1 && 0 = transparent 1 = opaque 
.PieLegendBackColor = RGB(255,245,255) 
.PieLegendDistance = 0.7 && distance in percentage from the center of pie to draw text 
&& if lnDistance < 1 text will be inside the slice 
&& if lnDistance = 1 text will be in the border of the slice 
&& if lnDistance > 1 text will be outside the border of the slice 
.LegendShow = .T. && Show Legends 
.LegendPosition = 2 && 1 = TopLeft 2 = bottLeft 3 = TopRight 4 = BottRight 
.LegendBorder = 1 
.LegendBorderColor = RGB(255,64,64) 
.LegendFontColor = RGB(0,0,0) && Black 
.LegendFontSize = 12 
.LegendFontStyle = "" 
.LegendBackColor = RGB(240,200,200) 
.LegendShapeBorder = 1 
  
.BackColor = RGB(220,220,220) && Light Grey 
.Height3D = 30 
.DetachDistance = 50 
.Create() 
*!* .PrintPreview() && Opens Report in PREVIEW mode or could be .Print(.T.) 
*!* .Print() && Prints Image in Default printer 
.Show 
ENDWITH 
loPieChart = NULL 
RETURN

Conclusion

With the techniques presented here, you can create any kind of Pie charts. You can improve and make beautiful charts, using some special brushes, providing transparencies and gradient colors.

Although GDI+ provides functions that enable us to manipulate the screen directly, I chose to save the created charts as image files. An advantage is that it becomes easier to print and save the created charts.

GDI+ came to make our lives with VFP more easy, fun and colored. The limit is our imagination!

Source code

Cesar Chalom
Cesar is an independent software developer. He is an active member of brazilian Visual FoxPro groups.
More articles from this author
Cesar Chalom, May 1, 2006
Certain Image types permit to save multiple images into a single file. It is possible to save several images to a single TIFF (Tagged Image File Format) file. GIF Image files also permit multi-frames, creating animated images, but is not totally supported by GDI+ version 1.
Cesar Chalom, April 1, 2006
Did you know that all the JPEGs from your digital camera contain a lot of extra information? We can easily retrieve some interesting information such as: Title, Equipment Make, Camera Model, Shutter Speed, Lens Aperture, Flash Mode, Date of Picture, and much more! These metadata "tags" are stored in...
Cesar Chalom, August 1, 2006
Following previous articles on the topic, this one describes how to create some cool effects on images with the help of color matrices. Sometimes we may need to make some adjustments in the colors of an image, like to change Saturation, Contrast, Brightness, Hue, convert to Grayscales, to increase o...
Cesar Chalom, October 1, 2006
In the first part of this article, Cesar showed how we can apply some interesting effects to images with the use of color matrices and the new GdiPlus-X library. In this article, Cesar continues by showing more options to make use of some interesting applications of color matrices. This articles in...
Cesar Chalom, September 1, 2006
As a continuation of an article published in the Universal Thread Magazine May 2006 issue, entitled "Multiframe Images with GDI+", Cesar Chalom describes in this article how to manipulate TIFF images using the new GDIPlus-X library from VFP-X project. This article also shows how to create TIFFs usin...