Now that you've gotten your feet wet using imaging Lingo, it's time to start the process of creating the graph for the training log. The graph will allow you to visualize mileage versus average speed for any month of the year.
You'll create this graph in a new Director movie. In the following lesson, you will open it as a Movie In A Window (MIAW).
Begin by opening the graph_start.dir file from the Lesson08 folder on the CD.
I've taken the liberty of creating and importing a suitable background image, as well as placing a few necessary text sprites.
Other than that, the movie is empty. Don't worry that the text sprites along the left side show 25100. The text is acting as a placeholder only so that it can be visualized while building the interface.
Before getting to the code, there are several points to consider when developing a movie that will be used as a MIAW. First, global variables are shared. That means that _global.theMonth is the same in the main application as it is within the movie in a window. Second, you can only edit one movie at a time. Sometimesand this is one of those timesthis will force you to create extra code that is duplicated between movies while testing. For instance, the startMovie handler in the main application creates the global myDB and then opens the exercise database. Eventually, you'll rely on the main movie having the database open. But for testing purposes, you'll need some code to open it within the graph movie as well.
Select the next empty cast member, then script window. Enter the following startMovie and stopMovie handlers, and name the new script main.
on startMovie _global.myDB = new(xtra "arca") _global.myDB.openDB(_movie.path & "exercise") end on stopMovie _global.myDB.closeDB() end
Notice you're not getting the return from the calls to the Xtra in order to look for an error. This is because you are already opening and doing all the error checking in the main movie. All you need here is the minimum necessary to open and close the database.
You should also add a standard loop on frame behavior to frame 10 so the movie pauses when played.
Double-click the behavior channel at frame 10 and add a standard loop on frame as shown here.
on exitFrame me _movie.go(_movie.frame) end
The next step is determining the ranges for your graph so that you can be sure the data will fit in the available space.
Determining the Ranges
Before you can go about creating a graph from the data, you need a method of fitting that data into the available space. Let's say your maximum daily mileage in June is 42, but only 25 in July. In both cases the max mileage bar should go to the top of the graph, so that the entire range is used.
The first thing you need to determine is the available pixel space where the graph is to be drawn. The easiest way to find that is to simply draw a rectangle shape over the background and get its dimensions, as shown here.
As you can see, I drew a plain black rectangle shape within the graphing area. The rect property of the rectangle, as noted in the Property inspector, is rect (36, 35, 596, 275). This yields a width of 560 and a height of 240.
Now, let's assume you queried the database for the month of July and got the following data back:
In order to properly scale the distances into the graph you first need to calculate a distance-scaling factor. You determine this by dividing the maximum distance into the total height of the graph.
You know the height of the graphing region is 240 pixels. The max distance is 22.5 miles, done on the 16th of the month.
240 / 22.5 = 10.67
You then multiply each of the distances you want to graph by the scaling factor to determine its height in the graph. Examine the following image.
The image illustrates how the distance is multiplied by the calculated scaling factor to determine the pixel height of the corresponding bar in the graph.
The other thing you need to determine is the horizontal spacing. It's easy, but a bit more complicated than the vertical. There are two ways you could create the graph: with variable-width bars, or fixed-width bars.
With variable-width bars, the amount of data returned by the query would determine how wide each bar would need to be.
If you go with a fixed-width bar, you need to first determine the maximum number of data points in the graph and then take into account the available space, as well as any spacing between the bars you require. In this graph, the maximum amount of data you'd ever be graphing would be 31 days' worth. If you take the maximum width of the graph, which is 560 pixels and divide it by the number of possible data points, 31, you get 18. With 31 days graphed, each distance bar could be 18 pixels wide, although with no space between them. For an equal amount of space, each bar should be cut in half, making it 9 pixels wide.
Because you'll also be graphing the average speed, and should keep a consistent look, we'll use a fixed width of 9 pixels for the distance bars. Of course, what happens when you only have five days to graph, as in the example? If you made the space between the bars the same width as the bars they would all be bunched together. To determine the spacing between the bars you add up the number of bars and multiply by the width of each bar to determine the total width the bars are using. Subtract that from the total available space, and you get the remaining blank space. Divide the remaining blank space by the number of bars plus one, and you'll get the spacing between each bar. You need to use the number of bars plus one so that you have even space between each barand also between the edges of the graph. Do thishold up two fingers on your right hand and imagine those are bars in your graph. There is one space between your fingersand a space on the outside of each fingerfor two fingers there are three spaces. Get the picture?
Continuing with the sample data for the example:
5 days being graphed at a width of 9 pixels each = 45 pixels total
Width of graph = 560 pixels
560 45 = 515 pixels of blank space
515 / 6 = 85 pixels per space
Now that you have an understanding of how to calculate the range the graph will occupy, you can select the data to be graphed.
Getting the Data
In the last lesson, you learned about using SQL's SELECT statement in order to retrieve data from the database. In this lesson, you'll expand on that knowledge to learn how to select only the necessary data. There's no reason to select an entire table when you only need a few columns. It may not matter for such a small database, but it will certainly matter on larger ones.
Let's begin by creating a method within the movie script to select the data from the database file.
Open the main script and then add the following geTData method:
on getData whichType err = _global.myDB.executeSQL("SELECT d,distance,time FROM data ¬ WHERE m=? AND y=? AND type=? ORDER BY d",[_global.theMonth, ¬ _global.theYear, whichType]) errCode = err.errorMsg if errCode = 0 then return err.rows else return errCode end if end
When getdata is called it uses the executeSQL method of the Arca Xtra to perform the following SELECT command.
SELECT d,distance,time FROM data WHERE m=? AND y=? AND type=? ORDER BY¬ d,[_global.theMonth, _global.theYear, whichType]
As before, Arca automatically substitutes the values in the list for the question marks in the command. In this case, the data is selected where m = _global.theMonth and y = _global.theYear and type = whichType. Selecting the data by type, and not just the date, will allow you to create separate graphs for biking, running, and walking, instead of combining all three into one.
Notice that instead of selecting data from all the fields in the data table using a wildcard character, you're only selecting data from the d, distance, and time fields.
Another new addition is the ORDER BY command, which has the effect of ordering the returned data according to a specific field. Because data can be placed into the database in non-linear fashion, the returned data won't necessarily be ordered by the day of the month, as it should be. Note that the default ordering is ascending, although you can switch it if you need to.
Once the data is retrieved the errorMsg property is checked and if zero, the data is returned by way of the rows property. If an error occurred, and the returned errorMsg is not zero, then the error number itself is returned.
Note that using return allows you to send data back to the calling function. Take for example the following very simple doReturn function:
on doReturn a = 5 return a end
If you then trace the output of the function in the Message window, you'll see that 5 is returned:
trace(doReturn()) -- 5
In the same regard, you can set a variable to the result:
variableName = doReturn() trace(variableName) -- 5
As you can see, using return allows you to return a value to the initiating call.
Now that the proper data can be selected from the database, you need a way to call the geTData method. Because the graphing movie will be running as a MIAW, you'll need a method that can be called from the main movie whenever necessary.
Add the following showMonth method to the movie script.
on showMonth exType data = getData(exType) if ilk(data) <> #list then --window("stage").movie.showError(data) trace("error") else display(data) end if end
First, the getdata method is called, with the type of data to be retrieved, and the value returned is placed into the data variable. Next, Lingo's ilk() method is used on the variable to find out what type of information is being held. Lingo's ilk method is nearly identical to that used in normal language. (If you look up "ilk" in the dictionary you'll see that it means type or kind; "You can't trust people of that ilk.") Because the return value could either be a number (if an error occurs) or a list (if data is returned) you need a way to know how to react, depending on its type. If the data is not a list you will see the word error output to the Message window. The line that is currently commented out will be uncommented later, when you implement the graph as a movie in a window. When it is running as a MIAW the commented line will call the showError method in the main movie, in order to display the error string.
If the data returned is a list, then the display method is called, and the data is passed to it.
Before moving on to drawing the graph, let's take a brief look at Director's debugger.
Open the Message window and enter the following.
theMonth = 7 theYear = 2004
You need to do this step in order to place values into the global variables because they are used by the SQL statement to retrieve the data from the database. These globals will be set by the main application and shared by the MIAW. But during testing, they need to be initialized, or nothing will be selected from the database. Note that you should use a month that you have entered data for as well.
Play the movie. Open the Message window and enter showMonth("B"). You should receive a script error telling you that the display handler is not defined, as shown.
Because you haven't written the display method yet, Lingo generates an error when you try to call it. However, this error is telling you that the SQL statement is working, and data has been retrieved from the database. If you don't receive this error, it is because no data was selected, and the display method was not called. If that is the case, you may have failed to add data to the database, as instructed in Lesson 7. You should save the graph movie, and repeat the last section in Lesson 7 to be sure you add data to the database. Without it, you won't be able to draw the graph.
Press the Debug button within the error alert, to open Director's debugger. Within the debugger you'll see the code panel on the right-hand side. Note the small green arrow, pointing out the line where the error happened: display(data). On the left-hand side of the debugger are three windows. The top one is the call stack, and lists the names of any handlers, or methods, that were running when the error occurred. The middle panel lists any variables, and has tabs for allowing you to switch between All, Local, Property and Global, making it easy to find what you're looking for in case you have a lot of different variables. The lower panel is the watcher; it allows you to add a variable to it and it will then show the value of the variable, as the program is run. This can be a very handy diagnostic tool for finding bugs in your code.
Click the small plus sign (+) to the left of the data variable, to expand the variable.
If the type of the variable is a list, it will be able to be expanded, allowing you to examine each individual item in the list.
You'll use the debugger again, in the next project. For now, you can close it, and write the display method that will draw the graph.