Simple Graphics in Python

by SysOps
This entry is part 1 of 3 in the series Simple Graphic in Python

For the beginning Python programmer, getting started with command line applications is the first step. At some point however, even a NooB will get bored with the terminal window and desire something more. Yet, many of the graphics and gui libraries for Python can be overwhelming for a novice. Prof. John Zelle saw the need for a simple graphics library for his students and went searching. He didn’t find anything he felt was suitable for the truly novice python student. So, he created a new graphics library to fill this niche’. John’s library was used in his book “Python Programming An Introduction to Computer Science”. Which has seen three editions so far.

John’s library is great for teaching and keeping students interested. It’s simple, lacks complex features, and is straight-forward to use. John has provided great documentation. However, many new pythonistas will desire examples.

In searching for examples I came across a short series on youtube.com.   That series, though well done and well received , was also incomplete. There had been sever posts requesting updates but those have yet to surface. For that reason I decided to do a series of articles showing some sample code and just what can be accomplished with John’s great little library. So here goes:

As a software developer you must get used to reading documentation. You can find the documentation for John’s library at : http://mcsp.wartburg.edu/zelle/python/. The best thing you can do for yourself is read this documentation. It is short, only 8 pages. Then come back here and follow along with the examples.

Before we can start drawing on a window, we need to get our environment set up. First, we need John’s graphics library. Second, we need to place it in the same folder as our code because it is not part of the python standard libraries. You can download the graphics.py file from John’s site here: http://mcsp.wartburg.edu/zelle/python/graphics.py. Once you have the library place it in the same folder as your project file. It is important that you place the “graphics.py” file in the same folder as your code file. Otherwise python may not be able to locate it. Also note that you will tkinter (python-tk) installed on your machine to use John’s library.


While Completing this series of blog posts, I had a minor issue. The issue turned out to be that I misunderstood the way autoflush=false worked, we’ll cover that much later in this series. I wrote John and he set me straight on the inner workings of the update() method, (also covered later). In his reply he informed me that his library can now be installed using pip. To install with pip run this command:

pip3 install –user http://bit.ly/csc161graphics

You can find more info here: http://www.pas.rochester.edu/~rsarkis/csc161/python/pip-graphics.html

Now create a folder for your projects. We will call this folder “graphics-zelle” and it will be placed in a “projects” folder.  Finally, we will create a sub-folder for each exercise we do. Create a file named ex-01.py and add the code below.

Ok, now let’s write some code!

Opening a window

To open a window that we can draw on requires two simple steps. First, import the graphics library file and second, instantiate a window object and assign it to a variable so we can access it later.

The first step is simple:

from graphics import *

Once we have imported the library we can instantiate the window by calling the GraphWin function and passing in the window title and it’s width and height. We’ll do this in function we’ll call main. Lastly, we need to call the main function to execute the program.


# Define a main function and instantiate the window object.
def main():
    window = GraphWin("Exercise-01", 640, 480)

main() # Call the main function 

Putting this all together we get:

"""
Prog:   ex-01.py

Auth:   R. Morgan

Desc:   Demonstrate how to use John Zelle's
        graphics.py library to open a window
        on the desktop.

Lic:    This code is placed in the public domain.

"""

# Creating a gui window requires only two steps
# Step 1: import the graphics.py library into 
# your local folder.

# Step 2: Instantiate a GraphWin object and assign
# it to a variable, passing the window title, and
# size parameters.

# Demo
from graphics import *

def main():
    win = GraphWin("Window Title", 640, 480)

    
main()

Now run ex-01.py. If you’re quick you’ll see a window pop open and immediately close. What’s going on here?

What’s going on is the program is doing exactly what was asked of it. It creates a window and then exits. So how do we stop this from happening? The easiest solution for the moment is get the program to wait for some input, until we close the window. We can add one line of code to ex-01.py to ask it to wait for a mouse click before exiting.


"""
Prog:  ex-01.py

Auth:   R. Morgan

Desc:   Demonstrate how to use John Zelle's
        graphics.py library to open a window
        on the desktop.

Lic:    This code is placed in the public domain.

"""

# Creating a gui window requires only two steps
# Step 1: import the graphics.py library into 
# your local folder.

# Step 2: Instantiate a GraphWin object and assign
# it to a variable, passing the window title, and
# size parameters.

# Demo
from graphics import *

def main():
    win = GraphWin("Window Title", 640, 480)
    win.getMouse()

main()

Now run the program. You should see the window open and stay open until you click the close button on the widow’s frame.  Clicking the window frame close button causes python to ask the OS and interpreter to clean up the window we created. This is a very poor practice. If we want the window destroyed, we should explicitly close it. We can do this by calling the close method on the window object. Modify your code as show below:


"""
Prog:   ex-01.py

Auth:   R. Morgan

Desc:   Demonstrate how to use John Zelle's
        graphics.py library to open a window
        on the desktop.

Lic:    This code is placed in the public domain.

"""

# Creating a gui window requires only two steps
# Step 1: import the graphics.py library into 
# your local folder.

# Step 2: Instantiate a GraphWin object and assign
# it to a variable, passing the window title, and
# size parameters.

# Demo
from graphics import *

def main():
    win = GraphWin("Window Title", 640, 480)
    win.getMouse()
    win.close()    

main()

This is much better. Now we can close the window simply by clicking on it. Still not a perfect solution but it at least allows the GraphWin object to clean itself up.

Drawing Points

When I first started programming PC’s back in the 1980s, I learned that the first step to drawing anything on the screen was to draw a simple point. In a Compuserve chat with Andre’ LaMonthe (then a hot shot game developer), he told me if you can draw a pixel, you can draw anything! Take that to heart. It’s the most basic object you can put on the screen. A simple point of light and everything else is simply built up out of many of these points of light. So, that’s our first task is to draw a point. 

Create a file called ex-02.py and in it add the code below:

"""
Prog:   ex-02.py

Auth:   R. Morgan

Desc:   Demonstrate how to use John Zelle's
        graphics.py library to draw a point 
        in the window.

Lic:    This code is placed in the public domain.

"""

from graphics import *

def main():
    win = GraphWin("Exercise-02, Points", 640, 480)
    # Our first point
    p = Point(320, 200)
    win.getMouse()
    win.close()
    
main()

Now if you run this code you may not see a point. It depends on you’re desktop’s default background color. On my desktop I have a darkula theme and my windows have a dark background. The default color in the graphics library is black and so in my window the point is very difficult to see.

To solve this we need to change the color of the point to something more easily seen. Before we can do that, we need to talk about how computer monitors display color.

Each pixel (picture element) on the screen is actually made up of fields. There is a field for each color channel, Red, Green, and Blue. From these three colors we can make almost any color. You may be saying wait? how can that be? These are not the primary colors I learned about in school. We’ll the colors you were taught were the primary colors are indeed one set of primary colors called the Sink or Subtractive primary colors.  When you add these colors (such as mixing paint or crayons) the color get darker. This is because the colors are reflecting light and adding another color means you’ll be reflecting more light off the surface. However, there is another set of primary colors known as the Source or Additive colors. These colors come from light sources so adding more colors means they get brighter. These are the primary colors used in computer monitors and light emitting devices.For the more curious readers checkout: https://stackoverflow.com/questions/6531536/why-rgb-and-not-ryb and http://en.wikipedia.org/wiki/Additive_color and http://en.wikipedia.org/wiki/Subtractive_color.

The graphics library we’re using has a special function for setting the values of these color channels. The color_rgb() method returns a color object that can be used in many of the library’s drawing methods. We simply need to pass in the values for the r, g, and b color channels. The values for these channels must be integers and be between a value of 0 and 255 inclusive. If we set a channel’s value to 0 it will turn that channel off. For example, if we pass (0,255,127) we are telling the system to add 0 red to the color, full green to the color, and half the available blue to the color. We can generate black by passing (0,0,0) and telling the system to add 0 red, 0 green and 0 blue to the color. We can also generate white by passing (255, 255, 255) to the system. And we can generate a gray scale of 256 levels from black to white by passing equal amounts of each color ex: (68, 68, 68).


"""
Prog:   ex-02.py

Auth:   R. Morgan

Desc:   Demonstrate how to use John Zelle's
        graphics.py library to draw a point 
        in the window.

Lic:    This code is placed in the public domain.

"""

from graphics import *

def main():
    win = GraphWin("Exercise-02, Points", 640, 480)
    win.setBackground(color_rgb(0,0,0))

    # Our first point
    p = Point(320, 200) 
    p.setFill(color_rgb(255,255,255))
    p.draw(win)
                    
    win.getMouse()
    win.close()
    
main()

Looking at the code we can see we call setBackground() on the window object, passing it a color_rgb object set to produce a black color. This simply sets our windows background to black as you would expect. Next, we create a Point() passing in the x and y coordinates of where it should be drawn. In most computer graphics systems the upper left corner of the screen will be (0,0). The x coordinate grows larger as we travel across the screen from left to right, and the y coordinate grows larger as we travel from the top down. This is a Cartesian coordinate system using only the fourth quadrant and taking the absolute value of the y axis. If that sounds confusing, see: https://www.mathsisfun.com/data/cartesian-coordinates.html or simply google Cartesian Coordinate System.

Next, we see that call the setFill() method on our point and pass in a color_rgb object with three equal values for red, green, and blue. So this will set the point’s color to white. Lastly, we call the draw method on the point so that it is displayed on the screen.

Run the code and you should see a small white dot in the middle of a window with a black background. It’s really that simple.

Now that we can draw a point in the window, let’s have some fun! First, let’s put what we’ve learned about setting colors and plotting points to work. We’ll create a little program to fill our screen with random points of random colors. Enter the code below:


"""
Prog:   ex-02.py

Auth:   R. Morgan

Desc:   Demonstrate how to use John Zelle's
        graphics.py library to draw a point 
        in the window.

Lic:    This code is placed in the public domain.

"""

from graphics import *
from random import randint

def main():
    width = 640
    height = 480

    win = GraphWin("Exercise-02, Points", width, height)
    win.setBackground(color_rgb(0,0,0))

    # Draw 10,000 points in the window
    # Each of a random color and at a
    # random position.
    for i in range(10000):
        # Get random position
        x = randint(0, width)
        y = randint(0, height)
        p = Point(x, y)
    
        # Get random color
        r = randint(0, 255)
        g = randint(0, 255)
        b = randint(0, 255)
        p.setFill(color_rgb(r, g, b))
        p.draw(win)
                    
    win.getMouse()
    win.close()
    

main()

Inspecting the code above the first thing we see is that we’ve imported the random library’s randint() method. We’ll use this method to generate random integer values for our point’s location and it’s color values.

Next, we see we created variables width and height to hold the width and height of the window. We then pass these variables to the GraphWin() method. Then we create a simple loop that iterates from 0 to 9,9999, for a total of 10,000 values. Within this loop we generate a point with random x and y coordinates and then generate a random color and set that color for the point’s color. Then we simply iterate and do it all again until we’ve filled the window with 10,000 random points of random colors.

Drawing Lines With Points

Ok, that was fun. But, we want to be able to draw more than just points. So, let’s try drawing a line. Now, we’ll use one of the most basic line drawing algorithms available. It’s the Bresenham line algorithm. This was the first line drawing algorithm I learned and perhaps the simplest. So it’s a great starting point for us. If you want to learn more about this algorithm check out Wikipedia’s article here: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm.

But first, we'll start by drawing a simple horizontal line as this and the purely vertical lines are the simplest case.

We'll add a horzLine function to our code. To draw a horizontal line all we need to do is draw a series of points starting a (x1,y) and continuing to (x2,y). Notice the y coordinate remains constant for a horizontal line

Create a new file called ex-03.py and add the following code:


"""
Prog:   ex-03.py

Auth:   R. Morgan

Desc:   Demonstrate how to use John Zelle's
        graphics.py library to draw horizontal
        lines at random positions and colors
        in the window.

Lic:    This code is placed in the public domain.

"""

from graphics import *
from random import randint

def horzLine(x1, y1, x2, color, win): 
    for x in range(x1, x2):
        p = Point(x, y1)
        p.setFill(color)
        p.draw(win)


def main():

    width = 640
    height = 480

    win = GraphWin("Exercise-03, Lines", width, height)
    win.setBackground(color_rgb(0,0,0))

    # Draw 10,000 points in the window
    # each with a random color and at a
    # random position/
    for i in range(1000):
        # Get random position
        x1 = randint(0, width)
        x2 = randint(0, width)
        y = randint(0, height)
            
        # Set random color
        r = randint(0, 255)
        g = randint(0, 255)
        b = randint(0, 255)

        # Draw our line using generated coordinates and color
        horzLine(x1, y, x2, color_rgb(r, g, b), win)
                      
    win.getMouse()
    win.close()
    
main()

As you can see I’ve added a function to create a horizontal line given the start and end points on the x axis and the y location on the y axis.  All we have to do is walk along the path drawing points until we reach the end of the line. Now it’s your turn. Create a function for drawing a vertical line and call it from the existing loop.

Hopefully, you were able to knock that code out quickly. If not, here’s a hint. All that is needed is to loop over the y coordinate just as we looped over the x coordinate in the horzLine function. Then in the main function’s loop we generate a single x coordinate and a y1 and y2 coordinate for our new line.  Here’s my solution:

"""
Prog:   ex-03.py

Auth:   R. Morgan

Desc:   Demonstrate how to use John Zelle's
        graphics.py library to draw lines 
        at random positions and colors
        in the window.

Lic:    This code is placed in the public domain.

"""

from graphics import *
from random import randint

def vertLine(x, y1, y2, color, win):
    for y in range(y1, y2):
        p = Point(x,y)
        p.setFill(color)
        p.draw(win)

def horzLine(x1, y1, x2, color, win):  
    for x in range(x1, x2):
        p = Point(x, y1)
        p.setFill(color)
        p.draw(win)

def main():

    width = 640
    height = 480

    win = GraphWin("Exercise-03, Lines", width, height)
    win.setBackground(color_rgb(0,0,0))

    # Draw 10,000 points in the window
    # each with a random color and at a
    # random position/
    for i in range(1000):
        # Get random position
        x = randint(0, width)
        y1 = randint(0, height)
        y2 = randint(0, height)
        
            
        # Set random color
        r = randint(0, 255)
        g = randint(0, 255)
        b = randint(0, 255)

        # Draw our line using generated coordinates and color
        vertLine(x, y1, y2, color_rgb(r, g, b), win)
                    
    win.getMouse()
    win.close()
    
main()

If you run this code you’ll see random vertical lines drawn at random locations and in random colors.  I think that was pretty straight forward.

Now what about lines that are not horizontal or vertical? How do we draw them? Well we will use a version of Bresenham line algorithm to handle this. This algorithm is designed to handle lines at arbitrary angles.  Therefore it can draw any line in our 2d window. The basic idea of the algorithm is to compute a step and direction such that the point positions fall on integer (x,y) coordinates. This is important as some of the pixels would typically fall partially on two separate (x,y) coordinates. That’s a problem because we can’t draw a partial point. So the algorithm calculates how far we should move in one direction before we take a step in the other direction, so that all our points end up on integer coordinates. For example, how far we should move horizontally before moving vertically. You can find a simple discussion of the algorithm here: https://www.tutorialspoint.com/computer_graphics/line_generation_algorithm.htm and a more in depth discussion at: https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm. For those who prefer videos, watch this youtube video for more details: https://www.youtube.com/watch?v=zytBpLlSHms. If you want some truly awesome and detailed text on the subject of computer graphics check out the Graphic Gen series. While they may be older books the information in them is still very relevant.

Ok, so let’s see how we can implement a simple version of this:


"""
Prog:   ex-03.py

Auth:   R. Morgan

Desc:   Demonstrate how to use John Zelle's
        graphics.py library to draw lines 
        at random positions and colors
        in the window using points.

Lic:    This code is placed in the public domain.

"""

from graphics import *
from random import randint

# Bresenham's line drawing algorithm
# to handle lines of any orientation
def line(x1, y1, x2, y2, color, win):
    # Calculate dx, sx
    dx = x2 -x1
    if dx < 0:
        sx = -1
    else:
        sx = 1
    # calculate dy, sy
    dy = y2 - y1
    if dy < 0:
        sy = -1
    else: 
        sy = 1
  
    if abs(dx) > abs(dy):
        slope = dy/dx
        pitch = y1 - slope * x1
        while x1 != x2:
            y = slope * x1 + pitch
            p = Point(x1,y)
            p.setFill(color) 
            p.draw(win)
            x1 += sx
    else:
        slope = dx/dy
        pitch = x1 - slope * y1
        while y1 != y2:
            x = slope = slope * y1 + pitch
            p = Point(x,y1) 
            p.setFill(color) 
            p.draw(win)
            y1 += sy  


def vertLine(x, y1, y2, color, win):
    for y in range(y1, y2):
        p = Point(x,y)
        p.setFill(color)
        p.draw(win)


def horzLine(x1, y1, x2, color, win):  
    for x in range(x1, x2):
        p = Point(x, y1)
        p.setFill(color)
        p.draw(win)


def main():

    width = 640
    height = 480
    color = None

    win = GraphWin("Exercise-03, Lines", width, height)
    win.setBackground(color_rgb(0,0,0))

    # Draw 10,000 points in the window
    # each with a random color and at a
    # random position/
    for i in range(100):
        # Get random position
        x1 = randint(0, width)
        x2 = randint(0, width)
        y1 = randint(0, height)
        y2 = randint(0, height)
            
        # Set random color
        r = randint(0, 255)
        g = randint(0, 255)
        b = randint(0, 255)

        # Draw our line using generated coordinates and color
        line(x1, y1, x2, y2, color_rgb(r, g, b), win)
    
    color = color_rgb(255,255,228)

    line(width/2, 0, 0, height/2, color, win)
    line(0, height/2, width/2, height, color, win)
    line(width/2, height, width, height/2, color, win)
    line(width, height/2, width/2, 0, color, win)
                    
    win.getMouse()
    win.close()
    
main()

Now John new that lines would be a desired feature of his library so he included a line drawing function. So why did we draw lines using points then? Well, so you would have an idea of just what the graphics library is doing for you.

Drawing Lines with the Library

Using the Line method of the graphics library is pretty simple. First, you create a Line object and assign it to a variable.

p1 = Point(0,0)
p2 = Point(640,480)
myLine = Line(p1, p2)
myLine.setFill(color_rgb(0,255,255))
myLine.draw(win)

Using line is really no different than using point. There is one aspect we didn’t touch on. That is The width of a line or point. You can set the width of a line or point with:

p1.setWidth(5)
myLine.setWidth(5)

The width value is given in terms of pixels.

Try using the library Line method in our ex-03.py random line program.

Calculating the Distance Between two Points

OK, create a new file called ex-04.py. In this section we are going to calculate the distance between two points. Now if you’ve had geometry in school you may recall that we can calculate this distance with the formula sqrt((x2 – x1)^2 + (y2 – y1)^2). So let’s create a new function called dist() that given two points will return the distance between them.

We don’t really need the graphics library to develop this function however, we can use it to draw the a line between the two points. So I’ve included it in the code in ex-04.py. We’ve developed this function as it is missing (IMHO) from the library (perhaps John left it out so his student had to code it manually?). However, it is easy to develop. enter the code below in to the ex-04.py file and run it. You should she a line from the upper left corner of the screen to the lower right corner. The window size here has been hard coded to (400 x 400). This makes it easy to calculate the length of the line using a calculator so we can confirm our function is working correctly.

#!/usr/bin/env python3

"""
Prog:   ex-04.py

Auth:   R. Morgan

Desc:   Demonstrate how to use John Zelle's
        graphics.py library to find the 
        distance between two points in the 
        window.

Lic:    This code is placed in the public domain.

"""
from graphics import *
from math import *

# Given (x1,y1) and (x2,y2)
# determine the distance 
# between the two points.
def dist(x1, y1, x2, y2):
    return sqrt(((x2-x1)**2) + ((y2 - y1)**2))

def main():
    win = GraphWin("Distance", 400, 400)
    win.setBackground(color_rgb(0,0,0))

    x1 = 0
    y1 = 0

    x2 = 400
    y2 = 400

    p1 = Point(x1, y1)
    p2 = Point(x2, y2)
    myLine = Line(p1, p2)
    myLine.setFill(color_rgb(188, 128, 176))
    myLine.draw(win)

    d = dist(x1, y1, x2, y2)
    
    print("Distance is: " + str(d))

    win.getMouse()
    win.close()

main()

If you run this code, you should see the length print in the terminal window, not the gui. The length should be 565.685…  You can check that with a calculator.

Ok, that brings up another issue. We’ve drawn points and line but what about text? We’ll get to the soon. I promise. But first let’s touch on circles.

Drawing Circles

We’ll begin by plotting our own circles and then touch on the Circle commands in the graphics library. Go ahead and create a new file ex-05.py and leave it empty for now.

Bresenham who we discussed earlier when talking about line drawing actually has an algorithm for circles. There on many circle drawing algorithms each with it’s own advantages and trade offs. We’ll use one of the simpler solutions for circle drawing. We’ll use plot the x,y coordinates of each point along the circumference using sin and cos from the python math library. Note that this is perhaps the poorest performing circle algorithm but it is simple to implement and easy to understand for most middle and high school math students.

The formula for each point on our circles circumference is: x = cos(i)*r + cx; y = sin(i)*r + cy; Where x and y are point coordinates, i is the angle in radians, cx and cy are the circle’s center coordinate, and r is the circle’s radius.  What we want to do is walk through 360 degrees or 2Pi radians. We could convert the degrees to radians but in practice, it works best to simply plot a minimum of 58 points. Why 58? Well 360 degrees / 6.28 ~= 58. So by plotting 58 points we walk all the way around our circle. We’ll see shortly that it’s not quite as easy as this but it’s a start.

Open your ex-05.py file and enter the code show below:


#!/usr/bin/env python3

"""
Prog:   ex-04.py

Auth:   R. Morgan

Desc:   Demonstrate how to use John Zelle's
        graphics.py library to draw circles 
        using points.

Lic:    This code is placed in the public domain.

"""

from graphics import *
from math import *

# Given the center x, center y
# and radius, draw a circle 
# using points.
def circle(cx, cy, r, color, win):
    # loop over 360 degree arc plotting pixels
    for i in range(0,58):
        x = cos(i)*r + cx;
        y = sin(i)*r + cy
        
        p = Point(x,y)
        p.setFill(color)
        p.draw(win)

def main():
    win = GraphWin("Circles", 400, 400)
    win.setBackground(color_rgb(0,0,0))

    circle(200, 200, 50, color_rgb(255, 128, 200), win)

    win.getMouse()
    win.close()

main()

Now if you run this code you’ll see our circle has many gaps in it’s circumference. This is because our point resolution isn’t high enough for the diameter of the circle we are drawing. So, we have two options:

  1. Shrink the size of our circle until the points meet.
  2. Draw many more points on the circumference of the circle.

Option 1 doesn’t work very well as we may need a larger circle. Option 2 reduces performance but allows us to grow the circle larger without producing gaps. So, we will take this route for now. Simply dump up the number of points produce in the loop from 58 to say 360. Now try the code.

That actually looks pretty good right? Ok, next try increasing the radius from 50 to 100 or 150 in the call to circle(). Oops! With a larger circle our missing points problem comes right back. Using this method to draw circles requires a balance between speed, size, and resolution. We can increase the circle size and resolution at the cost of speed, or reduce the resolution and increase speed at the cost of circle diameter. You’ll find much of engineering, not just Computer Science, requires constant trade-offs. Getting these right for every circumstance is as much an art as mathematics.

Above I only gave you two options for dealing with the gaps in the circles circumference. There are other methods that can be used. One such method is to use line segments to connect the points along the circumference. With this method you would simply place all your points in a list, sort them radially, and then use that list to draw line segments between them. At the end you’ll need to connect the last point to the first with another line segment. This sounds like a great exercise for the reader so I’ll leave it to you.

Drawing Circles with the Circle Method

OK, so you’ve seen that drawing lines and circles isn’t as straight forward as one might think. So let’s see how well the graphics library does on circles. We’ll add a few lines of code and compare out circles.  Edit ex-05.py to read as follows:


#!/usr/bin/env python3

"""
Prog:   ex-04.py

Auth:   R. Morgan

Desc:   Demonstrate how to use John Zelle's
        graphics.py library to draw circles.

Lic:    This code is placed in the public domain.

"""

from graphics import *
from math import *

# Given the center x, center y
# and radius, draw a circle 
# using points.
def circle(cx, cy, r, color, win):  
    for i in range(0,360):
        x = cos(i)*r + cx;
        y = sin(i)*r + cy

        p = Point(x,y)
        p.setFill(color)
        p.draw(win)

def main():
    win = GraphWin("Circles", 400, 400)
    win.setBackground(color_rgb(0,0,0))

    circle(200, 200, 150, color_rgb(255, 128, 200), win)

    # Draw circle from library
    p1 = Point(200,200)
    c = Circle(p1, 200)
    c.setOutline(color_rgb(220, 128, 164))
    c.draw(win)

    win.getMouse()
    win.close()

main()

Here you can see that using the Circle method from the library follows the same pattern as the Point and Line method. First, we instantiate an object and assign it to a variable, passing coordinates as a Point object. Then we call setOutline to passing a color_rgb object to set the outline color. We could also call setFill and pass a color_rgb object but that would hide the circle we drew ourselves. So if you decide to try setFill with Circle, make the calls to draw the Circle object before calling our own circle function. 

Now if you run this code, you’ll see that the library draws a much nicer circle than we do. This tutorial is more about using and appreciating the graphics library than about all the graphics algorithms. So I’ll leave it to the reader to explore the web in search of better 2D circle drawing algorithms.

Drawing Text in the Window

OK, as promised, we will now cover drawing text. Drawing text is much more complex than circles or lines so we wont try this ourselves. However, I encourage you to give it a try and see what you can accomplish. Playing around with code and ideas will give you a deeper understanding of programming in general, and besides, its a lot of fun!

The Text() method of the graphics.py library instantiates a text object that is centered around it’s anchor point. The anchor point is an (x,y) coordinate you pass to the method to position it in the window. You also need to pass the text string to be displayed.   You can also changed the text on the Text object by calling setText and retreieve the text contained in the object by calling getText. You might think that setting the text color would require a call to setFill or setOutline. But, you would be wrong. The text object requires you call setTextColor and pass a color_rgb object. Other notable text methods include setFace which sets the font family. setAnchor to set the text position (remember the text will be centered around this position. setSize which takes an integer font-point size (not to be confused with Points) between 5 and 36.  setStyle allows you to pass in strings of “bold”, “italic”, “italic bold” and “normal”. These do eactly what you expect.

So let’s see some Text in action. Create a file named ex-06.py and add the code shown below:


#!/usr/bin/env python3

"""
Prog:   ex-04.py

Auth:   R. Morgan

Desc:   Demonstrate how to use John Zelle's
        graphics.py library to draw text in
        the window.

Lic:    This code is placed in the public domain.

"""

from graphics import *
from random import randint
from math import *

width = 640
height = 480

def main():
    win = GraphWin("Circles", width, height)
    win.setBackground(color_rgb(0,0,0))

    messages = {"John Zelle", "Allen Turning",
                "Tommy Flowers", "Max Newman",
                "Gordon Wlchman", "John Atanasoff",
                "Tony Sale", "William Tutte",
                "Konrad Zuse", "Howard Aiken",
                "Charles Babbage", "Ada Lovelace",
                "Jack Kilby", "Robert Noyce",
                "Grace Hopper", "Ted Hoff",
                "Doug Engelbart", "Paul Otlet"
                "George Stibitz", "Clifford Berry"}
    fonts = ["helvetica","courier", "times roman", "arial"]
    styles = ["normal", "bold", "italic", "bold italic"]

    for msg in messages:
        # randomly pick a display point
        x = randint(100, width-100)
        y = randint(20, height-20)
        # randomly pick a text color
        r = randint(0, 255)
        g = randint(0, 255)
        b = randint(0, 255)

        p = Point(x, y)
        t = Text(p, msg)
        t.setTextColor(color_rgb(r, g, b))

        size = randint(10, 20)
        t.setSize(size)
        # Randomly set the font face and style
        s = randint(0,3)
        f = randint(0, 3)
        style = styles[s]
        print("style: " + style)
        t.setStyle(style)
        font = fonts[f]
        t.setFace(font)

        # draw the text to the window
        t.draw(win)
     
    win.getMouse()
    win.close()

main()

Run the code and you should see a bunch of names randomly placed on the screen at random locations, with random styles, sizes, colors, and font families.  This is just a simple demo. BTW, if these names mean nothing to you I recommend that if you’re serious about computer science you look these people up and read some of their history and any papers they have written.

OK, we can now place text on the screen. But how can we get text from the user? For this, graphics.py contains an object called Entry. Entry objects are little text boxes you enter text into. The Entry object is just an extension of the Text object we just saw. However, it is designed to allow the programmer to retrieve text from the user. Where the Text object is designed to display text to the user. The Entry object has the same set of methods as the Text object including all the font and style methods. It centers itself on the anchor as does the Text object. The only real difference is that the user can type into it.

So, how do we use the Entry to get text from the user? It’s really very simple. Set up the Entry object the same way you do Text and then call the getText() method to read the text out. Note that reading the text is nondestructive. If you need to clear the Entry after reading text you’ll need to call setText() on the object with an empty string. 

I’ve put together a simple example but before you can use it, you need to create a file called button.py and place it in the same folder as your other project code. This file provides a button widget built up out of  Rectangle and Text objects. If you have johns version you can use it. However, modified my version show below has added features. So create a button.py file and place the button code below in it.


"""
Prog:   button.py

Auth:   John Zelle

Mods:   Randall Morgan

Desc:   This is a modified version of John Zelle's
        Button class. Randall Morgan added additional
        features to allow the button outline, background,
        font face, and text style to be change. Also added
        is a method to force a redraw on the button.

Lic:    This code released under GPL to remain 
        compliant with John's original license.

"""

from graphics import *

class Button:

    """A button is a labeled rectangle in a window.
    It is activated or deactivated with the activate()
    and deactivate() methods. The clicked(p) method
    returns true if the button is active and p is inside it."""

    def __init__(self, win, center, width, height, label):
        """ Creates a rectangular button, eg:
        qb = Button(myWin, centerPoint, width, height, 'Quit') """ 

        self.win = win
        w,h = width/2.0, height/2.0
        x,y = center.getX(), center.getY()
        self.xmax, self.xmin = x+w, x-w
        self.ymax, self.ymin = y+h, y-h
        p1 = Point(self.xmin, self.ymin)
        p2 = Point(self.xmax, self.ymax)

        self.text_color = color_rgb(0,0,0)
        self.text_family = "helvetica"
        self.text_style = "bold"
        self.fill = color_rgb(200,200,225) # fill for btn background
        self.border_color = color_rgb(255,255,255)
        
        self.rect = Rectangle(p1,p2)
        self.rect.setOutline(self.border_color)
        self.rect.setFill(self.fill) 
        self.rect.setOutline(self.border_color)
        self.rect.draw(win)

        self.label = Text(center, label)
        self.label.setTextColor(self.text_color)
        self.label.setFace(self.text_family)
        self.label.setStyle(self.text_style)
        self.label.draw(win)
        self.deactivate()
        
    def clicked(self, p):
        "Returns true if button active and p is inside"
        return (self.active and
                self.xmin <= p.getX() <= self.xmax and
                self.ymin <= p.getY() <= self.ymax)

    def getLabel(self):
        "Returns the label string of this button."
        return self.label.getText()

    def activate(self):
        "Sets this button to 'active'."
        self.rect.setFill(self.fill)
        self.rect.setWidth(2)
        self.active = True

    def deactivate(self):
        "Sets this button to 'inactive'."
        self.label.setFill(self.text_color)
        self.rect.setWidth(1)
        self.active = False

    def setFill(self, color):
        "Sets the fill color of the button"
        self.fill = color
        self.rect.setFill(self.fill)
        self.rect.setOutline(self.border_color)

    def setTextColor(self, color):
        self.text_color = color
        self.label.setTextColor(color)

    def setTextFace(family):
        self.text_family = family
        self.label.setTextFace(family)

    def setTextSyle(style):
        self.text_style = style
        self.label.setTextSyle(self.text_style)

    def setBorderColor(self, color):
        self.border_color = color
        self.rect.setOutline(self.border_color)

    def draw(self, win):
        self.rect.undraw()
        self.label.undraw()
        self.rect.draw(self.win)
        self.label.draw(self.win)

I recommend you read through the button code. It’s pretty straight forward and you may find modifications you yourself would like to make.

Time to get on with our text input demo. We will set aside any discussion of the button code and concentrate on the text Entry object’s use.Create a file named ex-07.py and add the code shown next to it. Then run the code.


#!/usr/bin/env python3

"""
Prog:   ex-07.py

Auth:   R. Morgan

Desc:   Demonstrate how to use John Zelle's
        graphics.py library to get text entered 
        by the user into an Entry object.

Lic:    This code is placed in the public domain.

"""
from graphics import *
from button import *
from random import randint
from math import *

width = 400
height = 400

words = ["Tom Collins", "John Smith", "Alan Turing", "Mike Jones"]

def show_list(offset, words, win):
    y = 40
    for word in words:
        p = Point(offset, y)
        tbox = Text(p, word)
        tbox.setTextColor(color_rgb(255,255,255))
        tbox.draw(win)
        print("show: " + word)
        y += 20

def main():
    global words

    win = GraphWin("Word Sort", width, height)
    win.setBackground(color_rgb(0,0,0))

    p = Point(100, 40)
    box = Entry(p, 20)
    box.setText("Enter a word to sort")
    box.draw(win)

    p1 = Point(100, 80)
                #win, center, width, height, label
    btn = Button(win, p1, 100, 30, "Add to list")
    btn.setFill(color_rgb(200,200,225))
    btn.setBorderColor(color_rgb(255,255,255))
    btn.setTextColor(color_rgb(0,0,0))
    btn.activate()
    btn.draw(win)
   
    show_list(300, words, win)

    while True:
        pos = win.getMouse()
        if btn.clicked(pos):
            # get the text from the 
            # Entry and put it in the list.
            # then redraw the list
            text = box.getText()
            box.setText("")
            words.append(text)
            show_list(300, words, win)

    # Close app    
    win.close()

main()

The ex-07.py app opens a window and displays a Entry fields and a few names in a list on the right. Click in the Entry field and delete the current text. Next, type a name and click the button. The name will be added to the list.

The important thing to realize here is that the call to getText() on the Entry object simply returns immediately with what ever text is in the text field. This means we need to monitor some action, such as clicking a button, to indicate we need to read the text from the Entry object. The infinite while loop handles this for us. Once the loop is entered, the system will remain in the loop until the application is closed. So the win.close() method will never be called. This will cause an error message to be printed on exit. A better approach would have been to add an Exit button so the user could click it to exit cleanly. This would only require another if statement to handle the click on the Exit button. 

Alright, I think this will do for this post. I’ll continue this post soon and add more drawing functions and more information on the library. Their might even be a simple game soon.

Hope you enjoyed the post. If you find it helpful leave a comment and let me know.


Series NavigationSimple Graphics in Python – Part 2 >>