- The commands are short
- You can draw in cartesian and polar coordinates
- You have a limited set of attributes to worry about
- By using relative drawing commands, you do not have to calculate the coordinates - just let the script worry about them.
AM (24 june 2003) I have started to improve the script below, with the suggestions from Peter Milne (notably the bounding box) and a new mode, "turtle", with accompanying commands modelled after LOGO's turtle graphics.The thing is becoming rather lengthy (mostly because of the comments, mind you, that head each proc), so probably the best way to distribute it, is as a starkit.For yet another example of what you can do with this little package: Daddy, how does a computer workAM (8 july 2003) Submitted the application as a starkit, with four different demos to choose from - including the fractions demo by Peter (slightly adjusted to fit the default screen). The starkit is called "plain_geometry", to emphasize that it deals with simple geometry in the (Euclidean) plane.See the sdarchive for the starkit.
See also Turtle graphics the LOGO way
AM (12 january 2005) This approach still makes it necessary to do all kinds of coordinate computations. I wanted to avoid that - it is tedious and error-prone. So, here is a quick alternative - Drawing geometrical objects
# constructions.tcl --
#
# Package providing tools for showing geometrical constructions
#
# Version information:
# version 0.1: initial implementation, december 2002
#
# Constructions --
# namespace to hold all specific variables and procedures
#
package require Tk
namespace eval ::Constructions {
variable mode "cartesian"
variable canvas .c
variable colour black
variable fillcolour black
variable textcolour black
variable textfont "Times 10"
variable delay 300
variable xcurr 0.0
variable ycurr 0.0
variable width 12.0
variable height 12.0
variable xmin
variable xmax
variable ymin
variable ymax
variable degtorad
set degtorad [expr {3.1415926/180.0}]
namespace export draw display moveto colour mode \
textfont textcolour erase
}
# mode --
# Set the coordinates mode (cartesian or polar)
#
# Arguments:
# type New mode
#
# Result:
# None
#
# Side effect:
# The interpretation of coordinate arguments is changed, if the
# type is a valid type. Otherwise it is left unchanged
#
proc ::Constructions::mode {type} {
variable mode
if { $type == "cartesian" || $type == "polar" } {
set mode $type
}
}
# textcolour --
# Set the colour for text
#
# Arguments:
# newcolour New colour to use
#
# Result:
# None
#
# Side effect:
# Set a new colour for subsequent text drawing actions
#
proc ::Constructions::textcolour {newcolour} {
variable textcolour
set textcolour $newcolour
}
# textfont --
# Set the font for text
#
# Arguments:
# newfont New font to use
#
# Result:
# None
#
# Side effect:
# Set a new font for subsequent text drawing actions
#
proc ::Constructions::textfont {newfont} {
variable textfont
set textfont $newfont
}
# colour --
# Set the current colour
#
# Arguments:
# newcolour New colour to be used for outlines
# newfill New colour to be used for filling (defaults to newcolour)
#
# Result:
# None
#
# Side effect:
# Set a new colour for subsequent drawing actions
#
proc ::Constructions::colour {newcolour {newfill "same"}} {
variable colour
variable fillcolour
set colour $newcolour
if { $newfill == "same" } {
set fillcolour $newcolour
} else {
set fillcolour $newfill
}
}
# moveto --
# Set the current coordinates
#
# Arguments:
# newx New x coordinate or distance from origin
# newy New y coordinate or angle to positive x-axis
#
# Result:
# None
#
# Side effect:
# Set a new "current" position for subsequent drawing actions
#
proc ::Constructions::moveto {newx newy} {
variable mode
variable xcurr
variable ycurr
variable degtorad
if { $mode == "cartesian" } {
set xcurr $newx
set ycurr $newy
} else {
set dist $newx
set angle $newy
set xcurr [expr {$dist*cos($angle*$degtorad)}]
set ycurr [expr {$dist*sin($angle*$degtorad)}]
}
}
# moverel --
# Move the current coordinates by the given vector
#
# Arguments:
# delx X coordinate of vector over which to move or distance
# dely Y coordinate or angle
#
# Result:
# None
#
# Side effect:
# Set a new "current" position for subsequent drawing actions
#
proc ::Constructions::moverel {delx dely} {
variable xcurr
variable ycurr
if { $mode == "cartesian" } {
set xcurr [expr {$xcurr+$delx}]
set ycurr [expr {$ycurr+$dely}]
} else {
set dist $delx
set angle $dely
set xcurr [expr {$xcurr+$dist*cos($angle*$degtorad)}]
set ycurr [expr {$ycurr+$dist*sin($angle*$degtorad)}]
}
}
# erase --
# Erase items from the canvas
#
# Arguments:
# tagorid Tag or ID of item(s) to erase
#
# Result:
# None
#
# Side effect:
# Removes items from the canvas
#
proc ::Constructions::erase {tagorid} {
variable canvas
$canvas delete $tagorid
}
# draw --
# Draw an object into the canvas
#
# Arguments:
# objtype Type of object
# args List of arguments, appropriate for type
#
# Result:
# ID of object that was created (or a specific tag)
#
proc ::Constructions::draw {objtype args} {
variable mode
variable xcurr
variable ycurr
variable xmin
variable xmax
variable ymin
variable ymax
variable canvas
variable degtorad
variable colour
variable fillcolour
variable textcolour
variable textfont
variable delay
variable go_on
switch -- $objtype {
"grid" {
for { set x $xmin } { $x < $xmax } { set x [expr {$x+1.0}] } {
$canvas create line ${x}c ${ymin}c ${x}c ${ymax}c -tag grid -fill gray
}
for { set y $ymin } { $y < $ymax } { set y [expr {$y+1.0}] } {
$canvas create line ${xmin}c ${y}c ${xmax}c ${y}c -tag grid -fill gray
}
$canvas move grid ${xmax}c ${ymax}c
return grid
}
"axes" {
$canvas create line ${xmin}c 0.0c ${xmax}c 0.0c -tag axes -fill black
$canvas create line 0.0c ${ymin}c 0.0c ${ymax}c -tag axes -fill black
$canvas move axes ${xmax}c ${ymax}c
return axes
}
"line" {
if { $mode == "cartesian" } {
set xp [lindex $args 0]
set yp [lindex $args 1]
set xcurr [lindex $args 2]
set ycurr [lindex $args 3]
} else {
set dist1 [lindex $args 0]
set angle1 [lindex $args 1]
set dist2 [lindex $args 2]
set angle2 [lindex $args 3]
set xp [expr {$dist1*cos($angle1*$degtorad)}]
set yp [expr {$dist1*sin($angle1*$degtorad)}]
set xcurr [expr {$dist2*cos($angle2*$degtorad)}]
set ycurr [expr {$dist2*sin($angle2*$degtorad)}]
}
set x1 "${xp}c"
set y1 "[expr {-$yp}]c"
set x2 "${xcurr}c"
set y2 "[expr {-$ycurr}]c"
set obj [\
$canvas create line $x1 $y1 $x2 $y2 -fill $colour]
}
"linerel" {
set x1 "${xcurr}c"
set y1 "[expr {-$ycurr}]c"
if { $mode == "cartesian" } {
set xcurr [lindex $args 0]
set ycurr [lindex $args 1]
} else {
set dist [lindex $args 0]
set angle [lindex $args 1]
set xcurr [expr {$xcurr+$dist*cos($angle*$degtorad)}]
set ycurr [expr {$ycurr+$dist*sin($angle*$degtorad)}]
}
set x2 "${xcurr}c"
set y2 "[expr {-$ycurr}]c"
set obj [\
$canvas create line $x1 $y1 $x2 $y2 -fill $colour]
}
"circle" -
"disc" {
set rad [lindex $args 0]
set x1 "[expr {$xcurr-$rad}]c"
set y1 "[expr {-$ycurr+$rad}]c"
set x2 "[expr {$xcurr+$rad}]c"
set y2 "[expr {-$ycurr-$rad}]c"
if { $objtype == "circle" } {
set fill {}
} else {
set fill $fillcolour
}
set obj [\
$canvas create oval $x1 $y1 $x2 $y2 -outline $colour -fill $fill]
}
"arc" -
"pie" {
set rad [lindex $args 0]
set start [lindex $args 1]
set stop [lindex $args 2]
set x1 "[expr {$xcurr-$rad}]c"
set y1 "[expr {-$ycurr+$rad}]c"
set x2 "[expr {$xcurr+$rad}]c"
set y2 "[expr {-$ycurr-$rad}]c"
if { $objtype == "arc" } {
set fill {}
set style arc
} else {
set fill $fillcolour
set style pie
}
set obj [\
$canvas create arc $x1 $y1 $x2 $y2 -outline $colour \
-start $start -extent [expr {$stop-$start}] \
-style $style -fill $fill]
}
"text" {
set x1 "${xcurr}c"
set y1 "[expr {-$ycurr}]c"
set text [lindex $args 0]
set obj [\
$canvas create text $x1 $y1 -text $text -fill $textcolour \
-font $textfont]
}
default {return {}}
}
#
# Move the newly created object to the centre of the window,
# that is, correct for the origin
#
$canvas move $obj ${xmax}c ${ymax}c
#
# Wait a while before returning - gives a nice animated effect
#
set go_on 0
after $delay {set ::Constructions::go_on 1}
vwait ::Constructions::go_on
return $obj
}
# display --
# Create the initial canvas
#
# Arguments:
# None
# Result:
# None
#
proc ::Constructions::display {} {
variable canvas
variable xmin
variable xmax
variable ymin
variable ymax
variable width
variable height
canvas $canvas -background white -width ${width}c -height ${height}c
pack $canvas -fill both
set xmin [expr {-$width/2.0}]
set xmax [expr {+$width/2.0}]
set ymin [expr {-$height/2.0}]
set ymax [expr {+$height/2.0}]
draw grid
draw axes
}# main --
# Main code
#
namespace import ::Constructions::*
if { 1 } {
display
moveto 0.0 5.5
textfont "Times 14"
draw text "Construct a hexagon"
#
# Reset the drawing position - all is relative
#
moveto 0.0 0.0
mode "polar"
colour "black"
draw circle 5.0
colour "red"
moveto 5.0 90
draw disc 0.1
draw arc 5.0 -90 -20
draw linerel 5.0 -30
draw disc 0.1
draw arc 5.0 -70 -100
draw linerel 5.0 -90
draw disc 0.1
draw arc 5.0 -130 -160
draw linerel 5.0 -150
draw disc 0.1
draw arc 5.0 -200 -223
draw linerel 5.0 -210
draw disc 0.1
draw arc 5.0 -263 -289
draw linerel 5.0 -270
draw disc 0.1
draw arc 5.0 -310 -335
draw linerel 5.0 -330
draw disc 0.1
}
if { 0 } {
mode "cartesian"
colour "black" "blue"
textcolour "black"
textfont "Times 20 bold"
display
erase axes
erase grid
moveto -3.5 3.5
draw pie 1.8 90.1 270 ;# Ugly drawing under Windows 98
moveto -4.0 0
draw text "1/2"
moveto -2.5 3.5
draw text "+"
moveto -2.5 0
draw text "+"
moveto 0.5 3.5
draw pie 1.8 90.1 270
moveto 0.0 0
draw text "1/2"
moveto 1.0 3.5
draw text "="
moveto 1.0 0
draw text "="
moveto 4.0 3.5
draw disc 1.8
moveto 4.0 0
draw text "1"
}Peter Milne peterm@remware.demon.co.uk ( I hope you don't mind direct updates :-) ) It is a useful little toolkit, easy to write extended applications - I tried a short extension of the fractions picture. Biggest problem was maintaining the x-cursor - maybe the canvas can return its bounding rectangle to make this easier?AM Please, updating is part of the Wiki philosophy, and it shows that people read and use these pages. (I corrected the formatting a bit). Thanks for the feedback. I will try and see what can be done.
#
# Example fraction teaching app (Peter Milne peterm@remware.demon.co.uk)
#
# pie --
# Convenience proc to draw a pie
#
# Arguments:
# x y origin
# t1 t2 start, end angles (normalised for easy math, display)
# label
# fillcolour
#
# Result:
# Updated x cursor
# WORKTODO: x update is a kludge, large on lh pies, small on rh pies
#
# Side effect:
# Set a new colour for subsequent drawing actions
#
proc pie {x y t1 t2 label fillcolour} {
moveto $x $y
colour "black" $fillcolour
draw pie 1.8 [expr $t1+90] [expr $t2+90]
moveto $x [expr $y-3.5 ]
draw text $label
return [expr $x+2]
}
# label --
# Convenience proc to draw a label
#
# Arguments:
# x y origin
# label
#
# Result:
# Updated x cursor
#
proc label {x y label} {
moveto $x $y
draw text $label
return [expr $x+2.5]
}
if { 1 } {
mode "cartesian"
textcolour "black"
textfont "Times 20 bold"
display
erase axes
erase grid
set x -3.5; set y 7
set x [pie $x $y 0 180 "1/2" "blue"]
set x [label $x $y "+"]
set x [pie $x $y 180 360 "1/2" "yellow"]
set x [label $x $y "="]
pie $x $y 0 180 "1" "blue"
pie $x $y 180 360 "1" "yellow"
set x -3.5; set y 0
set x [pie $x $y 0 120 "1/3" "blue"]
set x [label $x $y "+"]
set x [pie $x $y 120 240 "1/3" "yellow"]
set x [label $x $y "+"]
set x [pie $x $y 240 360 "1/3" "red"]
set x [label $x $y "="]
pie $x $y 0 120 "1" "blue"
pie $x $y 120 240 "1" "yellow"
pie $x $y 240 360 "1" "red"
set x -3.5; set y -7
set x [pie $x $y 0 90 "1/4" "blue"]
set x [label $x $y "+"]
set x [pie $x $y 90 180 "1/4" "yellow"]
set x [label $x $y "+"]
set x [pie $x $y 180 270 "1/4" "red"]
set x [label $x $y "+"]
set x [pie $x $y 270 360 "1/4" "green"]
set x [label $x $y "="]
pie $x $y 0 90 "1" "blue"
pie $x $y 90 180 "1" "yellow"
pie $x $y 180 270 "1" "red"
pie $x $y 270 360 "1" "green"
}
