Class Notes: Graphics in Tkinter


  1. Important Notes [Pre-reading]
  2. Installing Required Modules (PIL/Pillow and Requests) [Pre-reading]
  3. Create an Empty Canvas
  4. Canvas Coordinates
  5. Draw a Line
  6. Draw a Rectangle with create_rectangle(left, top, right, bottom)
  7. Graphics Parameters
  8. Draw Other Shapes and Text
  9. Draw Custom Colors
  10. Draw Centered Shapes
  11. Graphics Helper Functions
  12. Dynamically Sizing Text
  13. Trigonometry 101 [Pre-reading]
  14. Drawing Circular Patterns with Trigonometry
  15. Example: Clocks!


  1. Important Notes [Pre-reading]
  2. Installing Required Modules (PIL/Pillow and Requests) [Pre-reading]

  3. To use all of the features of cmu_112_graphics.py, you need to have some modules installed. You don't absolutely need to have them installed to do basic graphics and animations so we have some time to help you with any issues, but if they are not installed, you will see a message like this (or a similar one for 'requests' instead of 'PIL'):
    **********************************************************
    ** Cannot import PIL -- it seems you need to install pillow
    ** This may result in limited functionality or even a runtime error.
    **********************************************************
    
    You can try to use 'pip' to install the missing modules, but it can be complicated making sure you are installing these modules for the same version of Python that you are running. Here are some more-reliable steps that should work for you:

    Important Hint: in the steps below, you will use the terminal (on Mac) or command prompt (on Windows). In each case, this is not the terminal in VS Code!
    With that, here are the steps:

    Quick test

    1. Create a new folder on your computer for week 3 (if you haven't already).
    2. Download both cmu_112_graphics.py and helloGraphics.py [UPDATED] and save them in your week 3 folder.
    3. In VS Code, open the helloGraphics.py file in your week 3 folder and run it with ctrl+b or cmd+b.
    4. A window with graphics should pop up. We'll ask you about this in the pre-reading checkpoint, so make sure you know exactly what this window looks like.

    If these steps do not work for you, please go to OH and we will be happy to assist.

  4. Create an Empty Canvas
  5. from cmu_112_graphics import * def redrawAll(app, canvas): pass # replace with your drawing code! runApp(width=400, height=200)
    Result:

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

  8. Draw a Line
  9. from cmu_112_graphics import * def redrawAll(app, canvas): # create_line(x1, y1, x2, y2, fill='black') # draws a black line from (x1, y1) to (x2, y2) canvas.create_line(25, 50, app.width/2, app.height/2, fill='black') runApp(width=400, height=200)
    Result:

  10. Draw a Rectangle with create_rectangle(left, top, right, bottom)

  11. from cmu_112_graphics import * def redrawAll(app, canvas): # 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, fill='black') runApp(width=400, height=200)
    Result:

  12. Graphics Parameters
  13. from cmu_112_graphics import * def redrawAll(app, canvas): # 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', outline='black') # width changes the size of the border canvas.create_rectangle(100, 50, 250, 100, fill='orange', outline='black', 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) runApp(width=400, height=200)
    Result:

  14. Draw Other Shapes and Text
  15. from cmu_112_graphics import * def redrawAll(app, canvas): # ovals provide the coordinates of the bounding box canvas.create_oval(100, 50, 300, 150, fill='yellow', outline='black') # 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') runApp(width=400, height=200)
    Result:

  16. Draw Custom Colors
  17. from cmu_112_graphics import * def rgbString(r, g, b): # Don't worry about the :02x part, but for the curious, # it says to use hex (base 16) with two digits. return f'#{r:02x}{g:02x}{b:02x}' def redrawAll(app, canvas): pistachio = rgbString(147, 197, 114) maroon = rgbString(176, 48, 96) canvas.create_rectangle(0, 0, app.width/2, app.height/2, fill=pistachio, outline='black') canvas.create_rectangle(app.width/2, app.height/2, app.width, app.height, fill=maroon, outline='black') runApp(width=400, height=200)
    Result:

  18. Draw Centered Shapes
  19. from cmu_112_graphics import * def redrawAll(app, canvas): margin = 10 # Approach #1: Add margin to top/left, subtract margin from bottom/right: canvas.create_rectangle(margin, margin, app.width-margin, app.height-margin, fill='darkGreen', outline='black') # Approach #2: add/subtract app.width/app.height from center (cx, cy) (cx, cy) = (app.width/2, app.height/2) (rectWidth, rectHeight) = (app.width/4, app.height/4) canvas.create_rectangle(cx - rectWidth/2, cy - rectHeight/2, cx + rectWidth/2, cy + rectHeight/2, fill='orange', outline='black') runApp(width=400, height=200)
    Result:

  20. Graphics Helper Functions
  21. from cmu_112_graphics import * 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 redrawAll(app, canvas): # 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) runApp(width=400, height=200)
    Result:

  22. Dynamically sizing text
  23. from cmu_112_graphics import * def redrawAll(app, canvas): # 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 = app.width // 10 canvas.create_text(app.width/2, app.height/2, text='Hello, World!', font=f'Arial {textSize} bold', fill='black') runApp(width=400, height=400)
    Result:

  24. Trigonometry 101 [Pre-reading]

  25. Drawing Circular Patterns with Trigonometry
  26. from cmu_112_graphics import * import math def redrawAll(app, canvas): (cx, cy, r) = (app.width/2, app.height/2, min(app.width, app.height)/3) canvas.create_oval(cx-r, cy-r, cx+r, cy+r, fill='yellow', outline='black') 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', fill='black') runApp(width=400, height=400)
    Result:

  27. Example: Clocks!
  28. from cmu_112_graphics import * 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 redrawAll(app, canvas): # 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) runApp(width=400, height=400)
    Result: