# CMU 15-112: Fundamentals of Programming and Computer Science Class Notes: Graphics in Tkinter

Notes
• We will only run graphics in Standard Python. These examples will not run in Brython.
• The standard Python documentation for tkinter can be hard to read. You can find our preferred documentation here (see chapter 8 on "The Canvas Widget").

1. Create an Empty Canvas
import basic_graphics def draw(canvas, width, height): pass # replace with your drawing code! basic_graphics.run(width=400, height=300)
Result:

For the examples below, simply use this skeleton and replace the draw function with the one provided.

2. Canvas Coordinates
3. Note: unlike in math, the y axis grows down instead of up. Thus, (0, 0) is at the top left corner of the canvas.

4. Draw a Line
def draw(canvas, width, height): # create_line(x1, y1, x2, y2) draws a line from (x1, y1) to (x2, y2) canvas.create_line(25, 50, width/2, height/2)
Result:

5. Draw a Rectangle with create_rectangle(left, top, right, bottom)
def draw(canvas, width, height): # The first four parameters are the upper-left (x,y) # and the lower-right (x,y) of the rectangle canvas.create_rectangle(0,0,150,150)
Result:

6. Graphics Parameters
def draw(canvas, width, height): # most graphics functions allow you to use optional parameters # to change the appearance of the object. These are written with the code # paramName=paramValue # after the core parameters in the code # fill changes the internal color of the shape canvas.create_rectangle( 0, 0, 150, 150, fill="yellow") # width changes the size of the border canvas.create_rectangle(100, 50, 250, 100, fill="orange", width=5) # outline changes the color of the border canvas.create_rectangle( 50, 100, 150, 200, fill="green", outline="red", width=3) # width=0 removes the border entirely canvas.create_rectangle(125, 25, 175, 190, fill="purple", width=0)
Result:

7. Draw Other Shapes and Text
def draw(canvas, width, height): # ovals provide the coordinates of the bounding box canvas.create_oval(100, 50, 300, 150, fill="yellow") # polygons and lines provide the (x,y) coordinates of each point # polygons must have 3+ points; lines must have 2+ canvas.create_polygon(100,30,200,50,300,30,200,10, fill="green") canvas.create_line(100, 50, 300, 150, fill="red", width=5) # text provides a single (x,y) point, then anchors the text there # text also requires the text, and can have a font canvas.create_text(200, 100, text="Amazing!", fill="purple", font="Helvetica 26 bold underline") canvas.create_text(200, 100, text="Carpe Diem!", anchor="sw", fill="darkBlue", font="Times 28 bold italic")
Result:

8. Draw Custom Colors
def rgbString(red, green, blue): # Don't worry about how this code works yet. return "#%02x%02x%02x" % (red, green, blue) def draw(canvas, width, height): pistachio = rgbString(147, 197, 114) maroon = rgbString(176, 48, 96) canvas.create_rectangle(0, 0, width/2, height/2, fill=pistachio) canvas.create_rectangle(width/2, height/2, width, height, fill=maroon)
Result:

9. Draw Centered Shapes
def draw(canvas, width, height): margin = 10 # Approach #1: Add margin to top/left, subtract margin from bottom/right: canvas.create_rectangle(margin, margin, width-margin, height-margin, fill="darkGreen") # Approach #2: add/subtract width/height from center (cx, cy) (cx, cy) = (width/2, height/2) (rectWidth, rectHeight) = (width/4, height/4) canvas.create_rectangle(cx - rectWidth/2, cy - rectHeight/2, cx + rectWidth/2, cy + rectHeight/2, fill="orange")
Result:

10. Graphics Helper Functions
def drawBelgianFlag(canvas, x0, y0, x1, y1): # draw a Belgian flag in the area bounded by (x0,y0) in # the top-left and (x1,y1) in the bottom-right width = (x1 - x0) canvas.create_rectangle(x0, y0, x0+width/3, y1, fill="black", width=0) canvas.create_rectangle(x0+width/3, y0, x0+width*2/3, y1, fill="yellow", width=0) canvas.create_rectangle(x0+width*2/3, y0, x1, y1, fill="red", width=0) def draw(canvas, width, height): # Draw a large Belgian flag drawBelgianFlag(canvas, 25, 25, 175, 150) # And draw a smaller one below it drawBelgianFlag(canvas, 75, 160, 125, 200) # Now let's have some fun and draw a whole grid of Belgian flags! flagWidth = 30 flagHeight = 25 margin = 5 for row in range(4): for col in range(6): left = 200 + col * flagWidth + margin top = 50 + row * flagHeight + margin right = left + flagWidth - margin bottom = top + flagHeight - margin drawBelgianFlag(canvas, left, top, right, bottom)
Result:

11. Dynamically sizing text
def draw(canvas, width, height): # Dynamically sizing text is harder, but possible! # Just compute the font size based on the width or height # Some guesswork helps to get the ratio right textSize = width // 10 canvas.create_text(width/2, height/2, text="Hello, World!", font=f'Arial {textSize} bold')
Result:

12. Drawing Circular Patterns with Trigonometry
Trig 101
• Circle centered at origin

• Circle centered at (cx, cy)

• Circle centered at (cx, cy) in Python graphics ("up is down!")

Example:
import math def draw(canvas, width, height): (cx, cy, r) = (width/2, height/2, min(width, height)/3) canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill="yellow") r *= 0.85 # make smaller so time labels lie inside clock face for hour in range(12): hourAngle = math.pi/2 - (2*math.pi)*(hour/12) hourX = cx + r * math.cos(hourAngle) hourY = cy - r * math.sin(hourAngle) label = str(hour if (hour > 0) else 12) canvas.create_text(hourX, hourY, text=label, font="Arial 16 bold")
Result:

13. Example: Clocks!
import math def drawClock(canvas, x0, y0, x1, y1, hour, minute): # draw a clock in the area bounded by (x0,y0) in # the top-left and (x1,y1) in the bottom-right # with the given time # draw an outline rectangle canvas.create_rectangle(x0, y0, x1, y1, outline="black", width=1) # find relevant values for positioning clock width = (x1 - x0) height = (y1 - y0) r = min(width, height)/2 cx = (x0 + x1)/2 cy = (y0 + y1)/2 # draw the clock face canvas.create_oval(cx-r, cy-r, cx+r, cy+r, outline="black", width=2) # adjust the hour to take the minutes into account hour += minute/60.0 # find the hourAngle and draw the hour hand # but we must adjust because 0 is vertical and # it proceeds clockwise, not counter-clockwise! hourAngle = math.pi/2 - 2*math.pi*hour/12 hourRadius = r*1/2 hourX = cx + hourRadius * math.cos(hourAngle) hourY = cy - hourRadius * math.sin(hourAngle) canvas.create_line(cx, cy, hourX, hourY, fill="black", width=1) # repeat with the minuteAngle for the minuteHand minuteAngle = math.pi/2 - 2*math.pi*minute/60 minuteRadius = r*9/10 minuteX = cx + minuteRadius * math.cos(minuteAngle) minuteY = cy - minuteRadius * math.sin(minuteAngle) canvas.create_line(cx, cy, minuteX, minuteY, fill="black", width=1) def draw(canvas, width, height): # Draw a large clock showing 2:30 drawClock(canvas, 25, 25, 175, 150, 2, 30) # And draw a smaller one below it showing 7:45 drawClock(canvas, 75, 160, 125, 200, 7, 45) # Now let's have some fun and draw a whole grid of clocks! width = 40 height = 40 margin = 5 hour = 0 for row in range(3): for col in range(4): left = 200 + col * width + margin top = 50 + row * height + margin right = left + width - margin bottom = top + height - margin hour += 1 drawClock(canvas, left, top, right, bottom, hour, 0)
Result:

14. More with basic_graphics.run()
1. Extra arguments to draw()
import basic_graphics # See how the extra arguments to draw are provided # as the first arguments to basic_graphics.run() def draw(canvas, width, height, message, color): canvas.create_text(width/2, height/2, text=message, fill=color) basic_graphics.run('This is cool!', 'blue', width=400, height=300)

2. Specifying the draw function
import basic_graphics # See how we can specify the draw function to use. # This lets us place multiple draw functions in the same file. def drawBigDot(canvas, width, height, color): cx, cy, r = width/2, height/2, min(width,height)/3 canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill=color) basic_graphics.run('purple', drawFn=drawBigDot)

15. Fractals: sierpinskiTriangle
Note: The video for this is for a slightly different version of the code, but the basics of the fractal are the same.
import basic_graphics # Change this value to adjust how deep the recursion goes LEVEL=5 def drawSierpinskiTriangle(canvas, level, x, y, size): # (x,y) is the lower-left corner of the triangle # size is the length of a side # Need a bit of trig to calculate the top point if level == 0: topY = y - (size**2 - (size/2)**2)**0.5 canvas.create_polygon(x, y, x+size, y, x+size/2, topY, fill='black') else: # Bottom-left triangle drawSierpinskiTriangle(canvas, level-1, x, y, size/2) # Bottom-right triangle drawSierpinskiTriangle(canvas, level-1, x+size/2, y, size/2) # Top triangle midY = y - ((size/2)**2 - (size/4)**2)**0.5 drawSierpinskiTriangle(canvas, level-1, x+size/4, midY, size/2) def draw(canvas, width, height): margin = min(width, height)//10 x, y = margin, height-margin size = min(width, height) - 2*margin drawSierpinskiTriangle(canvas, LEVEL, x, y, size) canvas.create_text(width/2, margin, text = f'Level {LEVEL} Fractal', font = 'Arial ' + str(int(margin/3)) + ' bold', anchor='n') basic_graphics.run(width=400, height=400)