Choosing Coordinates

The lion's share of the work in designing the f utval_graph program was in determining the precise coordinates where things would be placed on the screen. Most graphics programming problems require some sort of a coordinate transformation to change values from a real-world problem into the window coordinates that get mapped onto the computer screen. In our example, the problem domain called for x values representing the year (0-10) andy values representing monetary amounts (\$0-\$10,000). We had to transform these values to be represented in a 320 x 240 window. It's nice to work through an example or two to see how this transformation happens, but it makes for tedious programming.

Coordinate transformation is an integral and well-studied component of computer graphics. It doesn't take too much mathematical savvy to see that the transformation process always follows the same general pattern. Anything that follows a pattern can be done automatically. In order to save you the trouble of having to explicitly convert back and forth between coordinate systems, the graphics module provides a simple mechanism to do it for you. When you create a GraphWin you can specify a coordinate system for the window using the setCoords method. The method requires four parameters specifying the coordinates of the lower-left and upper-right corners, respectively. You can then use this coordinate system to place graphical objects in the window.

To take a simple example, suppose we just want to divide the window into nine equal squares, Tic-Tac-Toe fashion. This could be done without too much trouble using the default 200 x 200 window, but it would require a bit of arithmetic. The problem becomes trivial if we first change the coordinates of the window to run from 0 to 3 in both dimensions.

# create a default 200x200 window win = GraphWin("Tic-Tac-Toe")

# set coordinates to go from (0,0) in the lower left

# to (3,3) in the upper right. win.setCoords(0.0, 0.0, 3.0, 3.0)

# Draw vertical lines

Line(Point(1,0), Point(1,3)).draw(win) Line(Point(2,0), Point(2,3)).draw(win)

# Draw horizontal lines Line(Point(0,1), Point(3,1)).draw(win) Line(Point(0,2), Point(3,2)).draw(win)

Another benefit of this approach is that the size of the window can be changed by simply changing the dimensions used when the window is created (e.g. win = GraphWin("Tic-Tac-Toe", 300 , 300)). Because the same coordinates span the window (due to setCoords) the objects will scale appropriately to the new window size. Using "raw" window coordinates would require changes in the definitions of the lines.

We can apply this idea to simplify our graphing future value program. Basically, we want our graphics window to go from 0 through 10 (representing years) in the xdimension and from 0 to 10,000 (representing dollars) in the y dimension. We could create just such a window like this.

win = GraphWin("Investment Growth Chart", 320, 240) win.setCoords(0.0, 0.0, 10.0, 10000.0)

Then creating a bar for any values of year and principal would be simple. Each bar starts at the given year and a baseline of0 and grows to the next year and a height equal to principal.

bar = Rectangle(Point(year, 0), Point(year+1, principal))

There is a small problem with this scheme. Can you see what I have forgotten? The bars will fill the entire window; we haven't left any room for labels or margins around the edges. This is easily fixed by expanding the coordinates of the window slightly. Since our bars start at 0, we can locate the left side labels at -1. We can add a bit of whitespace around the graph by expanding the coordinates slightly beyond that required for our graph. A little experimentation leads to this window definition:

win = GraphWin("Investment Growth Chart", 320, 240) win.setCoords(-1.75,-200, 11.5, 10400)

Here is the program again, using the alternative coordinate system:

# futval_graph2.py from graphics import *

# Introduction print "This program plots the growth of" print "a 10-year investment."

# Get principal and interest rate principal = input("Enter the initial principal: ") apr = input("Enter the annualized interest rate: ")

# Create a graphics window with labels on left edge win = GraphWin("Investment Growth Chart", 320, 240) win.setBackground("white") win.setCoords(-1.75,-200, 11.5, 10400) Text(Point(-1, 0), ' 0.0K').draw(win) Text(Point(-1, 2500), ' 2.5K').draw(win) Text(Point(-1, 5000), ' 5.0K').draw(win) Text(Point(-1, 7500), ' 7.5k').draw(win) Text(Point(-1, 10000), '10.0K').draw(win)

# Draw bar for initial principal bar = Rectangle(Point(0, 0), Point(1, principal))

bar.setFill("green")

bar.setWidth(2)

bar.draw(win)

# Draw a bar for each subsequent year for year in range(1, 11):

bar = Rectangle(Point(year, 0), Point(year+1, principal))

bar.setFill("green")

bar.setWidth(2)

bar.draw(win)

raw_input("Press <Enter> to quit.") main()

Notice how the complex calculations have been eliminated. This version also makes it easy to change the size of the GraphWin. Changing the window size to 640 x 480 produces a larger, but correctly drawn bar graph. In the original program, all of the calculations would have to be redone to accommodate the new scaling factors in the larger window.

Obviously, the second version of our program is much easier to develop and understand. When you are doing graphics programming, give some consideration to choosing a coordinate system that will make your task as simple as possible.