Drawing the Graph

Knowing how to retrieve just the right data you need, and how to scale it to your viewing area, you can create the display method. The display method will use various imaging Lingo commands to draw rectangles, circles, and lines directly onto the Stage. Let's start by making a bar graph of the distance.

Creating a Bar Graph

To begin with, you need to determine the graph ranges we discussed earlier. To find the distance-scaling factor, you'll need to iterate through the list of returned data to find the maximum distance. Aside from a count of the number of days being graphed, that's all you'll need to draw the distance bars. But to draw the average speed, you'll need to take the time, stored as a string, and turn it into a useable number.


Stop the movie if it's playing and add the beginnings of the display method to the movie script.

 on display data   dataCount = data.count()   --get max distance   maxDist = 0.1   repeat with cnt = 1 to dataCount     curDay = data[cnt]     if curDay[2] > maxDist then maxDist = curDay[2]   end repeat   distanceScaler = 240.0 / maxDist   barWidth = 9 end 

First, the number of items in the data list is retrieved using the count method of the list object. Because data is a list, the number of items in it is stored in the dataCount local variable. The next six lines find the maximum distance and calculate the distance-scaling factor based on it. The maxDist variable is first set to 0.1, instead of 0 for a good reason. Note that in the last line, the total distance available (240) is divided by the maximum distance. In the case where there might not be any distance data, setting the variable to 0.1 first insures you won't receive a divide by zero error.

Recall that the data returned from Arca, in the rows property, is a list of lists, where each list in the main list is a single row of data from the table. As the repeat loop iterates through the list, each row (or day) is pulled out and stored in the curDay variable. Remember you retrieved the d, distance, and time fields from the database, so the distance is located in position 2 in the list. The if statement checks to see if the current distance is greater than the maximum. If it is, the maximum is set to the current and the loop continues until it's finished. The end result is that the maximum distance of the returned set of data is now stored in the maxDist variable. The scaling factor is then calculated by dividing the maxDist into the total vertical space available, 240 pixels.

The next thing to do is calculate the space between each bar. You can do that with just a few simple lines of Lingo.


Add the following code to the display method, just after the last line that calculates the distanceScaler .

 --calculate space between each bar  barWidth = 9  barSpace = barWidth * dataCount  spaceLeft = 560.0 - barSpace  spacePer = spaceLeft / (dataCount + 1) 

Remember that we decided that the width of the bars would be a constant 9 pixels, based on a maximum of 31 days, and 560 pixels wide, with space between each bar also being 9 pixels when the maximum number of bars is drawn.

With a bar width of 9 pixels, the total space the bars will occupy is 9 times the number of bars. The space remaining is then the total available space (560 pixels) minus the total bar space. Finally, the space between each bar is the remaining space divided by the number of bars plus one.

With this data in hand you can now draw the bar graph of the distance for the selected month.


Immediately after the code you just added to calculate the space between the bars, add the following code to draw the bars.

 --draw distance bars  pixColor = image(1, 1, 24)  pixColor.fill(pixColor.rect, rgb(255,176,89))  startPos = 36 + spacePer  bottomPos = 275  repeat with cnt = 1 to dataCount   curDay = data[cnt]   curDate = curDay[1]   curDist = curDay[2]   barHeight = curDist * distanceScaler   barRect = rect(startPos, bottomPos - barHeight, startPos + ¬     barWidth, bottomPos)    _movie.stage.image.copyPixels(pixColor, barRect, pixColor.rect)    --code for day text goes here    startPos = startPos + barWidth + spacePer    end repeat 

The first two lines create a single pixel image, filled with orange that will be copy pixeled to the correct size for each bar in the graph. The startPos variable determines what the left edge for the first and subsequent bars will be. Initially, it is set to the left edge of the graphing area, which is 36 pixels, plus the space per bar. The bottomPos is the bottom edge of the graphing area, of course. Next, a repeat loop iterates through each of the days in the returned list. Each day is first pulled from the main data list into a curDay local variable. Items 1 and 2 are then extracted from the current day list and stored in the curDate and curDist variables. The bar height is then calculated by multiplying the current distance (curDist) by the distanceScaler variable calculated previously. After that, a rect is created specifying the left, top, right, and bottom coordinates of the bar to be graphed. The following image should help show the relationships.

Once the rect defining the bar to graph has been created, the copyPixels command is used to draw the bar onto the Stage. Note how the single-pixel image stored in pixColor is stretched to fill the rect defined by barRect. After the bar is drawn, the startPos variable is incremented by the width of the bar, plus the space between each bar. The comment line will be replaced later, when you add the text for each day under its corresponding bar.


You can reformat your Lingo scripts at any time by pressing the Tab key.


Rewind and play the movie. With the movie playing, open the Message window and enter the following:


This will graph the distance values for biking, within the month and year defined in the global variables. You should have a graph that looks similar to the following.


Stop and rewind the movie, and then Save it. Save the movie with the name graph in the same folder as the main movie, which should be your project_two folder.

Notice that when you simply stopped the movie, the bars that were drawn in don't disappear. Only when you rewind the movie are the bars erased. This is because you modified the stage image, and stopping the movie doesn't do a refreshalthough rewind does, as you saw.

Now that the distance is being graphed properly, you can turn your attention to graphing the average speed. For this, you'll draw small circles over the bars, so that both sets of data can be viewed at the same time.

Splitting Strings

To be able to graph the average speed, you need to know both the distance traveled and the time it took to do so. While both values are being returned in the data from the database, the time value is a string in the form "hh:mm:ss". To be able to work with the time, you'll first need to convert it into a useable number. Because you want the average speed to be calculated in miles per hour, you should convert the time into a single number, representing the number of hours. What you need first is a way to split the string into its separate components, using the colon character as the delimiter.

There are a couple of ways you could go about this. Lingo has what's called the itemDelimiter, which allows you to specify a single character with which to break strings by. While I encourage you to read further on the itemDelimiter, you'll use a little bit of Director's new JavaScript syntax for this. The reason to use JavaScript syntax here is twofold: first because you have yet to use any, and second because using it here makes splitting the time string fairly easy.


Select the next empty cast member and press Ctrl/Command+0 to open a movie script window. Change the Script Syntax to JavaScript, using the drop-down at the far left of the scripts toolbar. Enter the following toHours function.

 function toHours (timeString){   ar = timeString.split(":");   if (ar.length == 3){     h = Number(ar[0]);    m = Number(ar[1]);    s = Number(ar[2]);    hours = h + (m / 60.0) + (s / 3600.0);  } else if (ar.length == 2){     m = Number(ar[0]);    s = Number(ar[1]);    hours = (m / 60.0) + (s / 3600.0);  } else {    s = Number(ar[0]);    hours = s / 3600.0;  }  return hours;  } 

First, JavaScript's split method is used to split the incoming string according to a character, the ":" in this case. The split method returns an array, which is nearly identical to a Lingo list, except that the array index begins at zero, while a Lingo list index begins at 1.

Once the string has been split into its component parts and placed into the ar variable, it is converted into hours by checking to see how many items are in the array. If there are three items, then a "hh:mm:ss" time was entered. If there are just two items, you can assume the time was entered as just "mm:ss". If just one item is in the array, it is assumed that only seconds was entered. Either way, the minutes component of the time is divided by 60 to convert it to hours, and seconds is divided by 3600 to do the same. The components are then added together to arrive at a discrete number of hours. That number is then passed back to the calling method using the return command.

And speaking of numbers, note the use of JavaScript's Number method, which is used to convert the string value entered by the user into an actual number that can be used in the calculations.

You should take note of the fact that you had to create the JavaScript in its own script member. This is because you can't mix Lingo and JavaScript in the same script, although you can call Lingo functions from JavaScript and vice versa.

Now that you can convert the time strings into regular numbers representing the hours, you can calculate the average speed.


Double-click to open the Main Script and add the following Lingo to the end of the display method:

 --calculate average speed  avgList = []  repeat with cnt = 1 to dataCount   curDay = data[cnt]   numHours = toHours(curDay[3])   dist = curDay[2]   avgSpeed = dist / float(numHours)   avgList[cnt] = avgSpeed  end repeat  maxAvg = avgList.max()  avgScaler = 240.0 / maxAvg 

First, an empty list is created in the variable avgList. This will be used to store the average speeds as they are calculated within the loop. Then the repeat loop iterates through the data list, pulling out the current day and storing it in the curDay variable.

The current time, at index 3 within the current day list, is then sent to the toHours method you wrote in JavaScript. The result is then stored in the numHours variable. Next, the distance is retrieved from the current day list, and finally the average speed is calculated by dividing the distance by the number of hours. Using the float function insures the result of the operation will be a floating-point number, instead of an integer. As an example of why this is important, examine the following output from the Message window.

 trace( 5 / 2) -- 2 

Were you expecting to see 2.5 instead of just 2? The result is 2 because you are doing integer division. As long as one of the numbers is a float, the result will be a float:

 trace( 5 / float(2)) -- 2.5000 

That's more like itand it produces the correct result.

After the average speed is calculated, its value is added to the average list (avgList).

Once the repeat loop finishes, you will have a list of average speeds for the month. To calculate where on the graph the averages should be placed, you need to calculate an average scaling factor the same way you calculated the distance-scaling factor. That is, to find the maximum average speed and divide it into the available vertical range of 240 pixels. This is accomplished using Lingo's max method. As its name implies, it will return the maximum value for a given list. That value is then divided into 240 and the result stored in the avgScaler variable.


For optimum speed you could combine the repeat loops that calculate the distance and average scaling factors. But for the sake of clarity I had you use separate loops.

With the list of average speeds and the average scaling factor, you're now ready to add the averages to the graph display.

Connecting the Dots

So that you can visualize both the distance and the average speed, you'll draw the average speed as a series of circles connected by lines. That way, they will interfere less with the bars being used to show the distance. To do that you'll use Lingo's draw method, which allows you to draw lines, ovals, and rectangles with varying color and line sizes. When you're through with this section, the graph will look something like the following.


If the Main Script isn't open, open it and then add the following Lingo at the end of the display method:

 --draw average speed circles and connectors  startPos = 36 + spacePer  lastSpot = 0  repeat with cnt = 1 to dataCount    vertPos = avgList[cnt] * avgScaler    circleTop = bottomPos - vertPos    circleRect = rect(startPos - 3, circleTop, startPos + 12, ¬     circleTop + 15)     _movie.stage.image.draw(circleRect,rgb(255,255,0), ¬       [#shapeType:#oval, #lineSize:8])     if lastSpot <> 0 then     _movie.stage.image.draw(lastSpot, point(startPos + 7, ¬       circleTop + 7), rgb(255,255,0))    end if    --code for average text goes here    lastSpot = point(startPos + 7, circleTop + 7)    startPos = startPos + barWidth + spacePer  end repeat 

By now, the code should be pretty self-explanatory, but let's go through it anyway. First, the startPos variable is reset to the left edge of the graphing area (36) plus the space between each bar. This will put the left edge in line with the first bar that was drawn. The lastSpot variable will be used to hold the center point of the last circle that was drawn on the graph. This will be used for drawing the connector lines between the circles.

A repeat loop is again used to iterate through the list. First, the vertical position for the average speed circle is calculated by multiplying the average by the average multiplying factor (avgScaler). That value is then subtracted from the graph's bottom (bottomPos) to arrive at the vertical position within the graph, for the circle. A 15x15 rect is then created that the circle will be drawn into:

 circleRect = rect(startPos - 3, circleTop, startPos + 12, circleTop + 15) 

Once the circleRect rectangle has been calculated, the circle is drawn into it using the draw command:

 _movie.stage.image.draw(circleRect, rgb(255,255,0), [#shapeType:#oval, ¬   #lineSize:8]) 

Note that the default for the draw command is to use a shapeType of line. If you don't specify the type of the shape to draw, you'll get a line from the upper left corner of the rectangle to the lower right corner. By specifying a shapeType of oval, however, an oval that fills the defined rectangle is drawn. Because the defined area is square, a circle is drawn.

Although the draw command isn't supposed to be able to draw filled shapes, you can actually do so quite easily by using a large enough line size. By using a line size half the size of the area being filled, the shape will be drawn solid. Because the circle's diameter is 15 pixels, a line size of 8 pixels fills the circle with solid yellow.

After a circle is drawn, an if statement checks the lastSpot variable to see if it is zero. Because it was initialized to zero, the if statement fails the first time through the repeat loop, which is what you want. Right after the if statement, lastSpot is set to a point variable, the point at the center of the circle just drawn. The next time through the loop lastSpot will not be zero and the if statement will be satisfied. When it is, a line will be drawn from lastSpot to the center of the current circle point (startPos + 7, circleTop + 7). In this manner a line will be drawn from the previous circle to the current circle, thus connecting the dots.

Notice how startPos is incremented in exactly the same way it was when you were drawing the bars. This is because you want to keep the circles centered on the bars. The line that creates the rectangle for the circle to be drawn into compensates for centering the circle on the specified point.

That's all there is to it. The graph will now show both the mileage and average speed for a full month's worth of data. And the data will dynamically scale to the available space.


Rewind and play the movie, then open the Message window. Enter showMonth("B") to graph the biking data for the selected month.

The graph appears showing the distance for each day of the month, as well as the average speed for the ride.

The last thing you need to do is add labels to the graph, as well as changing the title from September 2004 to the proper month and year.


Stop the movie and save it before continuing.

Macromedia Director MX 2004. Training from the Source
Macromedia Director MX 2004: Training from the Source
ISBN: 0321223659
EAN: 2147483647
Year: 2003
Pages: 166
Authors: Dave Mennenoh

Similar book on Amazon

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net