[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? [AM] Yes, in a manner of speaking: it is another way of working with mathematical subjects in Tcl. This time, a bit more educational/presentational. I guess I am looking for the right form. ---- 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: # -
for a new paragraph
# -
to break the line
# -
andto 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 }
This note lets you inspect the following function:
f(x) = 1.0 + a*x + x^3You 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 " $trimmed] == 0 } {
$TXT insert end "\n\n"
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"
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]
]]