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 :
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
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
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
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
lnX = 0 + lnDetachPixels lnY = 0 + lnDetachPixels lnW = wImg - 1 - (2*lnDetachPixels) lnH = hImg - 1 - (2*lnDetachPixels)
lnDetachAngle = 360 - (lnStart + (lnSweep / 2)) x1 = lnX + (COS(DTOR(lnDetachAngle)) * lnDetachPixels) y1 = lnY - (SIN(DTOR(lnDetachAngle)) * lnDetachPixels)
*!* 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:
* 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)
*!* 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
lndistance = 1.10 && distance from the center of pie to draw text
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)
#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:
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