canvas


canvas , a Tk command, creates and manipulates canvas widgets.

See Also

Alternative Canvases
lists widgets and toolkits with similar scope to the canvas widget
canvas woes
Tk examples
Category Animation
Category Games
Category Toys
tkpath
SVG-like rendering for the canvas

Documentation

official reference
The Tk Canvas Widget
by Derek Fountain , Linux Journal ,2004-02

Description

The canvas widget is Tk's workhorse for 2D graphical display, and can handle both bitmap and vector graphics. It was inspired by Joel Bartlett's ezd program, which provides structured graphics in a Scheme environment.

A canvas is one of the most powerful concepts in Tk. It acts as a drawing plane for lines, rectangles, ovals, polygons, text, arcs (e.g. pieslices) as well as container widget to group other widgets, and it provides the ability to group elements together for creation, deletion, moving, etc.

History

Don't Fidget with Widgets, Draw!
by Joel Bartlett ,1991

Item Types

canvas text

Examples

Look at the pages for each item type for more demos.

Arranged roughly in order from simple to complex:

A minimal starfield
canvashelp
expanding/collapsing text item
Alternative buttons on canvas
Canvas presentation graphics
Example Canvas Widget
Harmonic Color Wheel
Canvas pixel painting
Canvas rectangle marking
Sun, moon, and stars
Simple Canvas Demo
Canvas item selections
Canvas item selection by mouse click
A symmetric doodler
Tkeyes
an xeyes clone
Canvas buttons
button-like behaviour on canvas items, by Kevin Kenny
Canvas buttons in 3-D
3d buttons, by Kevin Kenny
Drawing and editing polygons
Bounding boxes of characters in canvases
Notes on a canvas
Crosshairs on a canvas
Canvas object dropper
XML Graph to canvas
Simple XML report writer

Grouping Items

Atomic canvas tag item groups with tags

Tk's canvas has a lot of support for manipulating multiple items at once by giving a few items the same tag, but I find sometimes I would like to group a bunch of items together, and treat them as if they were only one item.

For example the current tag only applies to the item first under the mouse, so if you have a graphic composite of say, a text and a rectangle with the same tag, you can not use the most generic code:

bind .c <1> {set oldX %x ; set oldY %y}
bind .c <B1-Motion> {%W move current [expr %x-$oldX] [expr %y-$oldY]}

because only one of the items, the rectangle or the text, will move. To enable both of the items to be moved at once, I implemented an idea I saw in Zinc, atomic groups.

To do this I added two methods to the canvas, tagconf and tagcget, which allow you to modify the behaviour of the tag as opposed to the items under the tag.

I propose an option -atomic which defaults to off. When it's turned on, if an individual item with a tag's whose l-atomic is true is passed to any method that operates on multiple items (basically move and scale), all of the items possesing the atomic tag have the passed tag added to their list of tags.

Tips

Unlike frames, canvases come with a 2-pixel -highlightthickness ... set them to zero if you want the canvas to pack/grid/etc. like a frame does.

Anti-Aliasing

Anti-aliasing for line, polygons, oval items (X only, requires the Cairo lib): http://phpsource.net/?page=tk

Moving Things Around

3-D

3-D Boxes (Support for Canvas Buttons in 3-D)
3D polyhedra with simple tk canvas

Layout

Getting the Canvas View Area in Pixels
Canvas microjustification
Attracting objects on the canvas
An experiment based on the idea of objects on the canvas attracting each other, and repositioning themselves accordingly.

Geometrical Calculations

Canvas geometrical calculations
Affine transforms on a canvas
Canvas Rotation
Canvas zooming

Polygons

Drawing rounded rectangles
Rectangle conversion
Drawing rounded polygons
Round Polygons

Transparency

Jeffrey Hobbs wrote in c.l.t on 2001-12-21:

I think the point is that a completely transparent rectangle is essentially a fully filled for bindings, which allows you to create "hot spots" of bindings that don't require visual extras (like for imagemaps). If an outline of fill is specified, then you have a semantically different box, and triggers only occur for the visible portion.


jal_frezie Would it be possible to allow a canvas to have a transparent background? This would be an easy way to implement hierarchies of graphical items, as you could have a child canvas in a window item on its parent canvas with no graphical indication of the distinction between the items on the two canvases. Thus you could replicate the most useful bit of Zinc, with the only change to the language being that the cammand ".canvas configure -bg {}" would set the background transparent rather than raising an exception. I have looked at the code implementing canvases, but not very much, and I imagine this change would be quite simple to implement.

DKF: The current rendering engine (except possibly on OSX) isn't up to handling alpha-blended windows, and you can only efficiently do non-rectangular windows with an extension.

Polygons from lines

Polygon items, even if displayed transparent with -fill {}, cover the underlying items, so tag bindings to them don't fire. Get truly transparent polygons by constructing them explicitly from lines:

proc myPolygon {c coords {color black}} {
    lassign $coordsx0 y0
    foreach {x1 y1} [concat [lrange $coords 2 end] $x0 $y0] {
        $c create line $x0 $y0 $x1 $y1  -fill $color -tag markup
        set x0 $x1; set y0 $y1
    }
}

DKF: You can do this more easily by using the fact that lines need not just be point-to-point entities.

proc myPolygon2 {c coords {color black}} {
    # Close the path first
    set p0 [lrange $coords 0 1]
    set pEnd [lrange $coords end-1 end]
    if {$p0 ne $pEnd} {
        eval lappend coords $p0
    }
    # Now create the item
    # note that the result of this command (the item id) is the proc result too
    $c create line $coords -fill $color -tag markup
}

Scrolling

Minimal scrolling canvas
scrolling explained
Kinetic scrolling

Panning

Canvas scrolling by dragging mouse

Adding panning to a canvas is simple. Suppose you have a canvas widget (with or without scrollbars, that doesn't matter). The only thing to add are two small bindings:

canvas .c
pack .c
bind .c <ButtonPress-1> {%W scan mark   %x %y}
bind .c <B1-Motion>     {%W scan dragto %x %y 1}

That's all. Pressing the mouse button 1 will initiate panning and subsequent moving pans the widget. You do not need to take care of scaling or other factors since all is done in window coordinates. Note the number '1' as the last argument to 'scan dragto'. This is the gain factor which defaults to 10. Setting it to '1' make the command behave like the cursor sticks to the point where you pressed the mouse button.

If you want to have fun, try to remove the first of the two bindings or set the gain to other values ... :-)

Finding Visible or Partly Visible Items

MAK: This function will return all of the tags for items that are currently visible (either entirely visible if partial is 0 or partly off-screen if partial is 1) within the canvas, provided you've got your scroll region set correctly.

proc canvasVisibleTags { hWnd {partial 1} } {
    lassign [$hWnd cget -scrollregion] xmin ymin xmax ymax
    lassign [$hWnd yview] y1 y2
    lassign [$hWnd xview] x1 x2

    set top   [expr {($ymax - $ymin) * $y1 + $ymin}]
    set bot   [expr {($ymax - $ymin) * $y2 + $ymin}]
    set left  [expr {($xmax - $xmin) * $x1 + $xmin}]
    set right [expr {($xmax - $xmin) * $x2 + $xmin}]

    if {$partial} {
        return [$hWnd find overlapping $left $top $right $bot]
    } else {
        return [$hWnd find enclosed $left $top $right $bot]
    }
}

Canvas see #1 - move view to make items visible

MAK: I didn't see any "see" functions for the canvas on the Wiki, so here is a somewhat reformatted (for my own aesthetics) version of a similar function found in an old usenet post [L1 ] with the added capability that you can specify more than one item as what you want to scroll to.

Useful if you have multiple items that together comprise one logical item (in my case, in my own tree widget where I want to "see" the expand/collapse button, the icon, and the label rather than just the label). This algorithm will scroll only as far as necessary to make the specified items visible, rather than centering on the specified items.

proc canvasSee { hWnd items } {
    set bbox [eval $hWnd bbox $items]

    if {$bbox == ""} { return }

    lassign $bbox x1 y1 x2 y2
    lassign [$hWnd yview] top bottom
    lassign [$hWnd xview] left right
    lassign [$hWnd cget -scrollregion] x_min y_min x_max y_max

    set width   [expr {$x_max - $x_min}]
    set right   [expr {$right * $width}]
    set left    [expr {$left * $width}]

    set height  [expr {$y_max - $y_min}]
    set top     [expr {$top * $height}]
    set bottom  [expr {$bottom * $height}]

    if { $x1 < $left } {
        $hWnd xview moveto [expr {double($x1-$x_min)/$width}]
    } elseif {$x2 > $right} {
        $hWnd xview moveto [expr {double($x2-$x_min-($right-$left))/$width}]
    }

    if { $y1 < $top } {
        $hWnd yview moveto [expr {double($y1-$y_min)/$height}]
    } elseif {$y2 > $bottom} {
        $hWnd yview moveto [expr {double($y2-$y_min-($bottom-$top))/$height}]
    }
}

Canvas see #2 - move view to make items visible

MAK

This is another similarly reformatted and extended algorithm from another usenet post [L2 ] -- unlike the above, this one tends to center the specified items within the visible area of the canvas.

proc canvasSee { hWnd items } {
    set box [eval $hWnd bbox $items]

    if {$box == ""} { return }

    if {[string match {} [$hWnd cget -scrollregion]] } {
        # People really should set -scrollregion you know...
        lassign $box x y x1 y1

        set x [expr round(2.5 * ($x1+$x) / [winfo width $hWnd])]
        set y [expr round(2.5 * ($y1+$y) / [winfo height $hWnd])]

        $hWnd xview moveto 0
        $hWnd yview moveto 0
        $hWnd xview scroll $x units
        $hWnd yview scroll $y units
    } else {
        # If -scrollregion is set properly, use this
        lassign $box x y x1 y1
        lassign [$hWnd yview] top  btm
        lassign [$hWnd xview] left right
        lassign [$hWnd cget -scrollregion] p q xmax ymax

        set xpos [expr (($x1+$x) / 2.0) / $xmax - ($right-$left) / 2.0]
        set ypos [expr (($y1+$y) / 2.0) / $ymax - ($btm-$top)    / 2.0]

        $hWnd xview moveto $xpos
        $hWnd yview moveto $ypos
    }
}

Tiled Backgrounds

DKF: Canvases don't directly support tiled backgrounds, but you can easily fix this by using a tiled image underneath everything else.

-highlightcolor

Robert Heller provided the following sample of how to use the canvas's -highlightcolor option (slightly corrected by Jeff Hobbs):

canvas .thecanvas
pack .thecanvas
.thecanvas create oval 0 0 50 50 -fill red -outline blue -tag theOval
.thecanvas itemconfigure theOval -fill \
    [tk_chooseColor -initialcolor [.thecanvas itemcget theOval -fill]]
.thecanvas itemconfigure theOval -outline \
    [tk_chooseColor -initialcolor [.thecanvas itemcget theOval -outline]]

proc Rand255 {} {
    return [expr int(rand() * 256)]
}

.thecanvas itemconfigure theOval \
    -fill [format {#%02x%02x%02x} [Rand255] [Rand255] [Rand255]]
.thecanvas itemconfigure theOval \
    -outline [format {#%02x%02x%02x} [Rand255] [Rand255] [Rand255]]

.thecanvas configure -highlightcolor [format {#%02x%02x%02x} [Rand255] [Rand255] [Rand255]]
focus .thecanvas

Getting the Canvas ID

MG 2005-02-20: Would it be useful for the [canvas bind ...] command to accept an extra substitution to the regular ones, for the canvas id of the item being clicked, and the name of the tag that invoked the binding? I know it's (always?) possible to get the id with [$canvas find overlapping %x,%y], but I'd guess it's something that's needed in pretty much every instance where canvas tags have bindings, so it would probably be much quicker and easier (in terms of learning how to do it and actual execution time) if it were done with a substitution?

Peter Newman 2005-02-20:

set myCanvasItemsCanvasID [.myCanvas create whatever xxx] ;
...
.myCanvas bind $myCanvasItemsCanvasID <Event> "MyEventHandler ${myCanvasItemsCanvasID} %x %y" ;
...
proc MyEventHandler { myCanvasItemsCanvasID mouseX_inWindow mouseY_inWindow } { xxx ... xxx } ;

That's even faster that an extra substitution to the regular ones, since the substitution is made only once, when the binding script is compiled, rather than every time the event occurs.

MG: That only works if you're binding to a single canvas item. If you're binding to a tag (which is what I meant, though didn't say too clearly). Then you can do something like

pack [canvas .c]
for {set i 1} {$i < 10} {incr i} {
    .c create image [expr {$i*5}] 20 -image test -tags [list clickable]
}
.c bind clickable <Button-1> {puts "You pressed canvas item %I on canvas %W"}

RS: The canvas item being clicked has the "current" tag...

MG Thanks, RS. Don't recall ever seeing that before :)

Scott Hill 2005-0-01: The tag current is managed automatically by Tk; it applies to the current item, which is the topmost item whose drawn area covers the position of the mouse cursor. If the mouse is not in the canvas widget or is not over an item, then no item has the current tag.

Exporting a Canvas

Exporting a canvas to other formats
can2svg
Pdf canvas
2007-08-03
pdf4tcl
Printing a canvas under Windows
Serializing a canvas widget

MG: I think all you need to do, assuming you have the Img package is this:

package require Img
pack [canvas .c -height 50 -width 50]
.c create rectangle 0 0 25 25 -fill blue
.c create rectangle 25 25 50 50 -fill green
raise . ;# if there's anything over the window on-screen, it'll be obscured in the image
image create photo theCanvas -format window -data .c
theCanvas write /your/path/to/image.gif -format gif

Clipping

Clipping graphical objects in a canvas widget

MAK 2005-01-26: I just noticed something about the way window canvas items are clipped that's quite interesting. I'm not sure if it's a bug or not, but at least it's the same on different platforms: window items are not clipped to the canvas, but rather to their parent window. A demonstration:

frame .f -bg blue
canvas .c2 -bg red -width 100
grid rowconfigure .f 0 -weight 1 -minsize 0
grid rowconfigure .f 1 -weight 0 -minsize 0
grid columnconfig .f 0 -weight 1 -minsize 0
grid columnconfig .f 1 -weight 0 -minsize 0

grid [canvas .f.c -bg yellow -width 100 \
    -xscrollcommand ".f.x set" -yscrollcommand ".f.y set"] \
        -row 0 -column 0 -sticky news
grid [scrollbar .f.y -orient vertical -command ".f.c yview"] \
        -row 0 -column 1 -sticky news
grid [scrollbar .f.x -orient horizontal -command ".f.c xview"] \
        -row 1 -column 0 -sticky news

grid columnconfig . 0 -weight 1 -minsize 0
grid columnconfig . 1 -weight 1 -minsize 0
grid rowconfigure . 0 -weight 1 -minsize 0
grid .f -row 0 -column 0 -sticky news
grid .c2 -row 0 -column 1 -sticky news

checkbutton .cb -text "012345678901234567890123456789" -bg green
checkbutton .f.cb -text "012345678901234567890123456789" -bg green
checkbutton .f.c.cb -text "012345678901234567890123456789" -bg green
.f.c create window 0  0 -anchor nw -window .cb
.f.c create window 0 30 -anchor nw -window .f.cb
.f.c create window 0 60 -anchor nw -window .f.c.cb

This script creates two canvases side by side - one red and yellow with scrollbars. It then creates three identical checkbuttons with green backgrounds - one that's a child of the toplevel, one that's a child of the frame with the canvas and scrollbars, and one that's a child of the canvas itself.

Notice that the topmost checkbutton is not clipped at all - it overlaps both canvases, while the middle checkbutton is clipped to the frame and overlaps the scrollbars, and the bottom checkbutton is clipped to the canvas.

However, that bottom checkbutton still overlaps the margin of the canvas.

I'm tempted to think it's a bug or an oversight, but on the other hand I think I might find it very useful in working around Aqua's overrideredirect problems in one case. Anyway, I found it interesting and didn't see mention of this behavior in the manpage.

DKF: This is general window-clipping behaviour, and is (and should be) that way on all platforms (and is useful/relevant with the text widget too). It is also why widgets embedded in a canvas should usually be children of the canvas itself.

If you look carefully, you'll notice that the third widget overlaps the border of the canvas (it becomes more visible if you use a more visible - thicker and solid - border and highlight ring). A wrapper frame is sometimes necessary to prevent that sort of nonsense...

Feature Requests

-elide canvas

AMG: I'd like an -elide option for canvas items, similar to the text widget. This option, when set to true, would hide the item and inhibit user interaction. Currently, hiding an option can be done in one of four ways:

  • Move it out of the visible region.
  • Delete it.
  • Reset its colors to empty string.
  • Lower it behind an opaque background item.

All four options require the script to remember how to recreate or otherwise reestablish the item. Adding an -elide option would allow the full item configuration to persist inside the canvas widget, simplifying the script. Unlike moving or deleting the item, [bbox] will still work.

tomas: Does "$canv itemconfigure $item -state hidden" help? (-state normal brings the item back).

Of course instead of an individual item you can use a "tag search expression", so you can make whole swathes of objects disappear or re-appear.

Or did you have something else in mind?

AMG: No, this is perfect. I was unaware of -state hidden. Thank you!

Discussion

Zinc looks interesting. On the zinc site there are source & unix binaries. Has anyone successfully compiled it for windows? - Ian Gay

Christophe Mertz: As mentionned in zinc, since 3.2.9x version, TkZinc is now compiled for windows. We are also looking for port on OSX... Any volunteer?

Bugs


LV: What can people tell me about the canvas -state attribute? For instance, on the perl/tk mailing list recently, the following Tcl code was used to demonstrate what appears to be a Tk canvas bug:

package require Tk
pack [canvas .c]
set w [.c create window 50 50 -window [ label .c.label -text Hello ] -state hidden]
set l [.c create line   30 60 70 60 -state hidden]

set i 0

proc repeatproc {} {
    global i w l
    incr i
    if { $i%2 == 0 } {
        .c itemconfigure $w -state hidden
        .c itemconfigure $l -state hidden
    } else {
        .c itemconfigure $w -state normal
        .c itemconfigure $l -state normal
    }
    after 1000 repeatproc
}

after 1000 repeatproc