Articles Archive
Articles Search
Director Wiki
 

Piecharts and Director

November 3, 1998
by Michael Geary

One of our project managers recently asked me if it was possible to create dynamic pie charts with Director. "Of course." I immediately replied. He left, gratified that he had added another bomb to the arsenal he can drop on me at will, and I, ears still ringing from shellshock from the _last_ bomb that hit me, decided I'd better put my Lingo where my mouth is.

First a quick look at Director: We cannot yet create simple Quickdraw polygons with or without Lingo, much less pie wedges. This left two possibilites: sweeping a line with trails on, or dynamically assembling several premade 1-bit wedge castmembers, whose forecolor would be determined at Pie generation.

The second option is one that some have decided upon. The big advantage is that your graphics can be as crisp and anti-aliased as you want to make 'em. Another advantage is that your pie-charts will be printable, whereas anything created with trails is screenbound. For an example of this solution, check out Michael Maffie's Chance Encounters.

I opted for the line-sweeping solution. Its advantages include skinniness (my shockwave version is 2.7k), and dynamic re-sizeability. Technically another advantage is accuracy, because with the wedge-solution above, your accuracy is limited by the number of wedges you make.

So, here is the process:

First let's start with a fake master handler--one that gives us an idea of what isolated functions we'll need to draw upon to create a pie chart. We'll call it MakePie:

(fake lingo)


on MakePie
  identify what slices we need to make
  identify a circle sprite on stage
     
  find the circle's center point
  repeat with each segment
     
    sweep a line for each segment
  end repeat
end

To keep things reusable, let's let the first two points of information change every time, that is, we will pass them as parameters to the function. That way we could theoretically have 120 different circles on stage and have each of them create different pie charts.

To begin we need a quickdraw circle, any size, on stage. We also need a line sprite, which will be dynamically controlled by these handlers.

Jumping into to Real Lingo now, we will pass this master handler two parameters:

SpriteNumber: The channel of the quickdraw circle you've placed onstage
ListOfPercentages: a list of percentages which will be represented as slices of the pie


on MakePie SpriteNumber, ListOfPercentages 
  -- accepts data in the form: 
  -- MakePie (7,[10,20,30,20,20])
  -- identify the wand (the line which 
  -- draws the segments
  set WandSprite = 2
  -- identify the center point of the circle sprite
  set PieLeft = the left of sprite SpriteNumber
  set PieRight = the right of sprite SpriteNumber
  set PieTop = the top of sprite SpriteNumber
  set PieBottom = the bottom of sprite SpriteNumber
  set PieDiameter = PieRight - PieLeft
  set PieRadius =(.5 * PieDiameter)
  
  --this is the H & V of the actual center 
  -- point of the circle
  set PieCenterH = (PieLeft + PieRadius)
  set PieCenterV = (PieTop + PieRadius)

  --establish a starting point for the wand
  set CurrentDegree = 0
  
  --establish a variable which we will use to 
  -- generate a random color for each segment
  set SegmentCount = 1
  
  --now we loop through the list of percentages
  --and draw a slice for each item
  repeat with thisSlice in ListOfPercentages
    
  --convert percent to degrees
  set theDegrees = thisSlice/100.0*360 + CurrentDegree
    
  --move the amount of degrees clockwise
  --this handler is described below
  DrawSegment (WandSprite ,CurrentDegree, theDegrees ¬
    ,PieRadius,point(PieCenterH,PieCenterV))
    
  --prepare for next segment
  set CurrentDegree = theDegrees    
    
  --change color. Note that this does not guarantee 
  -- a unique color for each segment
  --but it does make it pretty darn likely.
  --A better solution might be to pass the color 
  -- along with the number of slices.
  --this will prevent the wand from changing color
  -- when it's done
  if SegmentCount <> count(listOfSegments) then
    set the forecolor of sprite WandSprite = ¬
      (SegmentCount * 10) + random(100)
  end if
    
  set SegmentCount = SegmentCount + 1
    
  end repeat
  
end

Okay. So our master handler is done, but it relies upon an external handler: DrawSegment. This DrawSegment handler accepts a number of parameters. I find it is often useful to isolate specific procedures and modularize them. You never know when you might need to draw a pie segment without necessarily having to draw an entire pie... :)

So let's take a look at DrawSegment. It accepts the parameters:

WandSprite (which sprite is acting as the sweeping line?)
StartDegree (where on the circle are we presently pointing?)
EndDegree (to what degree should we sweep?)
Radius (how big is the circle?)
StartPoint (what are the coordinates of the center of the circle?)


--here is where we actually draw a slice of pie. 
--We give it degree starting and ending points
--as well as the circle radius and the center point 
-- of the circle
on DrawSegment WandSprite, StartDegree, EndDegree,¬
  Radius,StartPoint
  --turn on the trails of the wand
  set the trails of sprite WandSprite = true
  
  --now simply rotate the wand, which is composed 
  -- of two quickdraw lines
  --from the starting degree to the ending degree
  repeat with thisDegree = StartDegree to EndDegree
    --Aha! another sub-handler!
    --this one (described below) simply points the 
    -- line at a particular
    --degree point on the circle
    pointToDegree(WandSprite,thisDegree,Radius ¬
      ,StartPoint)
    updatestage
  end repeat
  
end

By now you may be wondering why we have all these sub-handlers. Yes, it certainly would have been possible to keep the code portable and put it all in one handler. However, having compartmentalized handlers is really a great thing. It keeps your code clean and readable, it allows you to quickly pinpoint what sections of your code are having problems, and it allows you to take small 'utility' handlers very easily from project to project.

Having said that, here is the PointToDegree handler. It accepts the following parameters:

WhichLineSprite: again, this is the wand sprite
WhichDegree: to which degree should the line point?
theRadius: how long should the line be?
CenterP: what is the center point of the circle?

By converting the radius and the degrees, both of which we already know, to a handler which converts from polar coordinates to cartesian (X,Y) coordinates, we can get an X and Y coordinate location for the point on the circle to which our line should point.

This handler actually simply produces the locH and locV of the outer point of the line. We will rely upon another handler to actually slap the line onstage.


on PointToDegree whichLineSprite,whichDegree¬
  ,theRadius, CenterP
  
  --feel free to change this for a thicker
  -- /thinner line
  set LineThickness = 2

  --we will consider the center of the circle 
  -- point zero  
  set ZeroX = the locH of CenterP
  set ZeroY = the locV of CenterP
  
  --get the degree in radians. This is necessary 
  -- for the cartesian to
  --polar conversion which happens next
  --Here again we have a small 'utility' hander 
  -- called degToRad (see below)
  set theDegree = degToRad(whichDegree)
  --We know where the starting point of the 
  -- line is (CenterP) now we will find out 
  -- the ending point
  --this is the polar to cartesian conversion
  set X2 = ZeroX + (theRadius * Cos(theDegree))
  set Y2 = ZeroY + (theRadius * Sin(theDegree))
  
  set X1 = ZeroX
  set Y1 = ZeroY
  
  --now we have our four points which define 
  -- the starting and ending point of the line 
  -- we wish to draw
  
  --place the line
  --Yup, another sub-handler
  drawLine (whichLineSprite,point(X1,Y1),point(X2, ¬
    Y2), LineThickness)  
 
end

We're just about finished now. As you can see, this has really simply been a process of identifying encapsulated functions, and creating lingo tools to do these.

Our final 'real' handler comes to us by way of Jim Collins (thanks for all your contributions to Direct-L, Jim!). Because a Quickdraw line in Director is not a line at all, but a rectangle whose registration point changes depending on where the line is pointing, we need two lines to do the trick. One line point up to the right '/' and the other line points up the the left '\'. This DrawLine handler will determine, based on the two points we give it, which member to use, and will place that member in the sprite channel onstage.

The parameters it accepts are:

LineSprite: Which sprite channel is the wand using?
PointA: the origin (center of the circle) of the line
PointB: the point on the circle where the line terminates
LineWidth: How fat do you want your line?


on drawLine lineSprite, pointA, pointB, lineWidth
  
  --break points down into X's and Y'x
  set x1 = the locH of pointA
  set y1 = the locV of pointA
  set x2 = the locH of pointB
  set y2 = the locV of pointB
  
  --determine which line to use, this is the key
  if (x1 - x2) * (y1 - y2) > 0 then
    set the member of sprite lineSprite to ¬
      member "lineLeft"
  else
    set the member of sprite lineSprite to ¬
      member "lineRight"
  end if
  
  --adjust the thickness of the line. Fortunately 
  -- Director does this for us
  set the lineSize of sprite lineSprite to lineWidth
  --now we find the actual points of the rectangle 
  --of  our quickdraw line
  set leftAdjust = lineWIdth / 2
  set rightAdjust = lineWIdth - leftAdjust
  
  set left   = min (x1, x2) - leftAdjust
  set right  = max (x1, x2) + rightAdjust
  set top    = min (y1, y2) - leftAdjust
  set bottom = max (y1, y2) + rightAdjust
  --implement the calculations  
  set the rect of sprite lineSprite to rect (left, ¬
    top, right, bottom)
  --Boom. Finally we get to see something on stage  
         
  updateStage
  
end drawLine

So, we're pretty much done. We cannot, however, leave out three handly utility functions which assist with polar coordinates. Their names are pretty much all the commentary they need. We only actually use the degToRad function, but the others will definitely be handy for the future:


on degToRad degAngle
  return degAngle*pi()/180.0
end

on radToDeg radAngle
  return radAngle*180.0/pi()
end

on PolarToCartesian r,theta
  
  --get theta in radians
  set NewAngle = degToRad(theta)
  
  set theX = r * cos(newangle)
  set theY = r * sin(newangle)
  
  return [theX,theY]
  
end

To sum it all up, we now have one master handler which is very easy to use, and which automatically draws upon sub-handlers as needed. We keep our hands clean, and our code is easy to expand and modify should we ever need to.

Here are the handlers we've made and used:

MakePie
DrawSegment
PointToDegree
drawLine
degToRad

and we've got two bonus handlers:

radToDeg
PolarToCartesian

This is obviously just a beginning. This pie chart generator could certainly be improved upon. This scheme is somewhat size-dependent in that when our line gets too thick (if we were doing a circle larger than about 225 pixels in diameter), our center point gets a bit distorted. There is also no guarantee that the pie slices will be a different color--only a very strong likelihood.

I'd be interested in hearing about your improvements or other experiences with this code. I can be reached at mgeary / at / antwerpes.de or mgeary / at / active-design.com.

A sample movie is available for download in Mac or PC format.

Michael Geary started working with Director at version 4. His pet technologies include multimedia databases, dynamic PDF generation, Binary file generation and XML. After tromping around the world for a while, he has settled down in Utah again. Boy, those mountains sure are big.... Michael can be reached at michael.geary@seranova.com

Copyright 1997-2024, Director Online. Article content copyright by respective authors.