Richard Suchenwirth 2001-06-05 -- Of course there is BLT, but Tcl is just such a great wheel reinvention tool ;-) Somebody asked on comp.lang.tcl whether there's software for plotting functions, and I just needed a little challenge. So here it is: fun2points tabulates x/y values for a given function, plotpoints draws, scales, and moves such a tabulated function (or whatever other x/y coordinates - stock quotes over time?) on a given canvas, and some wrapping lines as usage example. Sheer fun...
#!/bin/sh
# next lines restarts \
exec wish "$0" ${1+"$@"}
proc fun2points {fun args} {
array set opt {
-from -4.0 -to 4.0 -step .1
}
array set opt $args
set res [list]
for {set x $opt(-from)} {$x <= $opt(-to)} {set x [expr {$x+$opt(-step)}]} {
if {![catch {expr $fun} y]} {lappend res $x $y}
}
set res
}
proc plotpoints {w points args} {
eval $w create line $points $args
set maxx [set minx [lindex $points 0]]
set maxy [set miny [lindex $points 1]]
foreach {x y} $points {
if {$x<$minx} {set minx $x}
if {$x>$maxx} {set maxx $x}
if {$y<$miny} {set miny $y}
if {$y>$maxy} {set maxy $y}
}
$w create line $minx 0 $maxx 0
$w create line 0 $miny 0 $maxy
set xfac [expr 0.95*[$w cget -width]/($maxx-$minx)]
set yfac [expr 0.95*[$w cget -height]/($maxy-$miny)]
$w scale all 0 0 $xfac -$yfac
foreach {minxc minyc} [$w bbox all] break
$w move all [expr -$minxc+5] [expr -$minyc+5]
}
set fun [lindex $argv 0]
pack [canvas .c]
plotpoints .c [fun2points $fun] -fill red
wm title . $fun
# usage example: funplot.tcl 'sin($x)'Here are actually some problems. This script probably runs on UNIX, but not on Windows 2000. I had to change it to the following form to be able to run it from the wish.
#!/bin/sh
# next lines restarts \
# the following line doesn't do anything under windows, does it?
# exec wish "$0" ${1+"$@"}
proc fun2points {fun args} {
array set opt {
-from -4.0 -to 4.0 -step .1
}
array set opt $args
set res [list]
for {set x $opt(-from)} {$x <= $opt(-to)} {set x [expr {$x+$opt(-step)}]} {
# i had to exchange the following line:
# if {![catch {expr $fun} y]} {lappend res $x $y}
set func $fun
append func ($x)
lappend res $x [expr $func]
}
set res
}
proc plotpoints {w points args} {
eval $w create line $points $args
set maxx [set minx [lindex $points 0]]
set maxy [set miny [lindex $points 1]]
foreach {x y} $points {
if {$x<$minx} {set minx $x}
if {$x>$maxx} {set maxx $x}
if {$y<$miny} {set miny $y}
if {$y>$maxy} {set maxy $y}
}
$w create line $minx 0 $maxx 0
$w create line 0 $miny 0 $maxy
set xfac [expr 0.95*[$w cget -width]/($maxx-$minx)]
set yfac [expr 0.95*[$w cget -height]/($maxy-$miny)]
$w scale all 0 0 $xfac -$yfac
foreach {minxc minyc} [$w bbox all] break
$w move all [expr -$minxc+5] [expr -$minyc+5]
}
proc funplot {argv} {
set fun [lindex $argv 0]
if {[winfo exists .c]} {destroy .c}
pack [canvas .c]
plotpoints .c [fun2points $fun] -fill red
wm title . $fun
}
# i also had to exchange the usage
# usage example: funplot sinThere is something very weird also: x should run up to 4.0 in this example, but it runs only to 3.9 :-(Does anyone understand this, please? Yes - comparing doubles is a real problem. Since I just wanted to cover the range from -Pi to +Pi, I didn't look that hard, but you're right, and can isolate the problem to expr {3.9+.01>1.0}which returns 1. so a cleaner test condition would have been for ... {($x-$opt(-to))<0.00001} ...or another delta that bridges the gap between real and evident - RSBLT is at http://www.tcltk.com/blt/
. See also emu_graph, padgraph.Here's a little functional analysis version:Let's say you have a file of x,y pairs:
-24,-21.73388069 -23,-19.4630693 -22,-0.194728804 -21,17.56976841 -20,18.25890501 -19,3 -18,-13.51777044 -17,-16.34375736 -16,-4.606453067 -15,9.754317602 -14,13.86850298 -13,5.462171479 -12,-6.438875016 -11,-6.438875016 -10,-5.440211109 -9,3.709066367 -8,17.914865973 -7,4.598906191 -6,-1.676492989 -5,-4.794621373 -4,-3.027209981 -3,0.5 -2,1.818594854 -1,0.841470985 1,0.841470985 2,1.818594854 3,0.423360024 4,-3.027209981 5,-4.794621373 6,-1.676492989 7,4.598906191 9,3.709066367 10,-5.440211109 11,-10.99989227 12,-6.438875016 13,5.462171479 14,13.86850298 15,9.754317602 16,-4.606453067 17,-16.34375736 18,-13.51777044 19,2.847666984 20,18.25890501 21,17.56976841 22,-0.194728804 23,-19.4630693 24,-21.73388069 25,-3.308793752 26,19.82651971 27,25.82215007 28,7.585362073 29,-19.24538264 30,-29.64094872 31,-12.52516701 32,17.2 34,18 35,-15 36,-35.70403872 37,-23.81091093 38,11.26200599 39,37.58802007 40,30 41,-6.503529421 42,-38.49390501 15.5,-6from some data source. You want to plot them and then see if you can match the plot with an analytical function (rather than fit a taylor series or fourier series digitally).
# Function Analysis
#-----------------------------------------------defaults
set gwth 650
set ghght 180
set numtc 12
set nxtc 8
set pfnm [pwd]/pvt.csv
#-----------------------------------------------globals
set gblst {cnvs gwth ghght minx maxx miny \
i numtc nxtc garr cgarr xscale yscale}
#-----------------------------------------------Main
wm title . "Functional Analysis"
wm deiconify .
foreach f1 {1 2 3 4 5} {
frame .$f1 -borderwidth 2 -relief groove
pack .$f1 -side top -pady 1
foreach f2 {1 2 3 4} {
frame .$f1.$f2 -borderwidth 4
pack .$f1.$f2 -side left
}
}
set w .1.2
button $w.filebut -text Plot\nFile -command {graph 0 $pfnm $gblst}
label $w.filelab -text "File: "
entry $w.filent -textvariable pfnm -width 38
label $w.txt -text "text file of x,y pairs"
pack $w.filebut $w.filelab -side left
pack $w.txt $w.filent -side top
bind $w.filent <Return> {graph 0 $pfnm $gblst}
bind $w.filent <F1> {
if [winfo exists .1.5] {
destroy .1.5
} else {
frame .1.5 -borderwidth 4
pack .1.5 -side top
label .1.5.text -text "File of the type: <x value>,<y value>"
pack .1.5.text
}
}
set w .4.2
set cnvs $w.gcvs1
frame $w.gp -borderwidth 4; pack $w.gp -side top
set w .4.2.gp
label $w.wdlab -text "Graph Width:"
entry $w.wdent -textvariable gwth -relief flat -bg grey -width 8
label $w.htlab -text "Graph Height:"
entry $w.htent -textvariable ghght -relief flat -bg grey -width 8
pack $w.wdlab $w.wdent $w.htlab $w.htent -side left
bind . <Escape> exit
#-----------------------------------------------Plot difference
proc diffPlt {gblst} {
set cmd "global"
foreach v $gblst {append cmd " $v"}
eval $cmd
for {set p 0} {$p < $i} {incr p} {
set garr($p,y3) [expr $garr($p,y)-$garr($p,y2)]
set cgarr($p,y3) [expr $ghght -($garr($p,y3)-$miny)*$yscale]
}
for {set p 1} {$p < $i} {incr p} {
set q [expr $p -1]
$cnvs create line $cgarr($q,x) $cgarr($q,y3) \
$cgarr($p,x) $cgarr($p,y3) -width 1 -fill #ff55aa
}
destroy .2.pfun .2.funlab .2.funent .2.difbut
}
#--------------------------------------------Plot file or function
proc graph {flg fun gblst} {
set cmd "global"
foreach v $gblst {append cmd " $v"}
eval $cmd
if {$flg == 0} {
destroy $cnvs
canvas $cnvs -width $gwth -height $ghght \
-borderwidth 2 -relief sunken -bg white
pack $cnvs -side bottom
set c0y $ghght
set c0x 0
set fid [open $fun r]
set pvtlst [split [read $fid] \n]
close $fid
foreach ptpr $pvtlst {
if {$ptpr != ""} {lappend pvtlst2 [split $ptpr ,]}
};#---------------------------------------note: comma delimited
set pvtlst [lsort -real -index 0 $pvtlst2];#----------------
set minx [lindex [lindex $pvtlst 0] 0]
set numelems [llength $pvtlst]
set lastelem [incr numelems -1]
set maxx [lindex [lindex $pvtlst $lastelem] 0]
# get all cartesian pairs to plot
set i 0
foreach ptpair $pvtlst {
set garr($i,x) [lindex $ptpair 0]
set garr($i,y) [lindex $ptpair 1]
incr i
}
#now "i" is the number of array elements since it starts w/ 0 and goes to i-1
# turn cartesian pairs into canvas coordinates
# first find ymax and ymin
set maxy $garr(0,y)
set miny $garr(0,y)
for {set p 1} {$p<$i} {incr p} {
if {$garr($p,y)<$miny} then {set miny $garr($p,y)}
if {$garr($p,y)>$maxy} then {set maxy $garr($p,y)}
}
set yscale [expr 1.00*$ghght/($maxy-$miny)]
set xscale [expr 1.00*$gwth/($maxx-$minx)]
for {set p 0} {$p < $i} {incr p} {
set cgarr($p,x) [expr $c0x +($garr($p,x)-$minx)*$xscale]
set cgarr($p,y) [expr $c0y -($garr($p,y)-$miny)*$yscale]
}
# create lines in canvas
set c $cnvs
# draw Yaxis
set xmd [expr $gwth/2]
set tcinc [expr $ghght/$numtc]
$c create line $xmd $c0y $xmd 0 -width 1 -fill white
for {set p 0} {$p<$numtc} {incr p} {
set tcy [expr $ghght - $tcinc*$p]
$c create line 0 $tcy $gwth $tcy -width 1 -fill grey
set yval [format "%3.2f" [expr $miny+$p*$tcinc/$yscale]]
$c create text $xmd $tcy -text $yval -fill grey
}
set xl [expr $gwth/($i*3)]
$c create text $xl $tcinc -text $minx -fill grey -anchor w
$c create text $gwth $tcinc -text $maxx -fill grey -anchor e
set ntx [expr $nxtc - 1]
set tcd [expr int($gwth/$ntx)]
set xdl [expr int($maxx-$minx)/$ntx]
incr ntx -1
for {set p 1} {$p<=$ntx} {incr p} {
incr xl $tcd
set xtx [expr {$minx+$p*$xdl}]
$c create text $xl $tcinc -text $xtx -fill grey
}
for {set p 1} {$p < $i} {incr p} {
set q [expr $p -1]
$c create line $cgarr($q,x) $cgarr($q,y) $cgarr($p,x) $cgarr($p,y) -width 1
}
set w .2.2
destroy $w.pfun $w.funlab $w.funent
button $w.pfun -text Plot\nFunction \
-command {graph 1 $funstr $gblst} -fg blue
label $w.funlab -text "enter function; tcl format; x is independent var."
entry $w.funent -textvariable funstr -width 50
pack $w.pfun -side left -padx 6
pack $w.funlab $w.funent -side top
bind $w.funent <Return> {graph 1 $funstr $gblst}
bind $w.funent <F1> {
if [winfo exists .5.5] {
destroy .5.5
} else {
frame .5.5 -borderwidth 4
pack .5.5 -side top
label .5.5.text -text {
FUNCTIONS
acos cos hypot sinh asin cosh log sqrt
atan exp log10 tan atan2 floor pow tanh
ceil fmod sin
OPERATORS
-,+,~,! *,/ +,-
<<,>> <,>,<=,>=
==,!=,&,^,|,&&,||,x?y:z
} -justify left
pack .5.5.text
}
}
} else {
for {set p 0} {$p<$i} {incr p} {
# Assume that input function uses "x" as independant variable
set fun2 [string map {x $garr($p,x)} $fun]
# make sure "exp" function not clobberd
set fun2 [string map {e$garr($p,x)p exp} $fun2]
set garr($p,y2) [expr $fun2]
}
for {set p 0} {$p < $i} {incr p} {
set cgarr($p,y2) [expr {$ghght -($garr($p,y2)-$miny)*$yscale}]
}
for {set p 1} {$p < $i} {incr p} {
set q [expr $p -1]
$cnvs create line $cgarr($q,x) $cgarr($q,y2) \
$cgarr($p,x) $cgarr($p,y2) -width 1 -fill blue
}
set w .2.2
destroy $w.difbut
button $w.difbut -text "Plot (file-minus-function)" \
-command {diffPlt $gblst} -fg red
pack $w.difbut -side bottom -pady 4
bind $w.difbut <Return> {diffPlt $gblst}
}
}
