Richard Suchenwirth 2001-05-28 - My teenage daughter hates math. In order to motivate her, I beefed up A little function plotter which before only took one function, in strict Tcl (expr) notation, from the command line. Now there's an entry widget, and the accepted language has also been enriched: beyond exprs rules, you can omit dollar and multiplication signs, like 2x+1, powers can be written as x3 instead of ($x*$x*$x); in simple cases you can omit parens round function arguments, like sin x2. Hitting <Return> in the entry widget displays the function's graph.
If you need some ideas, click on the "?" button to cycle through a set of demo functions, from boring to bizarre (e.g. if rand() is used). Besides default scaling, you can zoom in or out. Moving the mouse pointer over the canvas displays x and y coordinates, and the display changes to white if you're on a point on the curve.
The target was not reached: my daughter still hates math. But at least I had hours of Tcl (and function) fun again, surfing in the Cartesian plane... hope you enjoy it too!
A starkit version of this code is available on sdarchive.
Jeff Smith 2019-09-09 : Below is an online demo using CloudTk
package require Tk proc main {} { canvas .c -bg white -borderwidth 0 bind .c <Motion> {displayXY .info %x %y} frame .f label .f.1 -text "f(x) = " entry .f.f -textvar ::function -width 40 bind .f.f <Return> {plotf .c $::function} button .f.demo -text " ? " -pady 0 -command {demo .c} label .f.2 -text " Zoom: " entry .f.fac -textvar ::factor -width 4 set ::factor 32 bind .f.fac <Return> {zoom .c 1.0} button .f.plus -text " + " -pady 0 -command {zoom .c 2.0} button .f.minus -text " - " -pady 0 -command {zoom .c 0.5} eval pack [winfo children .f] -side left -fill both label .info -textvar ::info -just left pack .info .f -fill x -side bottom pack .c -fill both -expand 1 demo .c } set ::demos { "cos x3" 2 1-x 0.5x2 x3/5 "sin x" "sin x2" 1/x sqrt(x) "tan x/5" x+1/x x abs(x) "exp x" "log x" "log x2" round(x) "int x%2" "x-int x" "0.2tan x+1/tan x" x*(rand()-0.5) x2/5-1/(2x) "atan x" sqrt(1-x2) "abs(x-int(x*2))" (x-1)/(x+1) "sin x-tan x" "sin x-tan x2" "x-abs(int x)" 0.5x-1/x -0.5x3+x2+x-1 3*sin(2x) -0.05x4-0.2x3+1.5x2+2x-3 "9%int x" 0.5x2/(x3-3x2+4) "abs x2-3 int x" "int x%3" } proc displayXY {w cx cy} { set x [expr {double($cx-$::dx)/$::factor}] set y [expr {double(-$cy+$::dy)/$::factor}] set ::info [format "x=%.2f y=%.2f" $x $y] catch { $w config -fg [expr {abs([expr $::fun]-$y)<0.01?"white":"black"}] } ;# may divide by zero, or other illegal things } proc zoom {w howmuch} { set ::factor [expr round($::factor*$howmuch)] plotf $w $::function } proc plotf {w function} { foreach {re subst} { {([a-z]) +(x[0-9]?)} {\1(\2)} " " "" {([0-9])([a-z])} {\1*\2} x2 x*x x3 x*x*x x4 x*x*x*x x \$x {e\$xp} exp } {regsub -all $re $function $subst function} set ::fun $function set ::info "Tcl: expr $::fun" set color [lpick {red blue purple brown green}] plotline $w [fun2points $::fun] -fill $color } proc lpick L {lindex $L [expr {int(rand()*[llength $L])}]} proc fun2points {fun args} { array set opt {-from -10.0 -to 10.0 -step .01} array set opt $args set res "{" for {set x $opt(-from)} {$x<= $opt(-to)} {set x [expr {$x+$opt(-step)}]} { if {![catch {expr $fun} y]} { if {[info exists lasty] && abs($y-$lasty)>100} { append res "\} \{" ;# incontinuity } append res " $x $y" set lasty $y } else {append res "\} \{"} } append res "}" } proc plotline {w points args} { $w delete all foreach i $points { if {[llength $i]>2} {eval $w create line $i $args -tags f} } set fac $::factor $w scale all 0 0 $fac -$fac $w create line -10000 0 10000 0 ;# X axis $w create line 0 -10000 0 10000 ;# Y axis $w create line $fac 0 $fac -3 ;# x=1 tick $w create line -3 -$fac 0 -$fac ;# y=1 tick set ::dx [expr {[$w cget -width]/2}] set ::dy [expr {[$w cget -height]/2}] $w move all $::dx $::dy $w raise f } proc demo {w} { set ::function [lindex $::demos 0] ;# cycle through... set ::demos [concat [lrange $::demos 1 end] [list $::function]] set ::factor 32 plotf $w $::function } main
PT 13-May-2003: This is fantastic! I wish I'd had one of these when I was at school. Great job.
23-Oct-2011: I've created 2005 a version with little more GUI and "screen" management. It was very helpful when writing my diploma thesis, see tkFPlot.
RR 24Nov03: I had a similar idea because of a problem I had at work. Sometimes I get a set of x-y data. I can plot it, of course. There are ways to approximate the data with some polynomial or Fourier series. In some cases, however, that misses some (relatively) simple analytical formula which is actually a better fit to the data. So I built a little script that plots a file of (comma separated) x-y data. Then, using the same scales and intervals, plots a user-input formula (I ditched the '$' but still require Tcl syntax). Then, you can plot the difference. In practice, this has only been really useful a couple of times, but then it was quite useful!
# Function Analysis #-----------------------------------------------defaults set gwth 650 set ghght 250 set numtc 12 set nxtc 8 set pfnm [pwd]/pvt.csv set flatc SystemButtonFace #-----------------------------------------------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 } } bind $w.filent <Button-3> {set pfnm [tk_getOpenFile]} 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 $flatc -width 8 label $w.htlab -text "Graph Height:" entry $w.htent -textvariable ghght -relief flat -bg $flatc -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} } #bind . <Control-l> {exec wish83 protols.tcl $::pfnm} bind . <Control-l> {set fid [open "| wish83 protols.tcl $::pfnm" r+]} }
See also A little graph plotter