Arjen Markus (1 september 2008) Inspired by the mathematical animations you can find on mathworld.wolfram.com, I sat down and wrote the little application below. Enjoy!
AM The update proc is not quite as useful as it ought to be - confusion over args. Oh well, it was but half an evening's work ;).
AM (3 september 2008) I needed to draw a fairly elaborate sketch, one with a smooth curve, some auxiliary lines and so on. I found this program to be rather useful :). No animation, and perhaps a bit much work to set up the basic drawing, but changing it (as I had to) afterwards made it all worthwhile.
TR (4 September 2010) The code from this page is now going to be part of Plotchart version 2.0.
# mathanim.tcl -- # Experiments with a little language for creating mathematical # animations # package require Tcl 8.5 rename update tk_update # scaling -- # Set up the canvas and coordinates # # Arguments: # min Minimum value of the x/y axis # max Maximum value of the x/y axis # # Result: # None # # Side effects: # Default square canvas set up # proc scaling {min max} { global scaling set scaling(min) $min set scaling(max) $max pack [canvas .c -width 500 -height 500 -bg white] set scaling(dx) [expr {500.0/($max-$min)}] set scaling(dy) [expr {500.0/($max-$min)}] } # pause -- # Pause the program for a given amount of time # # Arguments: # delay Delay in ms # # Result: # None # # Side effects: # Program pauses, but any GUI is still active, screen updates occur # proc pause {delay} { after $delay {set ::delayOver 1} vwait ::delayOver } # create -- # Create an item on the canvas (type is fixed) # # Arguments: # name Name of the item # itemdata Data for the item # # Result: # None # # Side effects: # An item with the given name now exists and is possibly visible # proc create {name itemdata} { global item set item(type,$name) [lindex $itemdata 0] switch -- $item(type,$name) { "point" { # Nothing to do } "dot" { set item(id,$name) [.c create oval 0 0 0 0] } "circle" { set item(id,$name) [.c create oval 0 0 0 0] } "line" { set item(id,$name) [.c create line 0 0 0 0] } } if { $item(type,$name) != "point" } { .c itemconfig $item(id,$name) {*}[lindex $itemdata end] } update $name {*}$itemdata } # update -- # Update the properties of an item # # Arguments: # name Name of the item # itemdata Data for the item # # Result: # None # # Side effects: # The item has updated properties # proc update {name args} { global scaling global item set type $item(type,$name) if { $type != [lindex $args 0] } { error "Item $name and given data are incompatible!" } set item(data,$name) [lrange $args 1 end] UpdateCoords $name [lrange $item(data,$name) 0 end-1] } # UpdateCoords -- # Update the canvas coordinates of an item # # Arguments: # name Name of the item # coords World coordinates for the item # # Result: # None # # Side effects: # The item is updated on the screen # proc UpdateCoords {name coords} { global item global scaling switch -- $item(type,$name) { "point" { # Not visible } "dot" { foreach {x y} $coords {break} set xd1 [expr {$scaling(dx)*($x -$scaling(min)) - 3}] set yd1 [expr {$scaling(dy)*($scaling(max)-$y) - 3}] set xd2 [expr {$scaling(dx)*($x -$scaling(min)) + 3}] set yd2 [expr {$scaling(dy)*($scaling(max)-$y) + 3}] .c coords $item(id,$name) $xd1 $yd1 $xd2 $yd2 } "circle" { set r "" foreach {x y r} $coords {break} if { $r == "" } { set r [lindex $item(data,$name) 2] } set rad [expr {$scaling(dx)*$r}] set xd1 [expr {$scaling(dx)*($x-$scaling(min)) - $rad}] set yd1 [expr {$scaling(dy)*($scaling(max)-$y) - $rad}] set xd2 [expr {$scaling(dx)*($x-$scaling(min)) + $rad}] set yd2 [expr {$scaling(dy)*($scaling(max)-$y) + $rad}] .c coords $item(id,$name) $xd1 $yd1 $xd2 $yd2 } "line" { foreach {x1 y1 x2 y2} $coords {break} set xd1 [expr {$scaling(dx)*($x1-$scaling(min))}] set yd1 [expr {$scaling(dy)*($scaling(max)-$y1)}] set xd2 [expr {$scaling(dx)*($x2-$scaling(min))}] set yd2 [expr {$scaling(dy)*($scaling(max)-$y2)}] .c coords $item(id,$name) $xd1 $yd1 $xd2 $yd2 } } } # point, dot, circle, line -- # Prepare the item data for an (invisible) point # # Arguments: # args Such things as x-coordinate/y-coordinate but also -fill colour # # Result: # Item data for [create] or [update] # # Note: # The extra arguments are only used in the [create] procedure # # point, dot: # The first argment can be the name of a point or a pair of # coordinates. The rest is used as attributes # # circle: # The first argment can be the name of a point or a pair of # coordinates. The second must be the radius, the rest is used as # attributes # # line: # The first and second argments can be the name of a point or a pair # of coordinates. The rest is used as attributes. # proc point {first args} { global item if { [llength $first] == 2 } { return [concat point $first [list $args]] } else { return [concat point [lrange $item(data,$first) 0 1] [list $args]] } } proc dot {first args} { global item if { [llength $first] == 2 } { return [concat dot $first [list $args]] } else { return [concat dot [lrange $item(data,$first) 0 1] [list $args]] } } proc circle {first rad args} { global item if { [llength $first] == 2 } { return [concat circle $first $rad [list $args]] } else { return [concat circle [lrange $item(data,$first) 0 1] $rad [list $args]] } } proc line {first second args} { global item if { [llength $first] == 1 } { set first [lrange $item(data,$first) 0 1] } if { [llength $second] == 1 } { set second [lrange $item(data,$second) 0 1] } return [concat line $first $second [list $args]] } # add -- # Translate a point over a given vector and return the coordinates # # Arguments: # point Point (name or coordinate pair) # vector Vector (name or coordinate pair) # # Result: # New coordinate pair # proc add {point vector} { global item if { [llength $point] == 1 } { set point [lrange $item(data,$point) 0 1] } if { [llength $vector] == 1 } { set vector [lrange $item(data,$vector) 0 1] } foreach {xb yb} $point {xe ye} $vector { set xn [expr {$xb+$xe}] set yn [expr {$yb+$ye}] break } return [list $xn $yn] } # translate -- # Translate one or more item over a given vector and update the coordinates # # Arguments: # items List of items # vector Vector (name or coordinate pair) # # Result: # New coordinate pair # proc translate {items vector} { global item if { [llength $vector] == 1 } { set vector [lrange $item(data,$vector) 0 1] } set xv [lindex $vector 0] set yv [lindex $vector 1] foreach name $items { switch -- $item(type,$name) { "point" {set last 1} "dot" {set last 1} "circle" {set last 1} "line" {set last 3} } set coords [lrange $item(data,$name) 0 $last] set newcoords {} foreach {xc yc} $coords { set xn [expr {$xc + $xv}] set yn [expr {$yc + $yv}] lappend newcoords $xn $yn } set item(data,$name) [lreplace $item(data,$name) 0 $last {*}$newcoords] UpdateCoords $name $newcoords } } # rotate -- # Rotate one or more item over a given angle and update the coordinates # # Arguments: # items List of items # centre Centre of rotation (name or coordinate pair) # angle Angle (in radians) # # Result: # New coordinate pair # proc rotate {items centre angle} { global item if { [llength $centre] == 1 } { set centre [lrange $item(data,$centre) 0 1] } set xr [lindex $centre 0] set yr [lindex $centre 1] set cosa [expr {cos($angle)}] set sina [expr {sin($angle)}] foreach name $items { switch -- $item(type,$name) { "point" {set last 1} "dot" {set last 1} "circle" {set last 1} "line" {set last 3} } set coords [lrange $item(data,$name) 0 $last] set newcoords {} foreach {xc yc} $coords { set xn [expr {$xr + $cosa * ($xc-$xr) - $sina*($yc-$yr)}] set yn [expr {$yr + $sina * ($xc-$xr) + $cosa*($yc-$yr)}] lappend newcoords $xn $yn } set item(data,$name) [lreplace $item(data,$name) 0 $last {*}$newcoords] UpdateCoords $name $newcoords } } # track -- # Track a point # # Arguments: # cmd Command in question (start, next or stop) # point Name (!) of the point to track # args Extra attributes (for colour and such) # # Result: # None # # Side effect: # A line connecting the dots is drawn # proc track {cmd point args} { global item global scaling set xp [lindex $item(data,$point) 0] set yp [lindex $item(data,$point) 1] switch -- $cmd { "start" { set xp [expr {$scaling(dx)*($xp-$scaling(min))}] set yp [expr {$scaling(dy)*($scaling(max)-$yp)}] set item(track,$point) [.c create line $xp $yp $xp $yp {*}$args] } "next" { set coords [.c coords $item(track,$point)] lappend coords \ [expr {$scaling(dx)*($xp-$scaling(min))}] \ [expr {$scaling(dy)*($scaling(max)-$yp)}] .c coords $item(track,$point) $coords } "stop" { unset item(track,$point) } } } # main -- # Quick test # console show if {0} { scaling -5 5 create P [dot {0 0} -fill blue] create C1 [point {1 1}] create C [circle C1 2 -outline red] create L [line {0 0} {1 1} -fill green] for {set i 0} {$i < 20} {incr i} { puts "$i ..." translate C {-0.2 -0.2} pause 30 } } # # Create a cardioid # scaling -5 5 create C1 [point {0 0}] create C2 [point [add C1 {2 0}]] create circle1 [circle C1 1] create circle2 [circle C2 1] create P [dot [add C2 {1 0}] -fill blue] create line [line C2 P -fill red] set pi [expr {acos(-1.0)}] set angle [expr {2.0*$pi/100.0}] track start P -fill blue parray item for {set i 0} {$i < 100} {incr i} { rotate {C2 P line circle2} C1 $angle rotate {P line} C2 $angle track next P pause 30 } track stop P
yahalom: I copied the code to text file and I got "extra characters after close-brace". I looked a bit but did not find the problem.
AM In that sort cases I print ::errorInfo - as I copied it myself from the Wiki the other day, I suspect the code got mangled a bit. Try printing ::errorInfo - that ought to give some more information. Otherwise I will a closer look.
AM Note: it is using Tcl 8.5 features - {*}. Could that explain the error? Added a require statement.
gold 25Nov2017, added pix and some categories.