The Canvas Widget

The canvas widget is a general-purpose widget for displaying graphical objects: lines, rectangles, circles, but also images and even other widgets. Furthermore it can be instructed to run commands in response to mouse clicks or keystrokes, thus making it suitable for interactive use - you could build a program for interactively drawing diagrams for instance.

Let us start with some short examples:

    pack [canvas .c]

    .c create oval 100 100 200 200 -fill green
    .c create rectangle 120 150 180 200 -outline red -width 3

This creates a canvas widget with two graphical objects:

Both the circle and the rectangle are created using two points — (100,100) and (200,200) for the circle and (120,150) and (180,200) for the rectangle. The pair of points defines the (rectangular) space that the objects is drawn in. As the two points (100,100) and (200,200) define a square, the result is a circle. In general it would be an ellipse. For instance:

    .c create oval 100 100 200 300 -fill green

Here are two more objects:

    .c create line 10 50 50 10 120 100
    .c create line {10 50 50 10 120 100} -smooth 1

Each line has three points, but the second line is drawn with the smoothing option, making it a smooth curve rather than a series of line pieces. Also note that the coordinates are given as a list, rather than individual arguments. This form is very useful if you have a large number of points or if the number of points varies:

    #
    # Polygons need not to be simple, as demonstrated by this pentagram
    #
    set angle [expr {3.1415926 * 4.0 / 5.0}]
    set coords {}
    for { set i 0 } { $i < 5 } { incr i } {
        set x [expr {cos($i*$angle)}]
        set y [expr {sin($i*$angle)}]

        lappend coords [expr {150 + 100 * $x}] [expr {150 + 100 * $y}]
    }

    .c create polygon $coords -fill green -width 2

13.1  Identifying objects

Each creation command returns the ID of the graphical object. This allows you to manipulate the object:

    set obj [.c create text 100 100 -text Hello]

    after 2000 [list .c itemconfig $obj -text Goodbye!]

Besides the object’s ID you can also use tags to manipulate the object, or better: all objects that have that tag:

    #
    # This text is hidden by the rectangle
    #
    .c create text 100 100 -text Goodbye -tag later
    .c create text 100 120 -text "See you soon" -tag later

    .c create text 100 100 -text Hello   -tag welcome

    .c create rectangle 50 50 250 250 -fill green


    #
    # Show the texts
    #
    after 500 {.c raise welcome}
    after 2500 {
        .c raise later
        .c lower welcome
    }

This example exhibits yet another property of the graphical objects: they are drawn in a particular order and you can influence that order via the raise and lower subcommands.

13.2  Coordinates

One thing to note about the canvas is that the y coordinate runs from top to bottom, as is common with computer graphics, but it may surprise you. So the origin (0,0) is at the top-left corner of the canvas.

If you just specify a number, that number is interpreted as the number of pixels. But you can use centimeters, millimeters, inches and points (1/72 of an inch) too:

    .c create line 1c 2.5c 2c 3.5c

What is more: coordinates are considered to be floating-point numbers. This is very useful when scaling the object for instance:

    set obj [.c create oval 100 100 110 110]

    for {set i 0} {$i < 10} {incr i} {
        after [expr {$i*100}] [list .c scale $obj 100 100 1.1 1.1]
    }

    for {set i 0} {$i < 10} {incr i} {
        after [expr {10+$i*100}] [list .c scale $obj 100 100 0.909 0.909]
    }

The scale subcommand scales the coordinates of the object by a certain factor (they may be different in both directions) with respect to a central point — (100,100) in this case.

You can change the coordinates of a given object directly too:

    .c coords $obj 20 20 200 200

and the oval grows and moves but the other attributes (colour, filled or not, etc.) remain the same.

13.3  Handling mouse events and keystrokes

For interactive applications it is very useful to be able to handle mouse events or keystrokes. In Tcl/Tk this is done via bindings — you bind a particular command to a particular event. The canvas widgets supports two types of bindings:

  1. Bindings to the widget as a whole

  2. Bindings to individual graphical objects

Here we consider the second type, as the first type is not specific to the canvas widget.

Consider the following script:

    .c create rectangle  20  20  60  60 -fill grey -tag enterleave
    .c create rectangle  60  20 100  60 -fill grey -tag enterleave
    .c create rectangle  20  60  60 100 -fill grey -tag enterleave
    .c create rectangle  60  60 100 100 -fill grey -tag enterleave

    .c bind enterleave <Enter> {.c itemconfig current -fill green}
    .c bind enterleave <Leave> {.c itemconfig current -fill grey}

If you move the mouse over these rectangles you will see that they turn green when it enters the rectangle and grey again when it leaves. The tag ‘current’ is a special tag that is managed by the canvas widget itself, and it indicates the topmost (last to be drawn) object that contains the mouse.

13.4  Embedding widgets and images

For the canvas, widgets and images that you want to put in it are just ‘ordinary’ graphical objects. Here is an example of how to create a widget inside a canvas:

    button .c.button -text OK -command {.c create text 100 100 -text OK}
    .c create window 100 200 -window .c.button

The widget can be a child of the canvas, or a child of one of the ancestors of the canvas. Pressing the button will create a text ‘OK’ above the button, as you would expect.

Images can be handled in a similar way, there are in fact two types of images available: images and bitmaps, which are two-colour images. As there are a number of built-in bitmaps, we will take that as an example:

    .c create bitmap 10 10 -bitmap question -anchor nw \
          -background yellow -foreground red

Like all other graphical items, there are a number of options you can use to control the appearance of the image or the widget. In the above example we have used the -anchor option to control how the image is displayed with respect to the coordinates we give. The value ‘nw’ (north-west) means that the given coordinates refer to the upper-left corner of the image.

13.5  More subcommands

The canvas has a large number of subcommands and they are all documented in the manual page. Here are a few common ones, just to give an impression:

bbox:
determine the smallest rectangle that holds one or more objects
delete:
delete graphical objects by ID or tag
find:
find the objects that meet certain conditions (have some tag, are near a particular point etc.)
move:
move objects over a certain distance in one or two directions
postscript:
store the contents in a PostScript file
xview, yview:
used to interact with scrollbars if the canvas is actually larger than the portion visible on the screen

Tip: If you issue the postscript subcommand while there is nothing visible on the screen, the PostScript file will be empty. You need to wait until the canvas has been drawn. This is a side-effect that you need to be aware of. Compare the results of the following script:
    pack [canvas .c]
    .c create rectangle 100 100 200 200 -fill red
    .c postscript rectangle1.ps
    exit

and

    pack [canvas .c]
    .c create rectangle 100 100 200 200 -fill red
    tkwait visibility .
    .c postscript rectangle2.ps
    exit

13.6  A more elaborate example

The program below will draw a fractal tree by first drawing a vertical line and then recursively drawing two lines at an angle to the first one. To create a bit more detail, it also calculates the colour for the new lines from the individual red, green and blue contributions. This way you can produce shaded graphics.

    pack [canvas .c -width 400 -height 300 -bg white]

    proc drawBranch {xstart ystart length angle rgb} {

        #
        # Compose the colour
        #
        foreach {red green blue} $rgb {break}
        set colour [format "#%2.2x%2.2x%2.2x" $red $green $blue]

        #
        # Determine the end point
        #
        set phi  [expr {$angle * 3.1415926 / 180.0}]
        set xend [expr {$xstart + $length * cos($phi)}]
        set yend [expr {$ystart + $length * sin($phi)}]

        #
        # The line thickness
        #
        set width 3
        if { $length < 25 } { set width 2 }
        if { $length <  5 } { set width 1 }

        #
        # Draw the branch and the sub-branches
        #
        .c create line $xstart $ystart $xend $yend -fill $colour -width $width

        if { $length > 1.0 } {

            set rgbLeft  [list [expr {int(0.8*$red)}] $green $blue]
            set rgbRight [list $red $green [expr {int(0.8*$blue)}]]

            set angleLeft  [expr {$angle + 45.0}]
            set angleRight [expr {$angle - 45.0}]

            set length     [expr {2.0*$length/3.0}]

            drawBranch $xend $yend $length $angleLeft  $rgbLeft
            drawBranch $xend $yend $length $angleRight $rgbRight
        }
    }

    drawBranch 200 280 100 270 {255 0 255}

The first line starts with an angle of 270 to avoid the somewhat awkward direction of the vertical coordinate (later on it does not matter anymore because of the symmetry of the design).

Drawing stops automatically when the line length is shorter than a single pixel.

The colour contribution is reduced by 20% and the length by 33% per generation, figures that were found experimentally to give a nice result.

Note: This example also shows that the canvas widget is capable of displaying large amounts of graphical objects. There are more than 8000 individual lines in the tree.