[Arjen Markus] (18 december 2003) I am mildly proud to present a first (underdocumented) version of a ''mathematical notebook''. ---- [LV] Is this in any way related to your tclmath.kit mathematical workbench work? ---- After at least 10000 seconds of hard labour, meticulous programming and self-defying testing, it works! I had to use the most cunning techniques I could muster: * pre-coding design phase (meaning I wrote some scribbles on a piece of paper on the way home) * rapid application development (meaning I was anxious to see it grow, so I ran it as soon as something could show up) * code reuse by multiple copy-paste-edit cycles (I hardly ever start from scratch, but simply copy an existing file into a new directory) * visual testing (meaning I tried to see that it did what I wanted it to do) This was all needed to meet my deadline: the end of the evening. But I did it! Below you will find the first fruits of this herculean achievement. Enjoy! Okay, what is it? It is a viewer for files that I call "mathematical notes": * HTML-like formatted text to show an explanation * Tcl code to define entry widgets for editing parameters * Tcl code to embed a canvas widget with a picture The example will make it a bit clearer. ''Note:'' this is not only meant for the mathematically inclined, you can set up any kind of "exercise book" you like. This being the first version, its user-interface is limited: You will have to use a command-line like: wish mathbook.tcl example.nb ---- Example of an input file - store this as a file "example.nb" For other examples: [Mathematical notebook - examples] ---- # Example of a mathematical note to show the possibilities # # Note: # The number of HTML-like tags is kept to a bare minimum: # -

big letters (the whole line;

ignored) # -

for a new paragraph # -
to break the line # -

 and 
to indicate preformatted text # - leading blanks are significant # # These tags are only recognised at the start of the line! # # Initialisation: define a simple function # @init { proc pol3 {x} { variable a expr {1.0+$a*$x+$x*$x*$x} } set a 0.0 set xmin -5.0 set xmax 5.0 }

Third-degree polynomial functions

This note lets you inspect the following function:


    f(x) = 1.0 + a*x + x^3

 
You can fill in the parameter a: @entry a 10

You can also change the horizontal limits of the graph:

Minimum x: @entry xmin 10
Maximum x: @entry xmax 10

By changing the value of "a" and pressing the Refresh button (or the Enter key), you update the graph:

@canvas 300 200 { set nosteps 50 set data [func pol3 $xmin $xmax $nosteps] scale $data axes polyline $data black polyline [list $xmin $xmin $xmax $xmax] red text 0.0 0.0 "Origin" } Note: the red line is the line "y = x" ---- End of example ---- Here is the code: ---- # mathbook.tcl -- # Script to show notes on mathematical subjects # # TODO: # - Implement a number of useful drawing commands # - Implement a formula renderer (a basic one _is_ available) # - Implement more convenient bindings # - Describe the application # # Missing commands: # @refresh - define your own refresh method # @label - allow a label (useful for variable text) # @button - allow a pushbutton # package require Tk # MathData -- # Namespace for the user-defined commands and data # namespace eval ::MathData:: { variable CNV "" variable TXT "" } # scale -- # Set up the scaling for the given canvas # Arguments: # data List of data (x, y, x, y ...) # Result: # None # Side effects: # Scaling parameters set # Note: # TODO: Should make sure there is some scaling involved # if only using pixels # proc ::MathData::scale { data } { variable CNV variable SCALE set width [$CNV cget -width] set height [$CNV cget -height] set xmin 1.0e30 set xmax -1.0e30 set ymin 1.0e30 set ymax -1.0e30 foreach {x y} $data { if { $x < $xmin } { set xmin $x } if { $x > $xmax } { set xmax $x } if { $y < $ymin } { set ymin $y } if { $y > $ymax } { set ymax $y } } if { $xmin == $xmax } { set xmax [expr {$xmax+1.0}] } if { $ymin == $ymax } { set ymax [expr {$ymax+1.0}] } set SCALE(xscale) [expr {$width/double($xmax-$xmin)}] set SCALE(yscale) [expr {$height/double($ymax-$ymin)}] set SCALE(xmin) $xmin set SCALE(xmax) $xmax set SCALE(ymin) $ymin set SCALE(ymax) $ymax } # polyline -- # Draw a line consisting of multiple points # Arguments: # data List of data (x, y, x, y ...) # colour Colour to use (default: black) # Result: # None # Side effects: # Line drawn according to current scales # proc ::MathData::polyline { data {colour black} } { variable CNV variable SCALE set xscale $SCALE(xscale) set yscale $SCALE(yscale) set xmin $SCALE(xmin) set xmax $SCALE(xmax) set ymin $SCALE(ymin) set ymax $SCALE(ymax) set pixels {} foreach {x y} $data { set px [expr {$xscale*($x-$xmin)}] set py [expr {$yscale*($ymax-$y)}] lappend pixels $px $py } $CNV create line $pixels -fill $colour } # text -- # Draw a text string at a given position # Arguments: # x X coordinate # y Y coordinate # string String to show # Result: # None # Side effects: # String drawn # proc ::MathData::text { x y string } { variable CNV variable SCALE set xscale $SCALE(xscale) set yscale $SCALE(yscale) set xmin $SCALE(xmin) set xmax $SCALE(xmax) set ymin $SCALE(ymin) set ymax $SCALE(ymax) set px [expr {$xscale*($x-$xmin)}] set py [expr {$yscale*($ymax-$y)}] $CNV create text $px $py -text $string -anchor nw } # axes -- # Draw two lines representing the axes # Arguments: # None # Result: # None # Side effects: # Two lines drawn (no labels yet) # proc ::MathData::axes { } { variable CNV variable SCALE set width [$CNV cget -width] set height [$CNV cget -height] set xscale $SCALE(xscale) set yscale $SCALE(yscale) set xmin $SCALE(xmin) set xmax $SCALE(xmax) set ymin $SCALE(ymin) set ymax $SCALE(ymax) set px0 [expr {$xscale*(0.0-$xmin)}] set py0 [expr {$yscale*($ymax-0.0)}] $CNV create line $px0 0 $px0 $height -fill black $CNV create line 0 $py0 $width $py0 -fill black } # func -- # Repeatedly run a function and return xy-pairs # Arguments: # funcname Name of the function (procedure) # xmin Minimum x-value # xmax Maximum x-value # nosteps Number of steps (inbetween; default: 50) # Result: # List of x, y values # proc ::MathData::func { funcname xmin xmax { nosteps 50 } } { set coords {} set xstep [expr {($xmax-$xmin)/double($nosteps)}] for { set i 0 } { $i <= $nosteps } { incr i } { set x [expr {$xmin+$i*$xstep}] set y [$funcname $x] lappend coords $x $y } return $coords } # MathBook -- # Namespace for the mathbook commands and data # namespace eval ::MathBook:: { variable count 0 variable CNV variable TXT } # @init -- # Execute code once (when reading the notebook file) # Arguments: # code Code to run # Result: # Nothing # proc ::MathBook::@init { code } { namespace eval ::MathData $code } # @canvas -- # Create a canvas of given size # Arguments: # width Width in pixels # height Height in pixels # code Code to execute # Result: # Nothing # Side effect: # Canvas created # proc ::MathBook::@canvas { width height code } { variable CNV variable CNVCODE variable TXT variable count incr count set CNV $TXT.cnv$count set ::MathData::CNV $CNV set CNVCODE($CNV) $code canvas $CNV -width $width -height $height -bg white $TXT insert end "\n" $TXT window create end -window $CNV $TXT insert end "\n" namespace eval ::MathData $code } # @entry -- # Create an entry widget of given width # Arguments: # name Name of the associated variable # width Width of the widget (in characters) # Result: # Nothing # Side effect: # Entry created # proc ::MathBook::@entry { name width } { variable TXT variable count incr count set entry $TXT.entry$count entry $entry -textvariable ::MathData::$name -width $width $TXT window create end -window $entry bind $entry ::MathBook::Refresh } # Refresh -- # Refresh the canvases and labels etc. # Arguments: # None # Result: # None # Side effect: # Canvases refreshed and whatever occurs in the @refresh method # proc ::MathBook::Refresh { } { variable CNV variable CNVCODE variable TXT variable count foreach {name code} [array get CNVCODE] { set ::MathData::CNV $name $name delete all namespace eval ::MathData $code } } # initMainWindow -- # Create the main window # Arguments: # None # Result: # None # Side effect: # Main window created # proc ::MathBook::initMainWindow { } { variable TXT variable count set count 0 set tf .textframe set tw $tf.text set TXT $tw frame $tf scrollbar $tf.scrollx -orient horiz -command "$tw xview" scrollbar $tf.scrolly -command "$tw yview" text $tw -yscrollcommand "$tf.scrolly set" \ -xscrollcommand "$tf.scrollx set" \ -fg black -bg white -font "courier 10" \ -wrap word grid $tw $tf.scrolly grid $tf.scrollx x grid $tw -sticky news grid $tf.scrolly -sticky ns grid $tf.scrollx -sticky ew grid columnconfigure $tf 0 -weight 1 grid rowconfigure $tf 0 -weight 1 button .refresh -text Refresh -command ::MathBook::Refresh -width 10 button .exit -text Exit -command exit -width 10 grid $tf - -sticky news grid .refresh .exit -sticky nw grid columnconfigure . 0 -weight 1 grid columnconfigure . 1 -weight 1 grid rowconfigure . 0 -weight 1 $tw tag configure bigbold -font "helvetica 12 bold" $tw tag configure normal -font "courier 10" $tw tag configure preform -font "courier 10" -background "lightgrey" } # fillTextWindow -- # Fill the text window # Arguments: # filename Name of the notebook file to use # Result: # None # Side effect: # Text window filled # proc ::MathBook::fillTextWindow { filename } { variable TXT set infile [open $filename "r"] set just "" set tag normal while { [gets $infile line] >= 0 } { set trimmed [string trim $line] # # Analyse the contents ... # if { [string first "#" $trimmed] == 0 } { continue } # Ignore empty lines, unless in preformatted text if { $trimmed == "" } { if { $just != "" } { $TXT insert end "\n" $tag } continue } if { [string first "@" $trimmed] == 0 } { RunWholeCommand $infile $line continue } if { [string first "

" $trimmed] == 0 } { set tag bigbold set trimmed [string map {

""

""} $trimmed] } if { [string first "
" $trimmed] == 0 } {
          $TXT insert end "\n"
          set tag  "preform"
          set just "\n"
          continue
       }
       if { [string first "
" $trimmed] == 0 } { $TXT insert end "\n" set tag "normal" set just "" continue } if { [string first "

" $trimmed] == 0 } { $TXT insert end "\n\n" continue } if { [string first "
" $trimmed] == 0 } { $TXT insert end "\n" continue } if { $just == "" } { $TXT insert end "$trimmed " $tag } else { $TXT insert end "$line\n" $tag } if { $tag == "bigbold" } { set tag "normal" } } close $infile $TXT configure -state disabled } # RunWholeCommand -- # Run an embedded command # Arguments: # infile Handle to the file # line First line of the command # Result: # None # Side effect: # Whatever the command does # proc ::MathBook::RunWholeCommand { infile line } { variable TXT while { ! [info complete $line] } { if { [gets $infile nextline] >= 0 } { append line "\n$nextline" } else { break } } eval $line } # main -- # Get the whole thing going # ::MathBook::initMainWindow ::MathBook::fillTextWindow [lindex $argv 0] ---- [[ [Category Mathematics] ]]