(code below)
The pie chart above was coded entirely in script (nothing on stage, nothing in the library), using the Flash MX drawing API, event handlers and a data file (as described at the bottom of the text file page), read in with an instance of LoadVars. After raw cookie sales numbers are read from the data file (mmm, raw cookie dough...), they are converted into numbers which represent the corresponding percentage of a 360-degree circle. The drawing API is used to draw corresponding wedges representing each category, in the color specified in the data file. Legend text (cookie category) is also pulled from the data file, along with more detailed information about each category that is displayed when the corresponding wedge is moused over.
Data for the pie chart comes from piechartsampledata.txt, in the same format and read in the same way as described on the Text Files page (second example). An additional line was added to include a more detailed description of each item, which appears onRollOver of the corresponding wedge. In that line, I separated the descriptions with slashes (/) instead of commas, because some of the descriptions include commas, so comma would not work as a delimiter. Each line of data is converted (with the String method split) into an array of values, and then one overall array is created with elements representing each item in the pie chart, with properties for its count, color, category name, and description. Properties for angle, rotation, and bisector angle are calculated and added to each array element after the data is read in, as described in the next paragraph.
The wedge's angle property (size of each wedge of the pie in degrees) is calculated by dividing the count for the corresponding item by the sum of all counts and multiplying that fraction by 360 (total number of degrees in the pie). After its size is calculated, the amount that the wedge must be rotated from 0 (the wedge's rotation property) is calculated by adding up the sizes of all preceding wedges, in degrees. So each wedge is drawn by making a filled arc from 0 to the number of degrees in the wedge, and then rotating it into position. The angle that bisects the wedge, after it has been rotated into place, is also calculated (the wedge's bisectAngle property), so that the wedge can be moved along this line on mouseover. The actual drawing functions for drawing an arc and drawing a box (for the legend boxes) are assigned to MovieClip.prototype, which makes them available to all instances of the MovieClip class in the movie.
The createEmptyMovieClip method of the MovieClip class is used to create a movieclip to hold each wedge and legend box in the sample. Notice that we used another MovieClip method, getNextHighestDepth, to assign a depth to each movieclip as its created, so we don't have to keep track of what depth we're currently on -- each one will just be drawn on a higher depth than the previous one (if it were put on the same depth as another accidentally, it would wipe out the previous content at that depth). Then Drawing API methods are applied (within the drawBox and drawWedge functions) to draw the shapes in each movieclip. createTextField is used, together with TextFormats, to produce the textfields for category name and rollover description.
An onRollOver function is assigned to each wedge movieclip as it's created. Because the value "i" used in the loop will not be known at runtime, it is assigned to each wedge on creation as a permanent property of the wedge, which can then be referred to at runtime to find out which array element to pull the corresponding description from. Code in the onRollOver function fills in the main legend with the right text for the selected wedge, moves that wedge outwards and moves all other wedges back in toward the center (in case any were previously pushed out).
After all the defining, drawing, and assigning are done, the only thing left to do is start the thing up. That's done with the line at the end: lv.load("textfiles/piechartsampledata.txt"); which makes Flash look in a folder named textfiles (within the folder that the html page with the embedded swf is in) to find piechartsampledata.txt and read it into LoadVars instance lv. When the read is complete, lv's onLoad handler is triggered, and the whole process we previously defined is set in motion.
Here is the code that code in frame 1 of an otherwise blank, 400 x 250 pixel movie:
// declare and assign values to variables used throughout
var lv:LoadVars = new LoadVars();
var chartInfo:Array = [];
var countSum:Number = 0;
var TO_RADIANS:Number = Math.PI/180;
// new method of MovieClip class to draw a box in the movieclip
MovieClip.prototype.drawBox = function (x1, y1, x2, y2) {
this.moveTo(x1, y1);
this.lineTo(x2, y1);
this.lineTo(x2, y2);
this.lineTo(x1, y2);
this.lineTo(x1, y1);
}
// new method of MovieClip class to draw a wedge of the specified angle
// and rotation in the movieclip
MovieClip.prototype.drawWedge = function (r, x, y, angle, rotation) {
// start at 0,0 so rotation will be around the point of the wedge
this.moveTo(0, 0);
this.lineTo(r, 0);
// calculate 30-degree segments for accuracy
var nSeg:Number = Math.floor(angle/30); // eg 2 if angle is 80
var pSeg:Number = angle - nSeg*30; // eg 20 if angle is 80
// draw the 30-degree segments
var a:Number = 0.268; // tan(15)
for (var i=0; i < nSeg; i++) {
var endx = r*Math.cos((i+1)*30*TO_RADIANS);
var endy = r*Math.sin((i+1)*30*TO_RADIANS);
var ax = endx+r*a*Math.cos(((i+1)*30-90)*TO_RADIANS);
var ay = endy+r*a*Math.sin(((i+1)*30-90)*TO_RADIANS);
this.curveTo(ax, ay, endx, endy);
}
// draw the remainder
if (pSeg > 0) {
a = Math.tan(pSeg/2 * TO_RADIANS);
endx = r*Math.cos((i*30+pSeg)*TO_RADIANS);
endy = r*Math.sin((i*30+pSeg)*TO_RADIANS);
ax = endx+r*a*Math.cos((i*30 + pSeg-90)*TO_RADIANS);
ay = endy+r*a*Math.sin((i*30 + pSeg-90)*TO_RADIANS);
this.curveTo(ax, ay, endx, endy);
}
this.lineTo(0, 0);
// rotate the wedge to its correct location in the circle
this._rotation = rotation;
this._x = x;
this._y = y;
}
// function to read pie chart data and call displayChart when done
lv.onLoad = function(success:Boolean):Void {
if (success) {
// make arrays out of each of the passed strings
var count:Array = this.count.split(",");
var color:Array = this.color.split(",");
var category:Array = this.category.split(",");
var descript:Array = this.descript.split("/");
// make one array to hold all the information: one entry for each cookie type
for (var i:Number=0; i < count.length; i++) {
countSum += Number(count[i]);
chartInfo.push({
// convert string to Number
count:Number(count[i]),
// convert hex color as string to Number
color:parseInt(color[i], 16),
// no conversion needed for category or descript
category:category[i],
descript:descript[i]
});
}
// calculate the degrees each wedge should be, and the sum of all
// the wedges before and including the current one, and save both
// in a property of chartInfo[i]
var anglesum:Number = 0;
for (i=0; i < count.length-1; i++) {
chartInfo[i].rotation = anglesum; // amount to rotate wedge
chartInfo[i].angle = Math.round((chartInfo[i].count / countSum) * 360);
chartInfo[i].bisectAngle = chartInfo[i].rotation + chartInfo[i].angle/2;
anglesum += chartInfo[i].angle;
}
// Make sure total is 360, despite rounding mismatches
chartInfo[i].rotation = anglesum;
chartInfo[i].angle = 360-anglesum;
chartInfo[i].bisectAngle = chartInfo[i].rotation + chartInfo[i].angle/2;
// draw the chart, legend and textfields and fill with specified color/text
displayChart();
} else {
trace('The cookie information file could not be read');
}
}
function displayChart() {
// draw frame (border only, no fill) from [10, 10] to [390, 240]
createEmptyMovieClip("border",1);
border.lineStyle(1, 0x000000);
border.drawBox(10, 10, 390, 240);
// provide font information for legend tags
var tFormat = new TextFormat();
tFormat.font = "Verdana"; // use this for legend tags and rollover notes
// create information field for rollovers
createTextField("moreinfo", this.getNextHighestDepth(), 20, 200, 360, 100);
tFormat.color = 0x000000;
this.moreinfo.setNewTextFormat(tFormat);
this.moreinfo.multiline = true;
this.moreinfo.wordWrap = true;
this.moreinfo.text = "Roll your mouse over a segment to find out sales details";
// create arc and legend box movieclips, and legend and description text fields
for (var i=0; i < chartInfo.length; i++) {
var arc:MovieClip = createEmptyMovieClip("arc"+i, getNextHighestDepth());
var lb:MovieClip = createEmptyMovieClip("legendbox"+i, getNextHighestDepth());
createTextField("legend"+i, i*10+2*chartInfo.length, 270, 30+i*30, 150, 100);
// set up category textfield
tFormat.color = chartInfo[i].color;
this["legend"+i].setNewTextFormat(tFormat);
this["legend"+i].text = chartInfo[i].category;
// draw arc
arc.lineStyle( .5, 0x000000);
arc.beginFill( chartInfo[i].color, 100);
// drawWedge(radius, x, y, angle, rotation)
arc.drawWedge(80, 120, 110, chartInfo[i].angle, chartInfo[i].rotation);
arc.endFill();
// assign an i property to use at runtime
arc.i = i;
// define onRollOver function for the arc
arc.onRollOver = function() {
this._parent.moreinfo.text = chartInfo[this.i].category + " (" +
Math.round((chartInfo[this.i].angle)/360*100) + "%): " +
chartInfo[this.i].descript;
// move wedge outwards 1/8 of the radius along bisecting angle, move others back in
// 120, 110 is piechart origin
for (var j=0; j<chartInfo.length; j++) {
if (j==this.i) {
this._x = 120 + 10 * Math.cos(chartInfo[this.i].bisectAngle * TO_RADIANS);
this._y = 110 + 10 * Math.sin(chartInfo[this.i].bisectAngle * TO_RADIANS);
} else {
this._parent["arc"+j]._x = 120;
this._parent["arc"+j]._y = 110;
}
}
};
// set up the legend box
lb.lineStyle( 1, 0x000000);
lb.beginFill( chartInfo[i].color, 100);
lb.drawBox(240, 30+i*30, 260, 50+i*30);
lb.endFill();
}
}
lv.load("textfiles/piechartsampledata.txt");
last update: 5 Oct 2005
Discussed on this page:
piechart, drawing api example, createtext, read from text file
Files:
none
Content is dynamically generated, code available below. Data source file viewable here.
A list of all files currently available at the site may be viewed here.
Other Resources
Do you love pie slices? Here is a lovely thing (that really has nothing to do with this page) from ze frank