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


Notes
  1. Create an Empty Canvas
  2. Canvas Coordinates
  3. Draw a Line
  4. Draw a Rectangle with create_rectangle(left, top, right, bottom)
  5. Graphics Parameters
  6. Draw Other Shapes and Text
  7. Draw Custom Colors
  8. Draw Centered Shapes
  9. Graphics Helper Functions
  10. Dynamically Sizing Text
  11. Drawing Circular Patterns with Trigonometry
  12. Example: Clocks!
  13. More with basic_graphics.run()
  14. Fractals

  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)