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.