AS Reference  :  Notes Index  :  Resources  :  About/Contact  :  Downloads

Creating an interactive, dynamically-generated pie chart


(code below)

Process description

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.

Reading and converting the data

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.

Calculating wedge properties

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.

Drawing the chart and legend

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.

Assigning event handlers to the wedges

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).

Making it go

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");  

Intro
Flash: What & How
Example Sites
Create
Draw, Edit Shapes
Gradients
More Drawing Tips
Import
A Sample
Animate
Frames, Keyframes
Motion Tweens
More Motion Tweens
Shape Tweens
Masks
Control
Stop/Replay
Movieclips Intro
Movieclip Reference
Site Structure 1
Slideshow Movieclip
Contact Form
Scroll Resume
Preloader
Site Structure 2
Publish
Display Options
Player Detection
Optimize
AS 2.0 Basics
Intro to Syntax
Playhead Commands
Playhead Cmds 2
Coded Tween
onEnterFrame
Intro to Classes
Declare/Assign
Comments, Trace
Simple Data Types
Arrays & Objects
Code Blocks
Operators
Beyond Buttons
Code Structure
Toggle Controls
Group of Buttons
Drag and Hit
Distort Magnifier
Scroll Text
Bee Game
Dart Shooter
Sound Control
Easing Slider
Easing Slider 2
Components Intro
Timers & Delays
Dynamic Content
Intro
Drawing API
Create Text
Attach Movieclips
Easing Slider 3
Easing Slider 4
Load jpg/swf
Sliding Viewer
Preload swf
XML
Easing Slider 5
Server Comm
LoadVars (w/ PHP)
AS - PHP Lookup
Text File
Database 1:LoadVars
Database 2:Remoting
Read from directory
AS 2.0 Classes
Intro
Math
Key
Date
Color
EventDispatcher
New Samples
Pie Chart
Event-model Emailer
Tween Sequence
Fuse Sequence
SVG in Flash
Bitmap Topo
SWF as Data Holder
Two-level Menu
Yahoo! Flash Maps
Class-based Game
ASTB Samples
Disclaimer
3D Outlines
Bounce Collide
Address Book
Save Drawings
Home  :  Notes Index  :  Resources  :  About/Contact  :  Downloads